Merge autoland to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Sun, 10 Mar 2019 23:39:24 +0200
changeset 521293 364e947a111c70809e481ea29e2a09ae66d357e8
parent 521273 e6a4cd115b45542c35f2ac2ab64758602a619f30 (current diff)
parent 521292 99b57fdeaf5467fbf323d87d79e3b59cc9e4632f (diff)
child 521294 2369f5bbf37977bf974744cdeb072dacc4babb90
child 521308 b61917acc3b7c804be4a2e3678a66714fc6ad75e
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
364e947a111c / 67.0a1 / 20190310214003 / files
nightly linux64
364e947a111c / 67.0a1 / 20190310214003 / files
nightly mac
364e947a111c / 67.0a1 / 20190310214003 / files
nightly win32
364e947a111c / 67.0a1 / 20190310214003 / files
nightly win64
364e947a111c / 67.0a1 / 20190310214003 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
remote/ContentProcessSession.jsm
remote/Session.jsm
remote/Target.jsm
remote/Targets.jsm
remote/frame-script.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1374,17 +1374,17 @@ pref("plain_text.wrap_long_lines", true)
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // All the Geolocation preferences are here.
 //
 #ifndef EARLY_BETA_OR_EARLIER
-pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
+pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_LOCATION_SERVICE_API_KEY%");
 #else
 // Use MLS on Nightly and early Beta.
 pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
 #endif
 
 #ifdef XP_MACOSX
 pref("geo.provider.use_corelocation", true);
 #endif
--- a/browser/base/content/test/about/browser_aboutSupport.js
+++ b/browser/base/content/test/about/browser_aboutSupport.js
@@ -1,22 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 add_task(async function() {
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:support" }, async function(browser) {
-    let keyGoogleStatus = await ContentTask.spawn(browser, null, async function() {
-      let textBox = content.document.getElementById("key-google-box");
+    let keyLocationServiceGoogleStatus = await ContentTask.spawn(browser, null, async function() {
+      let textBox = content.document.getElementById("key-location-service-google-box");
       await ContentTaskUtils.waitForCondition(() => content.document.l10n.getAttributes(textBox).id,
-        "Google API key status loaded");
+        "Google location service API key status loaded");
       return content.document.l10n.getAttributes(textBox).id;
     });
-    ok(keyGoogleStatus, "Google API key status shown");
+    ok(keyLocationServiceGoogleStatus, "Google location service API key status shown");
+
+    let keySafebrowsingGoogleStatus = await ContentTask.spawn(browser, null, async function() {
+      let textBox = content.document.getElementById("key-safebrowsing-google-box");
+      await ContentTaskUtils.waitForCondition(() => content.document.l10n.getAttributes(textBox).id,
+        "Google Safebrowsing API key status loaded");
+      return content.document.l10n.getAttributes(textBox).id;
+    });
+    ok(keySafebrowsingGoogleStatus, "Google Safebrowsing API key status shown");
+
 
     let keyMozillaStatus = await ContentTask.spawn(browser, null, async function() {
       let textBox = content.document.getElementById("key-mozilla-box");
       await ContentTaskUtils.waitForCondition(() => content.document.l10n.getAttributes(textBox).id,
         "Mozilla API key status loaded");
       return content.document.l10n.getAttributes(textBox).id;
     });
     ok(keyMozillaStatus, "Mozilla API key status shown");
--- a/browser/components/urlbar/tests/UrlbarTestUtils.jsm
+++ b/browser/components/urlbar/tests/UrlbarTestUtils.jsm
@@ -141,16 +141,28 @@ var UrlbarTestUtils = {
    * @returns {number} the number of results.
    */
   getResultCount(win) {
     let urlbar = getUrlbarAbstraction(win);
     return urlbar.getResultCount();
   },
 
   /**
+   * Returns the results panel object associated with the window.
+   * @note generally tests should use getDetailsOfResultAt rather than
+   * accessing panel elements directly.
+   * @param {object} win The window containing the urlbar
+   * @returns {object} the results panel object.
+   */
+  getPanel(win) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.panel;
+  },
+
+  /**
    * Ensures at least one search suggestion is present.
    * @param {object} win The window containing the urlbar
    * @returns {boolean} whether at least one search suggestion is present.
    */
   promiseSuggestionsPresent(win) {
     let urlbar = getUrlbarAbstraction(win);
     return urlbar.promiseSearchSuggestions();
   },
@@ -396,16 +408,24 @@ class UrlbarAbstraction {
       let actions = element.getElementsByClassName("urlbarView-action");
       let typeIcon = element.querySelector(".urlbarView-type-icon");
       let typeIconStyle = this.window.getComputedStyle(typeIcon);
       details.displayed = {
         title: element.getElementsByClassName("urlbarView-title")[0].textContent,
         action: actions.length > 0 ? actions[0].textContent : null,
         typeIcon: typeIconStyle["background-image"],
       };
+      let actionElement = element.getElementsByClassName("urlbarView-action")[0];
+      let urlElement = element.getElementsByClassName("urlbarView-url")[0];
+      details.element = {
+        action: actionElement,
+        row: element,
+        separator: urlElement || actionElement,
+        url: urlElement,
+      };
       if (details.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
         details.searchParams = {
           engine: context.results[index].payload.engine,
           keyword: context.results[index].payload.keyword,
           query: context.results[index].payload.query,
           suggestion: context.results[index].payload.suggestion,
         };
       }
@@ -422,16 +442,22 @@ class UrlbarAbstraction {
       details.tags = style.includes("tag") ?
         [...element.getElementsByClassName("ac-tags")].map(e => e.textContent) : [];
       let typeIconStyle = this.window.getComputedStyle(element._typeIcon);
       details.displayed = {
         title: element._titleText.textContent,
         action: action ? element._actionText.textContent : "",
         typeIcon: typeIconStyle.listStyleImage,
       };
+      details.element = {
+        action: element._actionText,
+        row: element,
+        separator: element._separator,
+        url: element._urlText,
+      };
       if (details.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
         details.searchParams = {
           engine: action.params.engineName,
           keyword: action.params.alias,
           query: action.params.input,
           suggestion: action.params.searchSuggestion,
         };
       }
--- a/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_searchSuggestions.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_searchSuggestions.js
@@ -1,93 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests various actions relating to search suggestions and the one-off buttons.
+ */
+
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE2_BASENAME = "searchSuggestionEngine2.xml";
+
+const serverInfo = {
+  scheme: "http",
+  host: "localhost",
+  port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
 
 add_task(async function init() {
   await PlacesUtils.history.clear();
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.oneOffSearches", true],
           ["browser.urlbar.suggest.searches", true]],
   });
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
+  let engine2 = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE2_BASENAME);
   let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.moveEngine(engine2, 0);
   await Services.search.moveEngine(engine, 0);
   await Services.search.setDefault(engine);
   registerCleanupFunction(async function() {
     await Services.search.setDefault(oldDefaultEngine);
 
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
     await UrlbarTestUtils.promisePopupClose(window);
   });
 });
 
+async function withSecondSuggestion(testFn) {
+  await BrowserTestUtils.withNewTab(gBrowser, async () => {
+    let typedValue = "foo";
+    await promiseAutocompleteResultPopup(typedValue, window, true);
+    await promiseSuggestionsPresent();
+    assertState(0, -1, typedValue);
+
+    // Down to select the first search suggestion.
+    EventUtils.synthesizeKey("KEY_ArrowDown");
+    assertState(1, -1, "foofoo");
+
+    // Down to select the next search suggestion.
+    EventUtils.synthesizeKey("KEY_ArrowDown");
+    assertState(2, -1, "foobar");
+
+    await withHttpServer(serverInfo, () => {
+      return testFn();
+    });
+  });
+  await PlacesUtils.history.clear();
+}
+
 // Presses the Return key when a one-off is selected after selecting a search
 // suggestion.
-add_task(async function oneOffReturnAfterSuggestion() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+add_task(async function test_returnAfterSuggestion() {
+  await withSecondSuggestion(async () => {
+    // Alt+Down to select the first one-off.
+    EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
+    assertState(2, 0, "foobar");
 
-  let typedValue = "foo";
-  await promiseAutocompleteResultPopup(typedValue, window, true);
-  await promiseSuggestionsPresent();
-  assertState(0, -1, typedValue);
-
-  // Down to select the first search suggestion.
-  EventUtils.synthesizeKey("KEY_ArrowDown");
-  assertState(1, -1, "foofoo");
+    let resultsPromise =
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                     `http://mochi.test:8888/?terms=foobar`);
+    EventUtils.synthesizeKey("KEY_Enter");
+    await resultsPromise;
+  });
+});
 
-  // Down to select the next search suggestion.
-  EventUtils.synthesizeKey("KEY_ArrowDown");
-  assertState(2, -1, "foobar");
-
-  // Alt+Down to select the first one-off.
-  EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
-  assertState(2, 0, "foobar");
+// Presses the Return key when a non-default one-off is selected after selecting
+// a search suggestion.
+add_task(async function test_returnAfterSuggestion_nonDefault() {
+  await withSecondSuggestion(async () => {
+    // Alt+Down twice to select the second one-off.
+    EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
+    EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
+    assertState(2, 1, "foobar");
 
-  let resultsPromise =
-    BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
-                                   `http://mochi.test:8888/?terms=foobar`);
-  EventUtils.synthesizeKey("KEY_Enter");
-  await resultsPromise;
-
-  await PlacesUtils.history.clear();
-  BrowserTestUtils.removeTab(tab);
+    let resultsPromise =
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                     `http://localhost:20709/?terms=foobar`);
+    EventUtils.synthesizeKey("KEY_Enter");
+    await resultsPromise;
+  });
 });
 
 // Clicks a one-off engine after selecting a search suggestion.
-add_task(async function oneOffClickAfterSuggestion() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+add_task(async function test_clickAfterSuggestion() {
+  await withSecondSuggestion(async () => {
+    let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+    let resultsPromise =
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                     `http://mochi.test:8888/?terms=foobar`);
+    EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
+    await resultsPromise;
+  });
+});
 
-  let typedValue = "foo";
-  await promiseAutocompleteResultPopup(typedValue, window, true);
-  await promiseSuggestionsPresent();
-  assertState(0, -1, typedValue);
-
-  // Down to select the first search suggestion.
-  EventUtils.synthesizeKey("KEY_ArrowDown");
-  assertState(1, -1, "foofoo");
+// Clicks a non-default one-off engine after selecting a search suggestion.
+add_task(async function test_clickAfterSuggestion_nonDefault() {
+  await withSecondSuggestion(async () => {
+    let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+    let resultsPromise =
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                     `http://localhost:20709/?terms=foobar`);
+    EventUtils.synthesizeMouseAtCenter(oneOffs[1], {});
+    await resultsPromise;
+  });
+});
 
-  // Down to select the next search suggestion.
-  EventUtils.synthesizeKey("KEY_ArrowDown");
-  assertState(2, -1, "foobar");
+// Selects a non-default one-off engine and then selects a search suggestion.
+add_task(async function test_selectOneOffThenSuggestion() {
+  if (!UrlbarPrefs.get("quantumbar")) {
+    // The legacy address bar doesn't work correctly for this scenario.
+    return;
+  }
+  await BrowserTestUtils.withNewTab(gBrowser, async () => {
+    let typedValue = "foo";
+    await promiseAutocompleteResultPopup(typedValue, window, true);
+    await promiseSuggestionsPresent();
+    assertState(0, -1, typedValue);
 
-  let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
-  let resultsPromise =
-    BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
-                                   `http://mochi.test:8888/?terms=foobar`);
-  EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
-  await resultsPromise;
+    // Select a non-default one-off engine.
+    EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
+    EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
+    assertState(0, 1, "foo");
 
-  await PlacesUtils.history.clear();
-  BrowserTestUtils.removeTab(tab);
+    // Now click the second suggestion.
+    await withHttpServer(serverInfo, async () => {
+      let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+
+      let resultsPromise =
+        BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                       `http://localhost:20709/?terms=foobar`);
+      EventUtils.synthesizeMouseAtCenter(result.element.row, {});
+      await resultsPromise;
+    });
+  });
 });
 
 add_task(async function overridden_engine_not_reused() {
   info("An overridden search suggestion item should not be reused by a search with another engine");
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.withNewTab(gBrowser, async () => {
     let typedValue = "foo";
     await promiseAutocompleteResultPopup(typedValue, window, true);
     await promiseSuggestionsPresent();
     // Down to select the first search suggestion.
     EventUtils.synthesizeKey("KEY_ArrowDown");
     assertState(1, -1, "foofoo");
     // ALT+Down to select the second search engine.
     EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
@@ -98,17 +166,17 @@ add_task(async function overridden_engin
     let label = result.displayed.action;
     // Run again the query, check the label has been replaced.
     await UrlbarTestUtils.promisePopupClose(window);
     await promiseAutocompleteResultPopup(typedValue, window, true);
     await promiseSuggestionsPresent();
     assertState(0, -1, "foo");
     result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
     Assert.notEqual(result.displayed.action, label, "The label should have been updated");
-    BrowserTestUtils.removeTab(tab);
+  });
 });
 
 function assertState(result, oneOff, textValue = undefined) {
   Assert.equal(UrlbarTestUtils.getSelectedIndex(window), result,
     "Expected result should be selected");
   Assert.equal(UrlbarTestUtils.getOneOffSearchButtons(window).selectedButtonIndex,
     oneOff, "Expected one-off should be selected");
   if (textValue !== undefined) {
--- a/browser/components/urlbar/tests/browser/browser_urlbar_empty_search.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_empty_search.js
@@ -22,30 +22,35 @@ add_task(async function test_setup() {
 
 add_task(async function test_empty() {
   info("Test searching for nothing");
   await promiseAutocompleteResultPopup("", window, true);
   // The first search collects the results, the following ones check results
   // are the same.
   let results = [{}]; // Add a fake first result, to account for heuristic.
   for (let i = 0; i < await UrlbarTestUtils.getResultCount(window); ++i) {
-    results.push(await UrlbarTestUtils.getDetailsOfResultAt(window, i));
+    let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+    // Don't compare the element part of the results.
+    delete result.element;
+    results.push(result);
   }
 
   for (let str of [" ", "  "]) {
     info(`Test searching for "${str}"`);
     await promiseAutocompleteResultPopup(str, window, true);
     // Skip the heuristic result.
     Assert.ok((await UrlbarTestUtils.getDetailsOfResultAt(window, 0)).heuristic,
               "The first result is heuristic");
     let length = await UrlbarTestUtils.getResultCount(window);
     Assert.equal(length, results.length, "Comparing results count");
     for (let i = 1; i < length; ++i) {
-      Assert.deepEqual(await UrlbarTestUtils.getDetailsOfResultAt(window, i),
-                        results[i],
+      let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+      // Don't compare the element part of the results.
+      delete result.element;
+      Assert.deepEqual(result, results[i],
                        `Comparing result at index ${i}`);
     }
   }
   await UrlbarTestUtils.promisePopupClose(window);
 });
 
 add_task(async function test_backspace_empty() {
   info("Testing that deleting the input value via backspace closes the popup");
--- a/browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<ShortName>browser_searchSuggestionEngine2 searchSuggestionEngine2.xml</ShortName>
 <!-- Redirect the actual search request to the test-server because of proxy restriction -->
 <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs?{searchTerms}"/>
 <!-- Redirect speculative connect to a local http server we run for this test -->
 <Url type="text/html" method="GET" template="http://localhost:20709/" rel="searchform">
   <Param name="terms" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/config/mozconfigs/linux32/common-opt
+++ b/browser/config/mozconfigs/linux32/common-opt
@@ -1,14 +1,15 @@
 # This file is sourced by nightly, beta, and release mozconfigs.
 
 . $topsrcdir/build/mozconfig.stylo
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
-ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=/builds/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=/builds/sb-gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
 
 . $topsrcdir/build/unix/mozconfig.linux32
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/linux64/common-opt
+++ b/browser/config/mozconfigs/linux64/common-opt
@@ -1,14 +1,15 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
 . $topsrcdir/build/mozconfig.stylo
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
-ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=/builds/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=/builds/sb-gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
 
 . $topsrcdir/build/unix/mozconfig.linux
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/linux64/nightly-asan-reporter
+++ b/browser/config/mozconfigs/linux64/nightly-asan-reporter
@@ -1,13 +1,14 @@
 # We still need to build with debug symbols
 ac_add_options --disable-debug
 ac_add_options --enable-optimize="-O2 -gline-tables-only"
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
-ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=/builds/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=/builds/sb-gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
 
 . $topsrcdir/build/mozconfig.stylo
 
 # ASan specific options on Linux
 ac_add_options --enable-valgrind
 
 . $topsrcdir/build/unix/mozconfig.asan
--- a/browser/config/mozconfigs/macosx64/common-opt
+++ b/browser/config/mozconfigs/macosx64/common-opt
@@ -1,14 +1,15 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
 . $topsrcdir/build/macosx/mozconfig.common
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
-ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=/builds/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=/builds/sb-gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
 # Package js shell.
--- a/browser/config/mozconfigs/win32/common-opt
+++ b/browser/config/mozconfigs/win32/common-opt
@@ -1,17 +1,18 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
 . "$topsrcdir/build/mozconfig.stylo"
 
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
-ac_add_options --with-google-api-keyfile=${WORKSPACE}/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=${WORKSPACE}/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=${WORKSPACE}/sb-gapi.data
 
 ac_add_options --with-mozilla-api-keyfile=${WORKSPACE}/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
--- a/browser/config/mozconfigs/win64-aarch64/common-opt
+++ b/browser/config/mozconfigs/win64-aarch64/common-opt
@@ -1,17 +1,18 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
 . "$topsrcdir/build/mozconfig.stylo"
 
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
-ac_add_options --with-google-api-keyfile=${WORKSPACE}/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=${WORKSPACE}/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=${WORKSPACE}/sb-gapi.data
 
 ac_add_options --with-mozilla-api-keyfile=${WORKSPACE}/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -1,17 +1,18 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
 . "$topsrcdir/build/mozconfig.stylo"
 
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
-ac_add_options --with-google-api-keyfile=${WORKSPACE}/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=${WORKSPACE}/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=${WORKSPACE}/sb-gapi.data
 
 ac_add_options --with-mozilla-api-keyfile=${WORKSPACE}/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
--- a/browser/config/mozconfigs/win64/nightly-asan-reporter
+++ b/browser/config/mozconfigs/win64/nightly-asan-reporter
@@ -1,12 +1,13 @@
 MOZ_AUTOMATION_L10N_CHECK=0
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
-ac_add_options --with-google-api-keyfile=${WORKSPACE}/gapi.data
+ac_add_options --with-google-location-service-api-keyfile=${WORKSPACE}/gls-gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=${WORKSPACE}/sb-gapi.data
 ac_add_options --with-mozilla-api-keyfile=${WORKSPACE}/mozilla-desktop-geoloc-api.key
 
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --disable-debug
 ac_add_options --enable-optimize="-O2 -gline-tables-only"
 ac_add_options --enable-address-sanitizer-reporter
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7639,90 +7639,46 @@ nsresult PresShell::EventHandler::Handle
   // XXX Is this intentional?  In such case, the score is really good because
   //     of nothing to do.  So, it may make average and median better.
   if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
       !mPresShell->GetCurrentEventContent()) {
     RecordEventHandlingResponsePerformance(aEvent);
     return NS_OK;
   }
 
-  bool touchIsNew = false;
-  bool isHandlingUserInput = false;
-
   if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow()) {
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     if (fm) {
+      // This may run script now.  So, mPresShell might be destroyed after here.
       fm->FlushBeforeEventHandlingIfNeeded(mPresShell->mCurrentEventContent);
     }
   }
 
-  // XXX How about IME events and input events for plugins?
-  if (aEvent->IsTrusted()) {
-    if (aEvent->IsUserAction()) {
-      mPresShell->mHasHandledUserInput = true;
-    }
-
-    switch (aEvent->mMessage) {
-      case eKeyPress:
-      case eKeyDown:
-      case eKeyUp: {
-        WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
-        MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
-        // Not all keyboard events are treated as user input, so that popups
-        // can't be opened, fullscreen mode can't be started, etc at unexpected
-        // time.
-        isHandlingUserInput = keyboardEvent->CanTreatAsUserInput();
-        break;
-      }
-      case eMouseDown:
-      case eMouseUp:
-      case ePointerDown:
-      case ePointerUp:
-        isHandlingUserInput = true;
-        break;
-
-      case eMouseMove:
-        nsIPresShell::AllowMouseCapture(
-            EventStateManager::GetActiveEventStateManager() == manager);
-        break;
-
-      case eDrop: {
-        nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
-        if (session) {
-          bool onlyChromeDrop = false;
-          session->GetOnlyChromeDrop(&onlyChromeDrop);
-          if (onlyChromeDrop) {
-            aEvent->mFlags.mOnlyChromeDispatch = true;
-          }
-        }
-        break;
-      }
-
-      default:
-        break;
-    }
-
-    RecordEventPreparationPerformance(aEvent);
-
-    if (!mPresShell->mTouchManager.PreHandleEvent(
-            aEvent, aEventStatus, touchIsNew, isHandlingUserInput,
-            mPresShell->mCurrentEventContent)) {
-      return NS_OK;
-    }
-  }
+  bool isHandlingUserInput = PrepareToDispatchEvent(aEvent);
 
   // If we cannot open context menu even though eContextMenu is fired, we
   // should stop dispatching it into the DOM.
-  // XXX Can it be untrusted eContextMenu event here?  If not, we can do
-  //     this in the above block's switch statement.
   if (aEvent->mMessage == eContextMenu &&
       !PrepareToDispatchContextMenuEvent(aEvent)) {
     return NS_OK;
   }
 
+  // We finished preparing to dispatch the event.  So, let's record the
+  // performance.
+  RecordEventPreparationPerformance(aEvent);
+
+  // XXX Why don't we measure the performance of TouchManager::PreHandleEvent()
+  //     with RecordEventPreparationPerformance()?
+  bool touchIsNew = false;
+  if (!mPresShell->mTouchManager.PreHandleEvent(
+          aEvent, aEventStatus, touchIsNew, isHandlingUserInput,
+          mPresShell->mCurrentEventContent)) {
+    return NS_OK;
+  }
+
   AutoHandlingUserInputStatePusher userInpStatePusher(isHandlingUserInput,
                                                       aEvent, GetDocument());
 
   nsAutoPopupStatePusher popupStatePusher(
       PopupBlocker::GetEventPopupControlState(aEvent));
 
   // FIXME. If the event was reused, we need to clear the old target,
   // bug 329430
@@ -7844,21 +7800,74 @@ nsresult PresShell::EventHandler::Handle
     }
     default:
       break;
   }
   RecordEventHandlingResponsePerformance(aEvent);
   return rv;
 }
 
+bool PresShell::EventHandler::PrepareToDispatchEvent(WidgetEvent* aEvent) {
+  if (!aEvent->IsTrusted()) {
+    return false;
+  }
+
+  if (aEvent->IsUserAction()) {
+    mPresShell->mHasHandledUserInput = true;
+  }
+
+  switch (aEvent->mMessage) {
+    case eKeyPress:
+    case eKeyDown:
+    case eKeyUp: {
+      WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
+      MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
+      // Not all keyboard events are treated as user input, so that popups
+      // can't be opened, fullscreen mode can't be started, etc at unexpected
+      // time.
+      return keyboardEvent->CanTreatAsUserInput();
+    }
+    case eMouseDown:
+    case eMouseUp:
+    case ePointerDown:
+    case ePointerUp:
+      return true;
+
+    case eMouseMove: {
+      bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
+                          GetPresContext() &&
+                          GetPresContext()->EventStateManager() ==
+                              EventStateManager::GetActiveEventStateManager();
+      nsIPresShell::AllowMouseCapture(allowCapture);
+      return false;
+    }
+    case eDrop: {
+      nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
+      if (session) {
+        bool onlyChromeDrop = false;
+        session->GetOnlyChromeDrop(&onlyChromeDrop);
+        if (onlyChromeDrop) {
+          aEvent->mFlags.mOnlyChromeDispatch = true;
+        }
+      }
+      return false;
+    }
+
+    default:
+      return false;
+  }
+}
+
 bool PresShell::EventHandler::PrepareToDispatchContextMenuEvent(
     WidgetEvent* aEvent) {
   MOZ_ASSERT(aEvent);
   MOZ_ASSERT(aEvent->mMessage == eContextMenu);
 
+  // XXX Why do we treat untrusted eContextMenu here?
+
   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
   if (mouseEvent->IsContextMenuKeyEvent() &&
       !AdjustContextMenuKeyEvent(mouseEvent)) {
     return false;
   }
 
   // If "Shift" state is active, context menu should be forcibly opened even
   // if web apps want to prevent it since we respect our users' intention.
@@ -7923,16 +7932,20 @@ void PresShell::EventHandler::MaybeHandl
     }
   }
 }
 
 void PresShell::EventHandler::RecordEventPreparationPerformance(
     const WidgetEvent* aEvent) {
   MOZ_ASSERT(aEvent);
 
+  if (!aEvent->IsTrusted()) {
+    return;
+  }
+
   switch (aEvent->mMessage) {
     case eKeyPress:
     case eKeyDown:
     case eKeyUp:
       if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
         GetPresContext()->RecordInteractionTime(
             nsPresContext::InteractionType::eKeyInteraction,
             aEvent->mTimeStamp);
@@ -7981,16 +7994,19 @@ void PresShell::EventHandler::RecordEven
 
     default:
       return;
   }
 }
 
 void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
     const WidgetEvent* aEvent) {
+  // XXX Why we include the peformance of untrusted events only here?
+  //     We don't include it at recoding the preparation performance.
+
   if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
       aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
       !aEvent->AsInputEvent()) {
     return;
   }
 
   TimeStamp now = TimeStamp::Now();
   double millis = (now - aEvent->mTimeStamp).ToMilliseconds();
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -1065,32 +1065,43 @@ class PresShell final : public nsIPresSh
      private:
       const EventHandler& mEventHandler;
       const WidgetEvent* mEvent;
       TimeStamp mHandlingStartTime;
     };
 
     /**
      * RecordEventPreparationPerformance() records event preparation performance
-     * with telemetry.
+     * with telemetry only when aEvent is a trusted event.
      *
      * @param aEvent            The handling event which we've finished
      *                          preparing something to dispatch.
      */
     void RecordEventPreparationPerformance(const WidgetEvent* aEvent);
 
     /**
      * RecordEventHandlingResponsePerformance() records event handling response
      * performance with telemetry.
      *
      * @param aEvent            The handled event.
      */
     void RecordEventHandlingResponsePerformance(const WidgetEvent* aEvent);
 
     /**
+     * PrepareToDispatchEvent() prepares to dispatch aEvent.
+     *
+     * @param aEvent            The handling event.
+     * @return                  true if the event is user interaction.  I.e.,
+     *                          enough obvious input to allow to open popup,
+     *                          etc.  false, otherwise.
+     */
+    MOZ_CAN_RUN_SCRIPT
+    bool PrepareToDispatchEvent(WidgetEvent* aEvent);
+
+    /**
      * MaybeHandleKeyboardEventBeforeDispatch() may handle aKeyboardEvent
      * if it should do something before dispatched into the DOM.
      *
      * @param aKeyboardEvent    The handling keyboard event.
      */
     MOZ_CAN_RUN_SCRIPT
     void MaybeHandleKeyboardEventBeforeDispatch(
         WidgetKeyboardEvent* aKeyboardEvent);
--- a/layout/base/TouchManager.cpp
+++ b/layout/base/TouchManager.cpp
@@ -217,16 +217,20 @@ nsIFrame* TouchManager::SuppressInvalidP
     }
   }
   return frame;
 }
 
 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
                                   bool& aTouchIsNew, bool& aIsHandlingUserInput,
                                   nsCOMPtr<nsIContent>& aCurrentEventContent) {
+  if (!aEvent->IsTrusted()) {
+    return true;
+  }
+
   switch (aEvent->mMessage) {
     case eTouchStart: {
       aIsHandlingUserInput = true;
       WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
       // if there is only one touch in this touchstart event, assume that it is
       // the start of a new touch session and evict any old touches in the
       // queue
       if (touchEvent->mTouches.Length() == 1) {
--- a/mobile/android/config/mozconfigs/android-api-16-gradle-dependencies/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16-gradle-dependencies/nightly
@@ -43,13 +43,14 @@ export MOZ_ANDROID_POCKET=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
 
 # End ../android-api-16-frontend/nightly.
 
 # Disable Keyfile Loading (and checks) since dependency fetching doesn't need these keys.
 # This overrides the settings in the common android mozconfig
 ac_add_options --without-mozilla-api-keyfile
-ac_add_options --without-google-api-keyfile
+ac_add_options --without-google-location-service-api-keyfile
+ac_add_options --without-google-safebrowsing-api-keyfile
 # We need dummy Keyfiles in order to enable features we care about.
 ac_add_options --with-adjust-sdk-keyfile="$topsrcdir/mobile/android/base/adjust-sdk-sandbox.token"
 ac_add_options --with-leanplum-sdk-keyfile="$topsrcdir/mobile/android/base/leanplum-sdk-sandbox.token"
 ac_add_options --with-pocket-api-keyfile="$topsrcdir/mobile/android/base/pocket-api-sandbox.token"
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -31,17 +31,18 @@ if [ -z "$NO_NDK" -a -z "$USE_ARTIFACT" 
     CXX="$topsrcdir/clang/bin/clang++"
     ac_add_options --with-android-ndk="$topsrcdir/android-ndk"
     # Enable static analysis plugin
     export ENABLE_CLANG_PLUGIN=1
 fi
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 
-ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-google-safebrowsing-api-keyfile=/builds/sb-gapi.data
+ac_add_options --with-google-location-service-api-keyfile=/builds/gls-gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-fennec-geoloc-api.key
 
 ac_add_options --enable-marionette
 
 # MOZ_INSTALL_TRACKING does not guarantee MOZ_UPDATE_CHANNEL will be set so we
 # provide a default state. Currently, the default state provides a default
 # keyfile because an assertion will be thrown if MOZ_INSTALL_TRACKING is
 # specified but a keyfile is not. This assertion can catch if we misconfigure a
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5612,44 +5612,44 @@ pref("browser.safebrowsing.id", "navclie
 #else
 pref("browser.safebrowsing.id", "Firefox");
 #endif
 
 // Download protection
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 15000);
-pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_SAFEBROWSING_API_KEY%");
 pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
 pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
 pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
 pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
 
 // Google Safe Browsing provider (legacy)
 pref("browser.safebrowsing.provider.google.pver", "2.2");
 pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,googpub-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
-pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_SAFEBROWSING_API_KEY%");
 pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
 pref("browser.safebrowsing.provider.google.advisoryName", "Google Safe Browsing");
 
 // Google Safe Browsing provider
 pref("browser.safebrowsing.provider.google4.pver", "4");
 pref("browser.safebrowsing.provider.google4.lists", "goog-badbinurl-proto,goog-downloadwhite-proto,goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto,goog-harmful-proto,goog-passwordwhite-proto");
-pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
-pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
+pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
+pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
 pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.provider.google4.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
 pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing");
-pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
+pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
 pref("browser.safebrowsing.provider.google4.dataSharing.enabled", false);
 
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 
 // Mozilla Safe Browsing provider (for tracking protection and plugin blocking)
 pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
 pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboyannoyance-ads-digest256,fanboysocial-ads-digest256,easylist-ads-digest256,easyprivacy-ads-digest256,adguard-ads-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
--- a/python/l10n/fluent_migrations/bug_1507595_aboutsupport.py
+++ b/python/l10n/fluent_migrations/bug_1507595_aboutsupport.py
@@ -54,17 +54,18 @@ app-basics-user-agent = { COPY("toolkit/
 app-basics-os = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsOS")}
 app-basics-memory-use = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsMemoryUse")}
 app-basics-performance = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsPerformance")}
 app-basics-service-workers = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsServiceWorkers")}
 app-basics-profiles = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsProfiles")}
 app-basics-multi-process-support = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsMultiProcessSupport")}
 app-basics-process-count = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsProcessCount")}
 app-basics-enterprise-policies = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.enterprisePolicies")}
-app-basics-key-google = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsKeyGoogle")}
+app-basics-location-service-key-google = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsLocationServiceKeyGoogle")}
+app-basics-safebrowsing-key-google = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsSafebrowsingKeyGoogle")}
 app-basics-key-mozilla = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsKeyMozilla")}
 app-basics-safe-mode = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.appBasicsSafeMode")}
 modified-key-prefs-title = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.modifiedKeyPrefsTitle")}
 modified-prefs-name = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.modifiedPrefsName")}
 modified-prefs-value = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.modifiedPrefsValue")}
 user-js-title = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.userJSTitle")}
 locked-key-prefs-title = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.lockedKeyPrefsTitle")}
 locked-prefs-name = { COPY("toolkit/chrome/global/aboutSupport.dtd", "aboutSupport.lockedPrefsName")}
--- a/remote/JSONHandler.jsm
+++ b/remote/JSONHandler.jsm
@@ -1,32 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["JSONHandler"];
 
-const {HTTP_404} = ChromeUtils.import("chrome://remote/content/server/HTTPD.jsm");
+const {HTTP_404, HTTP_505} = ChromeUtils.import("chrome://remote/content/server/HTTPD.jsm");
 const {Log} = ChromeUtils.import("chrome://remote/content/Log.jsm");
 const {Protocol} = ChromeUtils.import("chrome://remote/content/Protocol.jsm");
+const {RemoteAgentError} = ChromeUtils.import("chrome://remote/content/Error.jsm");
 
 class JSONHandler {
   constructor(agent) {
     this.agent = agent;
     this.routes = {
       "/json/version": this.getVersion.bind(this),
       "/json/protocol": this.getProtocol.bind(this),
       "/json/list": this.getTargetList.bind(this),
     };
   }
 
   getVersion() {
-    return {};
+    const mainProcessTarget = this.agent.targets.getMainProcessTarget();
+    return {
+      "Browser": "Firefox",
+      "Protocol-Version": "1.0",
+      "User-Agent": "Mozilla",
+      "V8-Version": "1.0",
+      "WebKit-Version": "1.0",
+      "webSocketDebuggerUrl": mainProcessTarget.toJSON().webSocketDebuggerUrl,
+    };
   }
 
   getProtocol() {
     return Protocol.Description;
   }
 
   getTargetList() {
     return [...this.agent.targets];
@@ -38,22 +47,27 @@ class JSONHandler {
     if (request.method != "GET") {
       throw HTTP_404;
     }
 
     if (!(request.path in this.routes)) {
       throw HTTP_404;
     }
 
-    const body = this.routes[request.path]();
-    const payload = JSON.stringify(body, sanitise, Log.verbose ? "\t" : undefined);
+    try {
+      const body = this.routes[request.path]();
+      const payload = JSON.stringify(body, sanitise, Log.verbose ? "\t" : undefined);
 
-    response.setStatusLine(request.httpVersion, 200, "OK");
-    response.setHeader("Content-Type", "application/json");
-    response.write(payload);
+      response.setStatusLine(request.httpVersion, 200, "OK");
+      response.setHeader("Content-Type", "application/json");
+      response.write(payload);
+    } catch (e) {
+      new RemoteAgentError(e).notify();
+      throw HTTP_505;
+    }
   }
 
   // XPCOM
 
   get QueryInterface() {
     return ChromeUtils.generateQI([Ci.nsIHttpRequestHandler]);
   }
 }
--- a/remote/RemoteAgent.js
+++ b/remote/RemoteAgent.js
@@ -12,17 +12,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   HttpServer: "chrome://remote/content/server/HTTPD.jsm",
   JSONHandler: "chrome://remote/content/JSONHandler.jsm",
   Log: "chrome://remote/content/Log.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   Observer: "chrome://remote/content/Observer.jsm",
   Preferences: "resource://gre/modules/Preferences.jsm",
   RecommendedPreferences: "chrome://remote/content/RecommendedPreferences.jsm",
   TabObserver: "chrome://remote/content/WindowManager.jsm",
-  Targets: "chrome://remote/content/Targets.jsm",
+  Targets: "chrome://remote/content/targets/Targets.jsm",
 });
 XPCOMUtils.defineLazyGetter(this, "log", Log.get);
 
 const ENABLED = "remote.enabled";
 const FORCE_LOCAL = "remote.force-local";
 
 const DEFAULT_HOST = "localhost";
 const DEFAULT_PORT = 9222;
@@ -174,17 +174,18 @@ class ParentRemoteAgent {
       this.listen(addr);
     } catch (e) {
       this.close();
       throw new FatalError(`Unable to start remote agent on ${addr.spec}: ${e.message}`, e);
      }
   }
 
   get helpInfo() {
-    return "  --remote-debugger [<host>][:<port>] Start the Firefox remote agent, which is \n" +
+    return "  --remote-debugger [<host>][:<port>]\n" +
+           "  --remote-debugging-port <port> Start the Firefox remote agent, which is \n" +
            "                     a low-level debugging interface based on the CDP protocol.\n" +
            "                     Defaults to listen on localhost:9222.\n";
   }
 
   // XPCOM
 
   get QueryInterface() {
     return ChromeUtils.generateQI([Ci.nsICommandLineHandler]);
copy from remote/domains/Domain.jsm
copy to remote/domains/ContentProcessDomain.jsm
--- a/remote/domains/Domain.jsm
+++ b/remote/domains/ContentProcessDomain.jsm
@@ -1,66 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["Domain"];
-
-class Domain {
-  constructor(session, target) {
-    this.session = session;
-    this.target = target;
-    this.name = this.constructor.name;
-
-    this.eventListeners_ = new Set();
-  }
-
-  destructor() {}
+var EXPORTED_SYMBOLS = ["ContentProcessDomain"];
 
-  emit(eventName, params = {}) {
-    for (const listener of this.eventListeners_) {
-      try {
-        if (isEventHandler(listener)) {
-          listener.onEvent(eventName, params);
-        } else {
-          listener.call(this, eventName, params);
-        }
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    }
-  }
+const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
 
-  addEventListener(listener) {
-    if (typeof listener != "function" && !isEventHandler(listener)) {
-      throw new TypeError();
-    }
-    this.eventListeners_.add(listener);
-  }
-
+class ContentProcessDomain extends Domain {
   // helpers
 
   get content() {
     return this.session.content;
   }
 
   get docShell() {
     return this.session.docShell;
   }
 
   get chromeEventHandler() {
     return this.docShell.chromeEventHandler;
   }
-
-  // static
-
-  static implements(methodName) {
-    return typeof this.prototype[methodName] == "function";
-  }
 }
-
-function isEventHandler(listener) {
-  return listener &&
-      "onEvent" in listener &&
-      typeof listener.onEvent == "function";
-}
--- a/remote/domains/Domain.jsm
+++ b/remote/domains/Domain.jsm
@@ -2,19 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Domain"];
 
 class Domain {
-  constructor(session, target) {
+  constructor(session) {
     this.session = session;
-    this.target = target;
     this.name = this.constructor.name;
 
     this.eventListeners_ = new Set();
   }
 
   destructor() {}
 
   emit(eventName, params = {}) {
@@ -33,30 +32,16 @@ class Domain {
 
   addEventListener(listener) {
     if (typeof listener != "function" && !isEventHandler(listener)) {
       throw new TypeError();
     }
     this.eventListeners_.add(listener);
   }
 
-  // helpers
-
-  get content() {
-    return this.session.content;
-  }
-
-  get docShell() {
-    return this.session.docShell;
-  }
-
-  get chromeEventHandler() {
-    return this.docShell.chromeEventHandler;
-  }
-
   // static
 
   static implements(methodName) {
     return typeof this.prototype[methodName] == "function";
   }
 }
 
 function isEventHandler(listener) {
--- a/remote/domains/Domains.jsm
+++ b/remote/domains/Domains.jsm
@@ -53,29 +53,36 @@ class Domains {
       const Cls = this.modules[name];
       if (!Cls) {
         throw new UnknownMethodError(name);
       }
       if (!isConstructor(Cls)) {
         throw new TypeError("Domain cannot be constructed");
       }
 
-      inst = new Cls(this.session, this.session.target);
+      inst = new Cls(this.session);
       if (!(inst instanceof Domain)) {
         throw new TypeError("Instance not a domain");
       }
 
       inst.addEventListener(this.session);
 
       this.instances.set(name, inst);
     }
 
     return inst;
   }
 
+  /**
+   * Tells if a Domain of the given name is available
+   */
+  has(name) {
+    return name in this.modules;
+  }
+
   get size() {
     return this.instances.size;
   }
 
   /** Calls destructor on each domain and clears the cache. */
   clear() {
     for (const inst of this.instances.values()) {
       inst.destructor();
--- a/remote/domains/ParentProcessDomains.jsm
+++ b/remote/domains/ParentProcessDomains.jsm
@@ -7,9 +7,10 @@
 var EXPORTED_SYMBOLS = ["ParentProcessDomains"];
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const ParentProcessDomains = {};
 
 XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
   Browser: "chrome://remote/content/domains/parent/Browser.jsm",
+  Target: "chrome://remote/content/domains/parent/Target.jsm",
 });
--- a/remote/domains/content/Log.jsm
+++ b/remote/domains/content/Log.jsm
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Log"];
 
-const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
+const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-class Log extends Domain {
-  constructor(session, target) {
-    super(session, target);
+class Log extends ContentProcessDomain {
+  constructor(session) {
+    super(session);
     this.enabled = false;
   }
 
   destructor() {
     this.disable();
   }
 
   enable() {
--- a/remote/domains/content/Page.jsm
+++ b/remote/domains/content/Page.jsm
@@ -1,23 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Page"];
 
-const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
+const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {UnsupportedError} = ChromeUtils.import("chrome://remote/content/Error.jsm");
 
-class Page extends Domain {
-  constructor(session, target) {
-    super(session, target);
+class Page extends ContentProcessDomain {
+  constructor(session) {
+    super(session);
     this.enabled = false;
   }
 
   destructor() {
     this.disable();
   }
 
   QueryInterface(iid) {
--- a/remote/domains/parent/Browser.jsm
+++ b/remote/domains/parent/Browser.jsm
@@ -1,21 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Browser"];
 
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
 
 class Browser extends Domain {
   getVersion() {
     return {
       protocolVersion: "1",
       product: "Firefox",
       revision: "1",
       userAgent: "Firefox",
       jsVersion: "1.8.5",
     };
   }
+
+  close() {
+    Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+  }
 }
copy from remote/domains/ParentProcessDomains.jsm
copy to remote/domains/parent/Target.jsm
--- a/remote/domains/ParentProcessDomains.jsm
+++ b/remote/domains/parent/Target.jsm
@@ -1,15 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["ParentProcessDomains"];
+var EXPORTED_SYMBOLS = ["Target"];
 
-const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
 
-const ParentProcessDomains = {};
+class Target extends Domain {
+  getBrowserContexts() {
+    return {
+      browserContextIds: [],
+    };
+  }
 
-XPCOMUtils.defineLazyModuleGetters(ParentProcessDomains, {
-  Browser: "chrome://remote/content/domains/parent/Browser.jsm",
-});
+  setDiscoverTargets({ discover }) {
+  }
+}
--- a/remote/jar.mn
+++ b/remote/jar.mn
@@ -1,39 +1,45 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 remote.jar:
 % content remote %content/
   content/Connection.jsm (Connection.jsm)
-  content/ContentProcessSession.jsm (ContentProcessSession.jsm)
   content/Error.jsm (Error.jsm)
   content/JSONHandler.jsm (JSONHandler.jsm)
   content/Log.jsm (Log.jsm)
   content/Observer.jsm (Observer.jsm)
   content/Protocol.jsm (Protocol.jsm)
   content/RecommendedPreferences.jsm (RecommendedPreferences.jsm)
-  content/Session.jsm (Session.jsm)
   content/Sync.jsm (Sync.jsm)
-  content/Target.jsm (Target.jsm)
-  content/Targets.jsm (Targets.jsm)
   content/WindowManager.jsm (WindowManager.jsm)
 
-  # Frame scripts
-  content/frame-script.js (frame-script.js)
+  # sessions
+  content/sessions/frame-script.js (sessions/frame-script.js)
+  content/sessions/ContentProcessSession.jsm (sessions/ContentProcessSession.jsm)
+  content/sessions/Session.jsm (sessions/Session.jsm)
+  content/sessions/TabSession.jsm (sessions/TabSession.jsm)
+
+  # targets
+  content/targets/MainProcessTarget.jsm (targets/MainProcessTarget.jsm)
+  content/targets/TabTarget.jsm (targets/TabTarget.jsm)
+  content/targets/Targets.jsm (targets/Targets.jsm)
 
   # domains
   content/domains/Domain.jsm (domains/Domain.jsm)
   content/domains/Domains.jsm (domains/Domains.jsm)
+  content/domains/ContentProcessDomain.jsm (domains/ContentProcessDomain.jsm)
   content/domains/ContentProcessDomains.jsm (domains/ContentProcessDomains.jsm)
   content/domains/ParentProcessDomains.jsm (domains/ParentProcessDomains.jsm)
   content/domains/content/Log.jsm (domains/content/Log.jsm)
   content/domains/content/Page.jsm (domains/content/Page.jsm)
   content/domains/parent/Browser.jsm (domains/parent/Browser.jsm)
+  content/domains/parent/Target.jsm (domains/parent/Target.jsm)
 
   # transport layer
   content/server/HTTPD.jsm (../netwerk/test/httpserver/httpd.js)
   content/server/Packet.jsm (server/Packet.jsm)
   content/server/Socket.jsm (server/Socket.jsm)
   content/server/Stream.jsm (server/Stream.jsm)
   content/server/Transport.jsm (server/Transport.jsm)
   content/server/WebSocket.jsm (server/WebSocket.jsm)
rename from remote/ContentProcessSession.jsm
rename to remote/sessions/ContentProcessSession.jsm
--- a/remote/ContentProcessSession.jsm
+++ b/remote/sessions/ContentProcessSession.jsm
@@ -31,17 +31,17 @@ class ContentProcessSession {
   }
 
   // Domain event listener
 
   onEvent(eventName, params) {
     this.messageManager.sendAsyncMessage("remote:event", {
       browsingContextId: this.browsingContext.id,
       event: {
-        method: eventName,
+        eventName,
         params,
       },
     });
   }
 
   // nsIMessageListener
 
   async receiveMessage({name, data}) {
rename from remote/Session.jsm
rename to remote/sessions/Session.jsm
--- a/remote/Session.jsm
+++ b/remote/sessions/Session.jsm
@@ -14,110 +14,79 @@ const {formatError} = ChromeUtils.import
  * A session represents exactly one client WebSocket connection.
  *
  * Every new WebSocket connections is associated with one session that
  * deals with despatching incoming command requests to the right
  * target, sending back responses, and propagating events originating
  * from domains.
  */
 class Session {
-  constructor(connection, target) {
+  constructor(connection, target, id) {
     this.connection = connection;
     this.target = target;
+    this.id = id;
 
-    this.connection.onmessage = this.dispatch.bind(this);
+    this.destructor = this.destructor.bind(this);
+
+    this.connection.onmessage = this.onMessage.bind(this);
+    this.connection.transport.on("close", this.destructor);
 
     this.domains = new Domains(this, ParentProcessDomains);
-    this.mm.addMessageListener("remote:event", this);
-    this.mm.addMessageListener("remote:result", this);
-    this.mm.addMessageListener("remote:error", this);
-
-    this.mm.loadFrameScript("chrome://remote/content/frame-script.js", false);
   }
 
   destructor() {
     this.domains.clear();
-    this.connection.onmessage = null;
-
-    this.mm.sendAsyncMessage("remote:destroy", {
-      browsingContextId: this.browsingContext.id,
-    });
-
-    this.mm.removeMessageListener("remote:event", this);
-    this.mm.removeMessageListener("remote:result", this);
-    this.mm.removeMessageListener("remote:error", this);
   }
 
-  async dispatch({id, method, params}) {
+  async onMessage({id, method, params}) {
     try {
       if (typeof id == "undefined") {
         throw new TypeError("Message missing 'id' field");
       }
       if (typeof method == "undefined") {
         throw new TypeError("Message missing 'method' field");
       }
 
       const [domainName, methodName] = Domains.splitMethod(method);
-      if (this.domains.domainSupportsMethod(domainName, methodName)) {
-        await this.executeInParent(id, domainName, methodName, params);
-      } else {
-        this.executeInChild(id, domainName, methodName, params);
-      }
+      await this.execute(id, domainName, methodName, params);
     } catch (e) {
-      const error = formatError(e, {stack: true});
-      this.connection.send({id, error: { message: error }});
+      this.onError(id, e);
     }
   }
 
-  async executeInParent(id, domain, method, params) {
+  async execute(id, domain, method, params) {
     const inst = this.domains.get(domain);
     const result = await inst[method](params);
-    this.connection.send({id, result});
+    this.onResult(id, result);
   }
 
-  executeInChild(id, domain, method, params) {
-    this.mm.sendAsyncMessage("remote:request", {
-      browsingContextId: this.browsingContext.id,
-      request: {id, domain, method, params},
+  onResult(id, result) {
+    this.connection.send({
+      id,
+      sessionId: this.id,
+      result,
     });
   }
 
-  get mm() {
-    return this.target.mm;
-  }
-
-  get browsingContext() {
-    return this.target.browsingContext;
+  onError(id, error) {
+    this.connection.send({
+      id,
+      sessionId: this.id,
+      error: {
+        message: formatError(error, {stack: true}),
+      },
+    });
   }
 
   // Domain event listener
 
   onEvent(eventName, params) {
     this.connection.send({
+      sessionId: this.id,
       method: eventName,
       params,
     });
   }
 
-  // nsIMessageListener
-
-  receiveMessage({name, data}) {
-    const {id, result, event, error} = data;
-
-    switch (name) {
-    case "remote:result":
-      this.connection.send({id, result});
-      break;
-
-    case "remote:event":
-      this.connection.send(event);
-      break;
-
-    case "remote:error":
-      this.connection.send({id, error: { message: formatError(error, {stack: true}) }});
-      break;
-    }
-  }
-
   toString() {
-    return `[object Session ${this.connection.id}]`;
+    return `[object ${this.constructor.name} ${this.connection.id}]`;
   }
 }
copy from remote/Session.jsm
copy to remote/sessions/TabSession.jsm
--- a/remote/Session.jsm
+++ b/remote/sessions/TabSession.jsm
@@ -1,123 +1,93 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["Session"];
+var EXPORTED_SYMBOLS = ["TabSession"];
 
-const {ParentProcessDomains} = ChromeUtils.import("chrome://remote/content/domains/ParentProcessDomains.jsm");
 const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm");
-const {formatError} = ChromeUtils.import("chrome://remote/content/Error.jsm");
+const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm");
 
-/**
- * A session represents exactly one client WebSocket connection.
- *
- * Every new WebSocket connections is associated with one session that
- * deals with despatching incoming command requests to the right
- * target, sending back responses, and propagating events originating
- * from domains.
- */
-class Session {
-  constructor(connection, target) {
-    this.connection = connection;
-    this.target = target;
+class TabSession extends Session {
+  constructor(connection, target, id) {
+    super(connection, target, id);
 
-    this.connection.onmessage = this.dispatch.bind(this);
-
-    this.domains = new Domains(this, ParentProcessDomains);
     this.mm.addMessageListener("remote:event", this);
     this.mm.addMessageListener("remote:result", this);
     this.mm.addMessageListener("remote:error", this);
 
-    this.mm.loadFrameScript("chrome://remote/content/frame-script.js", false);
+    this.mm.loadFrameScript("chrome://remote/content/sessions/frame-script.js", false);
   }
 
   destructor() {
-    this.domains.clear();
-    this.connection.onmessage = null;
+    super.destructor();
 
     this.mm.sendAsyncMessage("remote:destroy", {
       browsingContextId: this.browsingContext.id,
     });
 
     this.mm.removeMessageListener("remote:event", this);
     this.mm.removeMessageListener("remote:result", this);
     this.mm.removeMessageListener("remote:error", this);
   }
 
-  async dispatch({id, method, params}) {
+  async onMessage({id, method, params}) {
     try {
       if (typeof id == "undefined") {
         throw new TypeError("Message missing 'id' field");
       }
       if (typeof method == "undefined") {
         throw new TypeError("Message missing 'method' field");
       }
 
       const [domainName, methodName] = Domains.splitMethod(method);
-      if (this.domains.domainSupportsMethod(domainName, methodName)) {
-        await this.executeInParent(id, domainName, methodName, params);
+      if (typeof domainName == "undefined" || typeof methodName == "undefined") {
+        throw new TypeError("'method' field is incorrect and doesn't define a domain " +
+                            "name and method separated by a dot.");
+      }
+      if (this.domains.has(domainName)) {
+        await this.execute(id, domainName, methodName, params);
       } else {
         this.executeInChild(id, domainName, methodName, params);
       }
     } catch (e) {
-      const error = formatError(e, {stack: true});
-      this.connection.send({id, error: { message: error }});
+      this.onError(id, e);
     }
   }
 
-  async executeInParent(id, domain, method, params) {
-    const inst = this.domains.get(domain);
-    const result = await inst[method](params);
-    this.connection.send({id, result});
-  }
-
   executeInChild(id, domain, method, params) {
     this.mm.sendAsyncMessage("remote:request", {
       browsingContextId: this.browsingContext.id,
       request: {id, domain, method, params},
     });
   }
 
   get mm() {
     return this.target.mm;
   }
 
   get browsingContext() {
     return this.target.browsingContext;
   }
 
-  // Domain event listener
-
-  onEvent(eventName, params) {
-    this.connection.send({
-      method: eventName,
-      params,
-    });
-  }
-
   // nsIMessageListener
 
   receiveMessage({name, data}) {
     const {id, result, event, error} = data;
 
     switch (name) {
     case "remote:result":
-      this.connection.send({id, result});
+      this.onResult(id, result);
       break;
 
     case "remote:event":
-      this.connection.send(event);
+      this.onEvent(event.eventName, event.params);
       break;
 
     case "remote:error":
-      this.connection.send({id, error: { message: formatError(error, {stack: true}) }});
+      this.onError(id, error);
       break;
     }
   }
-
-  toString() {
-    return `[object Session ${this.connection.id}]`;
-  }
 }
rename from remote/frame-script.js
rename to remote/sessions/frame-script.js
--- a/remote/frame-script.js
+++ b/remote/sessions/frame-script.js
@@ -1,10 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {ContentProcessSession} = ChromeUtils.import("chrome://remote/content/ContentProcessSession.jsm");
+const {ContentProcessSession} =
+  ChromeUtils.import("chrome://remote/content/sessions/ContentProcessSession.jsm");
 
 /* global content, docShell */
 new ContentProcessSession(this, docShell.browsingContext, content, docShell);
new file mode 100644
--- /dev/null
+++ b/remote/targets/MainProcessTarget.jsm
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["MainProcessTarget"];
+
+const {Connection} = ChromeUtils.import("chrome://remote/content/Connection.jsm");
+const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm");
+const {WebSocketDebuggerTransport} = ChromeUtils.import("chrome://remote/content/server/WebSocketTransport.jsm");
+const {WebSocketServer} = ChromeUtils.import("chrome://remote/content/server/WebSocket.jsm");
+
+/**
+ * The main process Target.
+ *
+ * Matches BrowserDevToolsAgentHost from chromium, and only support a couple of Domains:
+ * https://cs.chromium.org/chromium/src/content/browser/devtools/browser_devtools_agent_host.cc?dr=CSs&g=0&l=80-91
+ */
+class MainProcessTarget {
+  /**
+   * @param Targets targets
+   */
+  constructor(targets) {
+    this.targets = targets;
+    this.sessions = new Map();
+
+    this.type = "main-process";
+  }
+
+  get wsDebuggerURL() {
+    const RemoteAgent = Cc["@mozilla.org/remote/agent"]
+        .getService(Ci.nsISupports).wrappedJSObject;
+    const {host, port} = RemoteAgent;
+    return `ws://${host}:${port}/devtools/page/${this.id}`;
+  }
+
+  toString() {
+    return `[object MainProcessTarget]`;
+  }
+
+  toJSON() {
+    return {
+      description: "Main process target",
+      devtoolsFrontendUrl: "",
+      faviconUrl: "",
+      id: "main-process",
+      title: "Main process target",
+      type: this.type,
+      url: "",
+      webSocketDebuggerUrl: this.wsDebuggerURL,
+    };
+  }
+
+  // nsIHttpRequestHandler
+
+  async handle(request, response) {
+    const so = await WebSocketServer.upgrade(request, response);
+    const transport = new WebSocketDebuggerTransport(so);
+    const conn = new Connection(transport);
+    this.sessions.set(conn, new Session(conn, this));
+  }
+
+  // XPCOM
+
+  get QueryInterface() {
+    return ChromeUtils.generateQI([
+      Ci.nsIHttpRequestHandler,
+    ]);
+  }
+}
rename from remote/Target.jsm
rename to remote/targets/TabTarget.jsm
--- a/remote/Target.jsm
+++ b/remote/targets/TabTarget.jsm
@@ -1,34 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["Target"];
+var EXPORTED_SYMBOLS = ["TabTarget"];
 
 const {Connection} = ChromeUtils.import("chrome://remote/content/Connection.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const {Session} = ChromeUtils.import("chrome://remote/content/Session.jsm");
+const {TabSession} = ChromeUtils.import("chrome://remote/content/sessions/TabSession.jsm");
 const {WebSocketDebuggerTransport} = ChromeUtils.import("chrome://remote/content/server/WebSocketTransport.jsm");
 const {WebSocketServer} = ChromeUtils.import("chrome://remote/content/server/WebSocket.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
     "@mozilla.org/browser/favicon-service;1", "nsIFaviconService");
 
 /**
- * A debugging target.
- *
- * Targets can be a document (page), an OOP frame, a background
- * document, or a worker.  They can all run in dedicated process or frame.
+ * Target for a local tab or a remoted frame.
  */
-class Target {
-  constructor(browser) {
+class TabTarget {
+  /**
+   * @param Targets targets
+   * @param BrowserElement browser
+   */
+  constructor(targets, browser) {
+    this.targets = targets;
     this.browser = browser;
     this.sessions = new Map();
   }
 
   connect() {
     Services.obs.addObserver(this, "message-manager-disconnect");
   }
 
@@ -123,17 +125,17 @@ class Target {
   }
 
   // nsIHttpRequestHandler
 
   async handle(request, response) {
     const so = await WebSocketServer.upgrade(request, response);
     const transport = new WebSocketDebuggerTransport(so);
     const conn = new Connection(transport);
-    this.sessions.set(conn, new Session(conn, this));
+    this.sessions.set(conn, new TabSession(conn, this));
   }
 
   // nsIObserver
 
   observe(subject, topic, data) {
     if (subject === this.mm && subject == "message-manager-disconnect") {
       // disconnect debugging target if <browser> is disconnected,
       // otherwise this is just a host process change
rename from remote/Targets.jsm
rename to remote/targets/Targets.jsm
--- a/remote/Targets.jsm
+++ b/remote/targets/Targets.jsm
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["Targets"];
 
 const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
 const {MessagePromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm");
-const {Target} = ChromeUtils.import("chrome://remote/content/Target.jsm");
+const {TabTarget} = ChromeUtils.import("chrome://remote/content/targets/TabTarget.jsm");
+const {MainProcessTarget} = ChromeUtils.import("chrome://remote/content/targets/MainProcessTarget.jsm");
 
 class Targets {
   constructor() {
     // browser context ID -> Target<XULElement>
     this._targets = new Map();
 
     EventEmitter.decorate(this);
   }
@@ -23,17 +24,17 @@ class Targets {
     // The tab may just have been created and not fully initialized yet.
     // Target class expects BrowserElement.browsingContext to be defined
     // whereas it is asynchronously set by the custom element class.
     // At least ensure that this property is set before instantiating the target.
     if (!browser.browsingContext) {
       await new MessagePromise(browser.messageManager, "Browser:Init");
     }
 
-    const target = new Target(browser);
+    const target = new TabTarget(this, browser);
     target.connect();
     this._targets.set(target.id, target);
     this.emit("connect", target);
   }
 
   /** @param {BrowserElement} browser */
   disconnect(browser) {
     // Ignore the browsers that haven't had time to initialize.
@@ -48,22 +49,38 @@ class Targets {
       this._targets.delete(target.id);
     }
   }
 
   clear() {
     for (const target of this) {
       this.disconnect(target.browser);
     }
+    if (this.mainProcessTarget) {
+      this.mainProcessTarget.disconnect();
+      this.mainProcessTarget = null;
+    }
   }
 
   get size() {
     return this._targets.size;
   }
 
+  /**
+   * Get the Target instance for the main process.
+   * This target is a singleton and only exposes a subset of domains.
+   */
+  getMainProcessTarget() {
+    if (!this.mainProcessTarget) {
+      this.mainProcessTarget = new MainProcessTarget(this);
+      this.emit("connect", this.mainProcessTarget);
+    }
+    return this.mainProcessTarget;
+  }
+
   * [Symbol.iterator]() {
     for (const target of this._targets.values()) {
       yield target;
     }
   }
 
   toJSON() {
     return [...this];
new file mode 100644
--- /dev/null
+++ b/remote/test/puppeteer-demo.js
@@ -0,0 +1,20 @@
+/**
+ * To run the server:
+ * $ ./mach run --remote-debugging-port=9000 --headless
+ *   (this requires `ac_add_options --enable-cdp` to be set in your mozconfig)
+ *
+ * To run the test script:
+ * $ npm install puppeteer
+ * $ DEBUG="puppeteer:protocol" node puppeteer-demo.js
+ */
+
+/* global require */
+
+const puppeteer = require("puppeteer");
+
+console.log("Calling puppeteer.connect");
+puppeteer.connect({ browserURL: "http://localhost:9000"}).then(async browser => {
+  console.log("Connect success!");
+
+  return browser.close();
+});
--- a/remote/test/unit/test_Session.js
+++ b/remote/test/unit/test_Session.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const {Session} = ChromeUtils.import("chrome://remote/content/Session.jsm");
+const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm");
 
 const connection = {onmessage: () => {}};
 
 class MockTarget {
   constructor() {
   }
 
   get browsingContext() {
--- a/testing/mozbase/docs/mozproxy.rst
+++ b/testing/mozbase/docs/mozproxy.rst
@@ -24,24 +24,26 @@ Mozproxy provide a function that returns
      pb.stop()
 
 **config** is a dict with the following options:
 
 - **playback_tool**: name of the backend. can be "mitmproxy", "mitmproxy-android"
 - **playback_recordings**: list of recording files
 - **playback_binary_manifest**: tooltool manifests for the proxy backend binary
 - **playback_pageset_manifest**: tooltool manifest for the pagesets archive
+- **playback_artifacts**: links to downloadable artifacts
 - **binary**: path of the browser binary
 - **obj_path**: build dir
 - **platform**: platform name (provided by mozinfo.os)
 - **run_local**: if True, the test is running locally.
 - **custom_script**: name of the mitm custom script (-s option)
 - **app**: tested app. Can be "firefox",  "geckoview", "refbrow", "fenix" or  "firefox"
 - **host**: hostname for the policies.json file
 - **local_profile_dir**: profile dir
 
 
 Supported environment variables:
 
+- **MOZPROXY_DIR**: directory used by mozproxy for all data files, set by mozproxy
 - **MOZ_UPLOAD_DIR**: upload directory path
 - **GECKO_HEAD_REPOSITORY**: used to find the certutils binary path from the CI
 - **GECKO_HEAD_REV**: used to find the certutils binary path frmo the CI
 - **HOSTUTILS_MANIFEST_PATH**: used to find the certutils binary path from the CI
--- a/testing/mozbase/mozproxy/mozproxy/backends/mitm.py
+++ b/testing/mozbase/mozproxy/mozproxy/backends/mitm.py
@@ -91,17 +91,19 @@ class Mitmproxy(Playback):
             )
 
         self.mozproxy_dir = os.path.join(self.mozproxy_dir, "testing", "mozproxy")
         self.recordings_path = self.mozproxy_dir
         LOG.info(
             "mozproxy_dir used for mitmproxy downloads and exe files: %s"
             % self.mozproxy_dir
         )
-
+        # setting up the MOZPROXY_DIR env variable so custom scripts know
+        # where to get the data
+        os.environ["MOZPROXY_DIR"] = self.mozproxy_dir
         # go ahead and download and setup mitmproxy
         self.download()
         # mitmproxy must be started before setup, so that the CA cert is available
         self.start()
 
         # In case the setup fails, we want to stop the process before raising.
         try:
             self.setup()
@@ -116,23 +118,34 @@ class Mitmproxy(Playback):
 
         LOG.info("downloading mitmproxy binary")
         _manifest = os.path.join(here, self.config["playback_binary_manifest"])
         transformed_manifest = transform_platform(_manifest, self.config["platform"])
         tooltool_download(
             transformed_manifest, self.config["run_local"], self.mozproxy_dir
         )
 
-        # we use one pageset for all platforms
-        LOG.info("downloading mitmproxy pageset")
-        _manifest = self.config["playback_pageset_manifest"]
-        transformed_manifest = transform_platform(_manifest, self.config["platform"])
-        tooltool_download(
-            transformed_manifest, self.config["run_local"], self.mozproxy_dir
-        )
+        if "playback_pageset_manifest" in self.config:
+            # we use one pageset for all platforms
+            LOG.info("downloading mitmproxy pageset")
+            _manifest = self.config["playback_pageset_manifest"]
+            transformed_manifest = transform_platform(_manifest, self.config["platform"])
+            tooltool_download(
+                transformed_manifest, self.config["run_local"], self.mozproxy_dir
+            )
+
+        if "playback_artifacts" in self.config:
+            artifacts = self.config["playback_artifacts"].split(",")
+            for artifact in artifacts:
+                artifact = artifact.strip()
+                if not artifact:
+                    continue
+                artifact_name = artifact.split("/")[-1]
+                dest = os.path.join(self.mozproxy_dir, artifact_name)
+                download_file_from_url(artifact, dest, extract=True)
 
     def start(self):
         """Start playing back the mitmproxy recording."""
 
         self.mitmdump_path = os.path.join(self.mozproxy_dir, "mitmdump")
 
         recordings_list = self.recordings.split()
         self.mitmproxy_proc = self.start_mitmproxy_playback(
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/CSS2/selectors/universal-selector-005.xht.ini
@@ -0,0 +1,4 @@
+[universal-selector-005.xht]
+  disabled:
+    if (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1383229
+    if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1383229
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
@@ -14,52 +14,19 @@ const POPUP_ACTION_COLOR_DARK = "#008f8a
 const POPUP_URL_COLOR_BRIGHT = "#45a1ff";
 const POPUP_ACTION_COLOR_BRIGHT = "#30e60b";
 
 const SEARCH_TERM = "urlbar-reflows-" + Date.now();
 const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+  UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
 });
 
-function promisePopupShown(popup) {
-  return new Promise(resolve => {
-    if (popup.state == "open") {
-      resolve();
-    } else {
-      popup.addEventListener("popupshown", function(event) {
-        resolve();
-      }, {once: true});
-    }
-  });
-}
-
-async function promiseAutocompleteResultPopup(inputText) {
-  gURLBar.focus();
-  gURLBar.value = inputText;
-  gURLBar.controller.startSearch(inputText);
-  await promisePopupShown(gURLBar.popup);
-  await BrowserTestUtils.waitForCondition(() => {
-    return gURLBar.controller.searchStatus >=
-      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
-  });
-}
-
-async function waitForAutocompleteResultAt(index) {
-  let searchString = gURLBar.controller.searchString;
-  await BrowserTestUtils.waitForCondition(
-    () => gURLBar.popup.richlistbox.itemChildren.length > index &&
-          gURLBar.popup.richlistbox.itemChildren[index].getAttribute("ac-text") == searchString,
-    `Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
-  // Ensure the addition is complete, for proper mouse events on the entries.
-  await new Promise(resolve => window.requestIdleCallback(resolve, {timeout: 1000}));
-  return gURLBar.popup.richlistbox.itemChildren[index];
-}
-
 add_task(async function setup() {
   await PlacesUtils.history.clear();
   const NUM_VISITS = 10;
   let visits = [];
 
   for (let i = 0; i < NUM_VISITS; ++i) {
     visits.push({
       uri: `http://example.com/urlbar-reflows-${i}`,
@@ -70,16 +37,18 @@ add_task(async function setup() {
   await PlacesTestUtils.addVisits(visits);
 
   registerCleanupFunction(async function() {
     await PlacesUtils.history.clear();
   });
 });
 
 add_task(async function test_popup_url() {
+  const quantumbar = UrlbarPrefs.get("quantumbar");
+
   // Load extension with brighttext not set
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
           "theme_frame": "image1.png",
         },
         "colors": {
@@ -111,57 +80,60 @@ add_task(async function test_popup_url()
 
   let visits = [];
 
   for (let i = 0; i < maxResults; i++) {
     visits.push({uri: makeURI("http://example.com/autocomplete/?" + i)});
   }
 
   await PlacesTestUtils.addVisits(visits);
-  await promiseAutocompleteResultPopup("example.com/autocomplete");
-  await waitForAutocompleteResultAt(maxResults - 1);
+  await UrlbarTestUtils.promiseAutocompleteResultPopup(window,
+                                                       "example.com/autocomplete",
+                                                       waitForFocus);
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, maxResults - 1);
 
-  let popup = gURLBar.popup;
-  let results = popup.richlistbox.itemChildren;
-  is(results.length, maxResults,
-     "Should get maxResults=" + maxResults + " results");
+  Assert.equal(UrlbarTestUtils.getResultCount(window), maxResults,
+               "Should get maxResults=" + maxResults + " results");
 
+  let popup = UrlbarTestUtils.getPanel(window);
   let popupCS = window.getComputedStyle(popup);
 
   Assert.equal(popupCS.backgroundColor,
                `rgb(${hexToRGB(POPUP_COLOR).join(", ")})`,
                `Popup background color should be set to ${POPUP_COLOR}`);
 
   testBorderColor(popup, POPUP_BORDER_COLOR);
 
   Assert.equal(popupCS.color,
                `rgb(${hexToRGB(POPUP_TEXT_COLOR_DARK).join(", ")})`,
                `Popup color should be set to ${POPUP_TEXT_COLOR_DARK}`);
 
   // Set the selected attribute to true to test the highlight popup properties
-  results[1].setAttribute("selected", "true");
-  let resultCS = window.getComputedStyle(results[1]);
+  UrlbarTestUtils.setSelectedIndex(window, 1);
+  let actionResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  let urlResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+  let resultCS = window.getComputedStyle(urlResult.element.row);
 
   Assert.equal(resultCS.backgroundColor,
                `rgb(${hexToRGB(POPUP_SELECTED_COLOR).join(", ")})`,
                `Popup highlight background color should be set to ${POPUP_SELECTED_COLOR}`);
 
   Assert.equal(resultCS.color,
                `rgb(${hexToRGB(POPUP_SELECTED_TEXT_COLOR).join(", ")})`,
                `Popup highlight color should be set to ${POPUP_SELECTED_TEXT_COLOR}`);
 
-  results[1].removeAttribute("selected");
+  // Now set the index to somewhere not on the first two, so that we can test both
+  // url and action text colors.
+  UrlbarTestUtils.setSelectedIndex(window, 2);
 
-  let urlText = results[1]._urlText;
-  Assert.equal(window.getComputedStyle(urlText).color,
+  Assert.equal(window.getComputedStyle(urlResult.element.url).color,
                `rgb(${hexToRGB(POPUP_URL_COLOR_DARK).join(", ")})`,
                `Urlbar popup url color should be set to ${POPUP_URL_COLOR_DARK}`);
 
-  let actionText = results[1]._actionText;
-  Assert.equal(window.getComputedStyle(actionText).color,
+  Assert.equal(window.getComputedStyle(actionResult.element.action).color,
                `rgb(${hexToRGB(POPUP_ACTION_COLOR_DARK).join(", ")})`,
                `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_DARK}`);
 
   let root = document.documentElement;
   Assert.equal(root.getAttribute("lwt-popup-brighttext"),
                "",
                "brighttext should not be set!");
   Assert.equal(root.getAttribute("lwt-popup-darktext"),
@@ -198,30 +170,27 @@ add_task(async function test_popup_url()
 
   await extension.startup();
 
   popupCS = window.getComputedStyle(popup);
   Assert.equal(popupCS.color,
                `rgb(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")})`,
                `Popup color should be set to ${POPUP_TEXT_COLOR_BRIGHT}`);
 
-  urlText = results[1]._urlText;
-  Assert.equal(window.getComputedStyle(urlText).color,
+  Assert.equal(window.getComputedStyle(urlResult.element.url).color,
                `rgb(${hexToRGB(POPUP_URL_COLOR_BRIGHT).join(", ")})`,
                `Urlbar popup url color should be set to ${POPUP_URL_COLOR_BRIGHT}`);
 
-  actionText = results[1]._actionText;
-  Assert.equal(window.getComputedStyle(actionText).color,
+  Assert.equal(window.getComputedStyle(actionResult.element.action).color,
                `rgb(${hexToRGB(POPUP_ACTION_COLOR_BRIGHT).join(", ")})`,
                `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_BRIGHT}`);
 
   // Since brighttext is enabled, the seperator color should be
   // POPUP_TEXT_COLOR_BRIGHT with added alpha.
-  let separator = results[1]._separator;
-  Assert.equal(window.getComputedStyle(separator).color,
+  Assert.equal(window.getComputedStyle(urlResult.element.separator, quantumbar ? ":before" : null).color,
                `rgba(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")}, 0.5)`,
                `Urlbar popup separator color should be set to ${POPUP_TEXT_COLOR_BRIGHT} with alpha`);
 
   Assert.equal(root.getAttribute("lwt-popup-brighttext"),
                "true",
                "brighttext should be set to true!");
   Assert.equal(root.getAttribute("lwt-popup-darktext"),
                "",
@@ -240,13 +209,12 @@ add_task(async function test_popup_url()
 
   // Calculate what GrayText should be. May differ between platforms.
   let span = document.createXULElement("span");
   span.style.color = "GrayText";
   document.documentElement.appendChild(span);
   let GRAY_TEXT = window.getComputedStyle(span).color;
   span.remove();
 
-  separator = results[1]._separator;
-  Assert.equal(window.getComputedStyle(separator).color,
+  Assert.equal(window.getComputedStyle(urlResult.element.separator, quantumbar ? ":before" : null).color,
                GRAY_TEXT,
                `Urlbar popup separator color should be set to ${GRAY_TEXT}`);
 });
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -347,20 +347,20 @@ var SafeBrowsing = {
       let updateURL = Services.urlFormatter.formatURLPref(
         "browser.safebrowsing.provider." + provider + ".updateURL");
       let gethashURL = Services.urlFormatter.formatURLPref(
         "browser.safebrowsing.provider." + provider + ".gethashURL");
       updateURL = updateURL.replace("SAFEBROWSING_ID", clientID);
       gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID);
 
       // Disable updates and gethash if the Google API key is missing.
-      let googleKey = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
+      let googleSafebrowsingKey = Services.urlFormatter.formatURL("%GOOGLE_SAFEBROWSING_API_KEY%").trim();
       if ((provider == "google" || provider == "google4") &&
-          (!googleKey || googleKey == "no-google-api-key")) {
-        log("Missing Google API key, clearing updateURL and gethashURL.");
+          (!googleSafebrowsingKey || googleSafebrowsingKey == "no-google-safebrowsing-api-key")) {
+        log("Missing Google SafeBrowsing API key, clearing updateURL and gethashURL.");
         updateURL = "";
         gethashURL = "";
       }
 
       log("Provider: " + provider + " updateURL=" + updateURL);
       log("Provider: " + provider + " gethashURL=" + gethashURL);
 
       // Urls used to update DB
--- a/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html
@@ -55,32 +55,32 @@ for (let provider in providers) {
 var lists = [];
 for (let pref of prefs) {
   lists = lists.concat(SpecialPowers.getCharPref(pref).split(","));
 }
 
 var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                   getService(Ci.nsIUrlListManager);
 
-let googleKey = SpecialPowers.Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
+let googleKey = SpecialPowers.Services.urlFormatter.formatURL("%GOOGLE_SAFEBROWSING_API_KEY%").trim();
 
 for (let list of lists) {
   if (!list)
     continue;
 
   // For lists having a provider, it should have a correct gethash url
   // For lists without a provider, for example, test-malware-simple, it should not
   // have a gethash url.
   var url = listmanager.getGethashUrl(list);
   var index = listsWithProvider.indexOf(list);
   if (index >= 0) {
     let provider = listsToProvider[index];
     let pref = "browser.safebrowsing.provider." + provider + ".gethashURL";
     if ((provider == "google" || provider == "google4") &&
-        (!googleKey || googleKey == "no-google-api-key")) {
+        (!googleKey || googleKey == "no-google-safebrowsing-api-key")) {
       is(url, "", "getHash url of " + list + " should be empty");
     } else {
       is(url, SpecialPowers.getCharPref(pref), list + " matches its gethash url");
     }
   } else {
     is(url, "", list + " should not have a gethash url");
   }
 }
--- a/toolkit/components/urlformatter/URLFormatter.jsm
+++ b/toolkit/components/urlformatter/URLFormatter.jsm
@@ -80,17 +80,18 @@ nsURLFormatterService.prototype = {
     PLATFORMBUILDID() { return Services.appinfo.platformBuildID; },
     APP() { return Services.appinfo.name.toLowerCase().replace(/ /, ""); },
     OS() { return Services.appinfo.OS; },
     XPCOMABI() { return this.ABI; },
     BUILD_TARGET() { return Services.appinfo.OS + "_" + this.ABI; },
     OS_VERSION() { return this.OSVersion; },
     CHANNEL:          () => UpdateUtils.UpdateChannel,
     MOZILLA_API_KEY:  () => AppConstants.MOZ_MOZILLA_API_KEY,
-    GOOGLE_API_KEY:   () => AppConstants.MOZ_GOOGLE_API_KEY,
+    GOOGLE_LOCATION_SERVICE_API_KEY:   () => AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY,
+    GOOGLE_SAFEBROWSING_API_KEY:   () => AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY,
     BING_API_CLIENTID: () => AppConstants.MOZ_BING_API_CLIENTID,
     BING_API_KEY:     () => AppConstants.MOZ_BING_API_KEY,
     DISTRIBUTION() { return this.distribution.id; },
     DISTRIBUTION_VERSION() { return this.distribution.version; },
   },
 
   formatURL: function uf_formatURL(aFormat) {
     var _this = this;
@@ -121,16 +122,19 @@ nsURLFormatterService.prototype = {
         format = Services.prefs.getComplexValue(aPref, Ci.nsIPrefLocalizedString).data;
       } catch (ex) {}
     }
 
     return this.formatURL(format);
   },
 
   trimSensitiveURLs: function uf_trimSensitiveURLs(aMsg) {
-    // Only the google API key is sensitive for now.
-    return AppConstants.MOZ_GOOGLE_API_KEY ? aMsg.replace(RegExp(AppConstants.MOZ_GOOGLE_API_KEY, "g"),
+    // Only the google API keys is sensitive for now.
+    aMsg = AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY ? aMsg.replace(RegExp(AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY, "g"),
+                                                 "[trimmed-google-api-key]")
+                                  : aMsg;
+    return AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY ? aMsg.replace(RegExp(AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY, "g"),
                                                  "[trimmed-google-api-key]")
                                   : aMsg;
   },
 };
 
 var EXPORTED_SYMBOLS = ["nsURLFormatterService"];
--- a/toolkit/components/urlformatter/tests/unit/test_urlformatter.js
+++ b/toolkit/components/urlformatter/tests/unit/test_urlformatter.js
@@ -41,16 +41,19 @@ function run_test() {
   // Keys must be uppercase
   Assert.notEqual(formatter.formatURL(lowerUrlRaw), ulUrlRef);
   Assert.equal(formatter.formatURL(multiUrl), multiUrlRef);
   // Encoded strings must be kept as is (Bug 427304)
   Assert.equal(formatter.formatURL(encodedUrl), encodedUrlRef);
 
   Assert.equal(formatter.formatURL(advancedUrl), advancedUrlRef);
 
-  for (let val of ["MOZILLA_API_KEY", "GOOGLE_API_KEY", "BING_API_CLIENTID", "BING_API_KEY"]) {
+  for (let val of ["MOZILLA_API_KEY", "GOOGLE_LOCATION_SERVICE_API_KEY", "GOOGLE_SAFEBROWSING_API_KEY", "BING_API_CLIENTID", "BING_API_KEY"]) {
     let url = "http://test.mozilla.com/?val=%" + val + "%";
     Assert.notEqual(formatter.formatURL(url), url);
   }
 
-  let url = "http://test.mozilla.com/%GOOGLE_API_KEY%/?val=%GOOGLE_API_KEY%";
-  Assert.equal(formatter.trimSensitiveURLs(formatter.formatURL(url)), "http://test.mozilla.com/[trimmed-google-api-key]/?val=[trimmed-google-api-key]");
+  let url_sb = "http://test.mozilla.com/%GOOGLE_SAFEBROWSING_API_KEY%/?val=%GOOGLE_SAFEBROWSING_API_KEY%";
+  Assert.equal(formatter.trimSensitiveURLs(formatter.formatURL(url_sb)), "http://test.mozilla.com/[trimmed-google-api-key]/?val=[trimmed-google-api-key]");
+
+  let url_gls = "http://test.mozilla.com/%GOOGLE_LOCATION_SERVICE_API_KEY%/?val=%GOOGLE_LOCATION_SERVICE_API_KEY%";
+  Assert.equal(formatter.trimSensitiveURLs(formatter.formatURL(url_gls)), "http://test.mozilla.com/[trimmed-google-api-key]/?val=[trimmed-google-api-key]");
 }
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -128,18 +128,21 @@ var snapshotFormatters = {
         $("policies-status").appendChild(activePolicies);
       } else {
         document.l10n.setAttributes($("policies-status"), policiesStrId);
       }
     } else {
       $("policies-status-row").hidden = true;
     }
 
-    let keyGoogleFound = data.keyGoogleFound ? "found" : "missing";
-    document.l10n.setAttributes($("key-google-box"), keyGoogleFound);
+    let keyLocationServiceGoogleFound = data.keyLocationServiceGoogleFound ? "found" : "missing";
+    document.l10n.setAttributes($("key-location-service-google-box"), keyLocationServiceGoogleFound);
+
+    let keySafebrowsingGoogleFound = data.keySafebrowsingGoogleFound ? "found" : "missing";
+    document.l10n.setAttributes($("key-safebrowsing-google-box"), keySafebrowsingGoogleFound);
 
     let keyMozillaFound = data.keyMozillaFound ? "found" : "missing";
     document.l10n.setAttributes($("key-mozilla-box"), keyMozillaFound);
 
     $("safemode-box").textContent = data.safeMode;
   },
 
   crashes(data) {
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -189,19 +189,26 @@
           <tr id="policies-status-row">
             <th class="column" data-l10n-id="app-basics-enterprise-policies"/>
 
             <td id="policies-status">
             </td>
           </tr>
 
           <tr>
-            <th class="column" data-l10n-id="app-basics-key-google"/>
+            <th class="column" data-l10n-id="app-basics-location-service-key-google"/>
 
-            <td id="key-google-box">
+            <td id="key-location-service-google-box">
+            </td>
+          </tr>
+
+          <tr>
+            <th class="column" data-l10n-id="app-basics-safebrowsing-key-google"/>
+
+            <td id="key-safebrowsing-google-box">
             </td>
           </tr>
 
           <tr>
             <th class="column" data-l10n-id="app-basics-key-mozilla"/>
 
             <td id="key-mozilla-box">
             </td>
--- a/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
+++ b/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
@@ -47,17 +47,18 @@ app-basics-os = OS
 app-basics-memory-use = Memory Use
 app-basics-performance = Performance
 app-basics-service-workers = Registered Service Workers
 app-basics-profiles = Profiles
 app-basics-launcher-process-status = Launcher Process
 app-basics-multi-process-support = Multiprocess Windows
 app-basics-process-count = Web Content Processes
 app-basics-enterprise-policies = Enterprise Policies
-app-basics-key-google = Google Key
+app-basics-location-service-key-google = Google Location Service Key
+app-basics-safebrowsing-key-google = Google Safebrowsing Key
 app-basics-key-mozilla = Mozilla Location Service Key
 app-basics-safe-mode = Safe Mode
 show-dir-label =
     { PLATFORM() ->
         [macos] Show in Finder
         [windows] Open Folder
        *[other] Open Directory
     }
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -315,17 +315,18 @@ this.AppConstants = Object.freeze({
   INSTALL_LOCALE: "@AB_CD@",
   MOZ_WIDGET_TOOLKIT: "@MOZ_WIDGET_TOOLKIT@",
   ANDROID_PACKAGE_NAME: "@ANDROID_PACKAGE_NAME@",
 
   DEBUG_JS_MODULES: "@DEBUG_JS_MODULES@",
 
   MOZ_BING_API_CLIENTID: "@MOZ_BING_API_CLIENTID@",
   MOZ_BING_API_KEY: "@MOZ_BING_API_KEY@",
-  MOZ_GOOGLE_API_KEY: "@MOZ_GOOGLE_API_KEY@",
+  MOZ_GOOGLE_LOCATION_SERVICE_API_KEY: "@MOZ_GOOGLE_LOCATION_SERVICE_API_KEY@",
+  MOZ_GOOGLE_SAFEBROWSING_API_KEY: "@MOZ_GOOGLE_SAFEBROWSING_API_KEY@",
   MOZ_MOZILLA_API_KEY: "@MOZ_MOZILLA_API_KEY@",
 
   BROWSER_CHROME_URL: "@BROWSER_CHROME_URL@",
 
   // URL to the hg revision this was built from (e.g.
   // "https://hg.mozilla.org/mozilla-central/rev/6256ec9113c1")
   // On unofficial builds, this is an empty string.
 #ifndef MOZ_SOURCE_URL
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -221,18 +221,21 @@ var dataProviders = {
     } catch (e) {
       data.autoStartStatus = -1;
     }
 
     if (Services.policies) {
       data.policiesStatus = Services.policies.status;
     }
 
-    const keyGoogle = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
-    data.keyGoogleFound = keyGoogle != "no-google-api-key" && keyGoogle.length > 0;
+    const keyLocationServiceGoogle = Services.urlFormatter.formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%").trim();
+    data.keyLocationServiceGoogleFound = keyLocationServiceGoogle != "no-google-location-service-api-key" && keyLocationServiceGoogle.length > 0;
+
+    const keySafebrowsingGoogle = Services.urlFormatter.formatURL("%GOOGLE_SAFEBROWSING_API_KEY%").trim();
+    data.keySafebrowsingGoogleFound = keySafebrowsingGoogle != "no-google-safebrowsing-api-key" && keySafebrowsingGoogle.length > 0;
 
     const keyMozilla = Services.urlFormatter.formatURL("%MOZILLA_API_KEY%").trim();
     data.keyMozillaFound = keyMozilla != "no-mozilla-api-key" && keyMozilla.length > 0;
 
     done(data);
   },
 
   extensions: async function extensions(done) {
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -297,17 +297,18 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
     ]
 
 for var in ('ANDROID_PACKAGE_NAME',
             'MOZ_APP_NAME',
             'MOZ_APP_VERSION',
             'MOZ_APP_VERSION_DISPLAY',
             'MOZ_BING_API_CLIENTID',
             'MOZ_BING_API_KEY',
-            'MOZ_GOOGLE_API_KEY',
+            'MOZ_GOOGLE_LOCATION_SERVICE_API_KEY',
+            'MOZ_GOOGLE_SAFEBROWSING_API_KEY',
             'MOZ_MACBUNDLE_NAME',
             'MOZ_MOZILLA_API_KEY',
             'MOZ_WIDGET_TOOLKIT',
             'DLL_PREFIX',
             'DLL_SUFFIX',
             'DEBUG_JS_MODULES'):
             DEFINES[var] = CONFIG[var]
 
--- a/toolkit/modules/tests/browser/browser_Troubleshoot.js
+++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js
@@ -144,17 +144,20 @@ const SNAPSHOT_SCHEMA = {
           type: "number",
         },
         maxContentProcesses: {
           type: "number",
         },
         policiesStatus: {
           type: "number",
         },
-        keyGoogleFound: {
+        keyLocationServiceGoogleFound: {
+          type: "boolean",
+        },
+        keySafebrowsingGoogleFound: {
           type: "boolean",
         },
         keyMozillaFound: {
           type: "boolean",
         },
         safeMode: {
           type: "boolean",
         },
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -650,17 +650,19 @@ check_prog('ZIP', ('zip',))
 check_prog('GN', ('gn',), allow_missing=True)
 
 # Key files
 # ==============================================================
 include('../build/moz.configure/keyfiles.configure')
 
 simple_keyfile('Mozilla API')
 
-simple_keyfile('Google API')
+simple_keyfile('Google Location Service API')
+
+simple_keyfile('Google Safebrowsing API')
 
 id_and_secret_keyfile('Bing API')
 
 simple_keyfile('Adjust SDK')
 
 id_and_secret_keyfile('Leanplum SDK')
 
 simple_keyfile('Pocket API')