Adds favicon to Dash/Zeal docset, adds OpenSearch metadata. (#25178)

This sets the favicon for the offline Dash/Zeal docs.

Also, sets up the OpenSearch Description metadata file so that people can create custom search shortcuts for the API docs site.

Fixes #6412
diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh
index dbc1f4e..eba6776 100755
--- a/dev/bots/docs.sh
+++ b/dev/bots/docs.sh
@@ -47,6 +47,7 @@
   echo "Building Flutter docset."
   rm -rf flutter.docset
   (dashing build --source ./doc --config ./dashing.json > /tmp/dashing.log 2>&1 || (tail -100 /tmp/dashing.log; false)) && \
+  cp ./doc/flutter/static-assets/favicon.png ./flutter.docset/icon.png && \
   tar cf flutter.docset.tar.gz --use-compress-program="gzip --best" flutter.docset
 }
 
@@ -62,7 +63,7 @@
   if [[ "$CIRRUS_BRANCH" == "stable" ]]; then
     echo -e "<entry>\n  <version>${FLUTTER_VERSION}</version>\n  <url>https://docs.flutter.io/offline/flutter.docset.tar.gz</url>\n</entry>" > doc/offline/flutter.xml
   else
-    echo -e "<entry>\n  <version>${FLUTTER_VERSION}</version>\n  <url>https://master-docs-flutter-io.firebaseapp.com/offline/flutter.docset.tar.gz</url>\n</entry>" > doc/offline/flutter.xml
+    echo -e "<entry>\n  <version>${FLUTTER_VERSION}</version>\n  <url>https://master-docs.flutter.io/offline/flutter.docset.tar.gz</url>\n</entry>" > doc/offline/flutter.xml
   fi
   mv flutter.docset.tar.gz doc/offline/flutter.docset.tar.gz
   du -sh doc/offline/flutter.docset.tar.gz
@@ -123,7 +124,7 @@
 if [[ -n "$CIRRUS_CI" && -z "$CIRRUS_PR" ]]; then
   echo "This is not a pull request; considering whether to upload docs... (branch=$CIRRUS_BRANCH)"
   if [[ "$CIRRUS_BRANCH" == "master" ]]; then
-    echo "Updating $CIRRUS_BRANCH docs: https://master-docs-flutter-io.firebaseapp.com/"
+    echo "Updating $CIRRUS_BRANCH docs: https://master-docs.flutter.io/"
     # Disable search indexing on the master staging site so searches get only
     # the stable site.
     echo -e "User-agent: *\nDisallow: /" > "$FLUTTER_ROOT/dev/docs/doc/robots.txt"
diff --git a/dev/docs/lib/opensearch.xml b/dev/docs/lib/opensearch.xml
new file mode 100644
index 0000000..888ab40
--- /dev/null
+++ b/dev/docs/lib/opensearch.xml
@@ -0,0 +1,14 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Flutter API</ShortName>
+<Description>
+Welcome to the Flutter API reference documentation search.
+
+Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time.
+Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.
+
+The API reference herein covers all libraries that are exported by the Flutter SDK.
+</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="32" height="32" type="image/x-icon">/flutter/static-assets/favicon.png</Image>
+<Url type="text/html" method="get" template="{SITE_URL}?search={searchTerms}"/>
+</OpenSearchDescription>
diff --git a/dev/docs/opensearch.html b/dev/docs/opensearch.html
new file mode 100644
index 0000000..0748469
--- /dev/null
+++ b/dev/docs/opensearch.html
@@ -0,0 +1 @@
+<link rel="search" type="application/opensearchdescription+xml" title="Flutter API" href="/opensearch.xml"/>
diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart
index 43bc5dd..2c259cf 100644
--- a/dev/tools/dartdoc.dart
+++ b/dev/tools/dartdoc.dart
@@ -100,6 +100,7 @@
 
   createFooter('$kDocsRoot/lib/footer.html');
   copyAssets();
+  createSearchMetadata('$kDocsRoot/lib/opensearch.xml', '$kDocsRoot/doc/opensearch.xml');
   cleanOutSnippets();
 
   final List<String> dartdocBaseArgs = <String>['global', 'run'];
@@ -134,6 +135,7 @@
     '--header', 'analytics.html',
     '--header', 'survey.html',
     '--header', 'snippets.html',
+    '--header', 'opensearch.html',
     '--footer-text', 'lib/footer.html',
     '--exclude-packages',
     <String>[
@@ -231,20 +233,25 @@
 
 final RegExp gitBranchRegexp = RegExp(r'^## (.*)');
 
+String getBranchName() {
+  final ProcessResult gitResult = Process.runSync('git', <String>['status', '-b', '--porcelain']);
+  if (gitResult.exitCode != 0)
+    throw 'git status exit with non-zero exit code: ${gitResult.exitCode}';
+  final Match gitBranchMatch = gitBranchRegexp.firstMatch(
+      gitResult.stdout.trim().split('\n').first);
+  return gitBranchMatch == null ? '' : gitBranchMatch.group(1).split('...').first;
+}
+
 void createFooter(String footerPath) {
   const int kGitRevisionLength = 10;
 
-  ProcessResult gitResult = Process.runSync('git', <String>['rev-parse', 'HEAD']);
+  final ProcessResult gitResult = Process.runSync('git', <String>['rev-parse', 'HEAD']);
   if (gitResult.exitCode != 0)
     throw 'git rev-parse exit with non-zero exit code: ${gitResult.exitCode}';
   String gitRevision = gitResult.stdout.trim();
 
-  gitResult = Process.runSync('git', <String>['status', '-b', '--porcelain']);
-   if (gitResult.exitCode != 0)
-    throw 'git status exit with non-zero exit code: ${gitResult.exitCode}';
-  final Match gitBranchMatch = gitBranchRegexp.firstMatch(
-      gitResult.stdout.trim().split('\n').first);
-  final String gitBranchOut = gitBranchMatch == null ? '' : '• </span class="no-break">${gitBranchMatch.group(1).split('...').first}</span>';
+  final String gitBranch = getBranchName();
+  final String gitBranchOut = gitBranch.isEmpty ? '' : '• </span class="no-break">$gitBranch</span>';
 
   gitRevision = gitRevision.length > kGitRevisionLength ? gitRevision.substring(0, kGitRevisionLength) : gitRevision;
 
@@ -256,6 +263,21 @@
     gitBranchOut].join(' '));
 }
 
+/// Generates an OpenSearch XML description that can be used to add a custom
+/// search for Flutter API docs to the browser. Unfortunately, it has to know
+/// the URL to which site to search, so we customize it here based upon the
+/// branch name.
+void createSearchMetadata(String templatePath, String metadataPath) {
+  final String template = File(templatePath).readAsStringSync();
+  final String branch = getBranchName();
+  final String metadata = template.replaceAll(
+    '{SITE_URL}',
+    branch == 'stable' ? 'https://docs.flutter.io/' : 'https://master-docs.flutter.io/',
+  );
+  Directory(path.dirname(metadataPath)).create(recursive: true);
+  File(metadataPath).writeAsStringSync(metadata);
+}
+
 /// Recursively copies `srcDir` to `destDir`, invoking [onFileCopied], if
 /// specified, for each source/destination file pair.
 ///
@@ -292,7 +314,8 @@
           (File src, File dest) => print('Copied ${src.path} to ${dest.path}'));
 }
 
-
+/// Clean out any existing snippets so that we don't publish old files from
+/// previous runs accidentally.
 void cleanOutSnippets() {
   final Directory snippetsDir = Directory(path.join(kPublishRoot, 'snippets'));
   if (snippetsDir.existsSync()) {