Docs: add search box

Adds an in-site search using the Google Custom Search API.
Demo: https://perfetto-docs-demo.storage.googleapis.com/docs/index.html

Change-Id: I70d7e7d0b8a27a5bdbd653d114dfa9b83407f7d9
diff --git a/infra/perfetto.dev/src/assets/script.js b/infra/perfetto.dev/src/assets/script.js
index 91c57e5..aa368bc 100644
--- a/infra/perfetto.dev/src/assets/script.js
+++ b/infra/perfetto.dev/src/assets/script.js
@@ -253,6 +253,69 @@
   document.body.appendChild(script);
 }
 
+function setupSearch() {
+  const URL =
+      'https://www.googleapis.com/customsearch/v1?key=AIzaSyBTD2XJkQkkuvDn76LSftsgWOkdBz9Gfwo&cx=007128963598137843411:8suis14kcmy&q='
+  const searchContainer = document.getElementById('search');
+  const searchBox = document.getElementById('search-box');
+  const searchRes = document.getElementById('search-res')
+  if (!searchBox || !searchRes) return;
+
+  document.body.addEventListener('keydown', (e) => {
+    if (e.key === '/' && e.target.tagName.toLowerCase() === 'body') {
+      searchBox.setSelectionRange(0, -1);
+      searchBox.focus();
+      e.preventDefault();
+    } else if (e.key === 'Escape' && searchContainer.contains(e.target)) {
+      searchBox.blur();
+
+      // Handle the case of clicking Tab and moving down to results.
+      e.target.blur();
+    }
+  });
+
+  let timerId = -1;
+  let lastSearchId = 0;
+
+  const doSearch = async () => {
+    timerId = -1;
+    searchRes.style.width = `${searchBox.offsetWidth}px`;
+
+    // `searchId` handles the case of two subsequent requests racing. This is to
+    // prevent older results, delivered in reverse order, to replace newer ones.
+    const searchId = ++lastSearchId;
+    const f = await fetch(URL + encodeURIComponent(searchBox.value));
+    const jsonRes = await f.json();
+    const results = jsonRes['items'];
+    searchRes.innerHTML = '';
+    if (results === undefined || searchId != lastSearchId) {
+      return;
+    }
+    for (const res of results) {
+      const link = document.createElement('a');
+      link.href = res.link;
+      const title = document.createElement('div');
+      title.className = 'sr-title';
+      title.innerText = res.title.replace(' - Perfetto Tracing Docs', '');
+      link.appendChild(title);
+
+      const snippet = document.createElement('div');
+      snippet.className = 'sr-snippet';
+      snippet.innerText = res.snippet;
+      link.appendChild(snippet);
+
+      const div = document.createElement('div');
+      div.appendChild(link);
+      searchRes.appendChild(div);
+    }
+  };
+
+  searchBox.addEventListener('keyup', () => {
+    if (timerId >= 0) return;
+    timerId = setTimeout(doSearch, 200);
+  });
+}
+
 window.addEventListener('DOMContentLoaded', () => {
   updateNav();
   updateTOC();
@@ -275,6 +338,7 @@
   }
 
   updateTOC();
+  setupSearch();
 
   // Enable animations only after the load event. This is to prevent glitches
   // when switching pages.
diff --git a/infra/perfetto.dev/src/assets/style.scss b/infra/perfetto.dev/src/assets/style.scss
index 7f23862..63d6fc1 100644
--- a/infra/perfetto.dev/src/assets/style.scss
+++ b/infra/perfetto.dev/src/assets/style.scss
@@ -147,8 +147,105 @@
     }
 }
 
+
+#search {
+    position: relative;
+    flex-grow: 0;
+    transition: flex-grow cubic-bezier(1, 0.01, 1, 1) var(--anim-time), background-color ease var(--anim-time);
+    padding: 0;
+    &::before {
+        visibility: hidden;
+        user-select: none;
+        content: '';
+        position: fixed;
+        left: 0;
+        right: 0;
+        top: var(--site-header-height);
+        bottom: 0;
+        z-index: -100;
+        background-color: rgba(255, 255, 255, 0.8);
+        backdrop-filter: blur(3px);
+        opacity: 0;
+        transition: opacity ease var(--anim-time), visibility 0s;
+
+    }
+    &:focus-within {
+        flex-grow: 1000;
+        &::before {
+            display: block;
+            opacity: 1;
+            visibility: visible;
+        }
+        #search-res {
+            display: block;
+        }
+
+    }
+
+    @media #{$mobile} {
+        display: none;
+    }
+
+    #search-box {
+        width: 100%;
+        height: 32px;
+        font-size: 1rem;;
+        color: #333;
+        background-color: rgba(255, 255, 255, 0.9);
+        border: 1px solid #eee;
+        border-radius: 2px;
+        background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Z"/></svg>');
+        background-repeat: no-repeat;
+        background-size: contain;
+        padding-left: 40px;
+        outline: none;
+        &:hover, &:focus {
+            background-color: rgba(255, 255, 255, 0.95);
+        }
+    }
+
+    #search-res {
+        display: none;
+        background-color: rgba(255, 255, 255, 1.0);
+        border: 1px solid #eee;
+        box-shadow: #aaa 0px 1px 5px;
+        color: #333;
+        line-height: initial;
+        margin-top: -4px;
+        overflow-x: auto;
+        position: fixed;
+        top: var(--site-header-height);
+        max-height: calc(100vh - var(--site-header-height));
+        z-index: 10;
+        >div {
+            padding: 10px;
+            margin: 0;
+            &:hover {
+                background-color: #f0f0f0;
+            }
+        }
+        .sr-title {
+            color: #333;
+            font-weight: bold;
+        }
+        .sr-snippet {
+            color: #444;
+            font-size: 0.9rem;
+         }
+
+        a { text-decoration: none; }
+        a:hover { color: initial };
+
+        &:empty {
+            visibility: hidden;
+        }
+    }
+
+}
+
+
 // -----------------------------------------------------------------------------
-// Site header
+// Site footer
 // -----------------------------------------------------------------------------
 
 // Footer in the index page.
diff --git a/infra/perfetto.dev/src/template_header.html b/infra/perfetto.dev/src/template_header.html
index 45960d9..46fc39b 100644
--- a/infra/perfetto.dev/src/template_header.html
+++ b/infra/perfetto.dev/src/template_header.html
@@ -29,6 +29,13 @@
   </div>
   <a href="#toggle" class="menu"><i class="material-icons-round">menu</i></a>
 
+  <% if (fileName.startsWith('/docs/')) { %>
+    <div id="search">
+      <input id="search-box" type="text" placeholder="Search" autocomplete="off">
+      <div id="search-res"></div>
+    </div>
+  <% } %>
+
   <a href="/docs/">Docs</a>
   <a href="/docs/contributing/getting-started#community">Community</a>
   <a href="https://ui.perfetto.dev/">Trace Viewer</a>