Merge autoland to mozilla-central a=merge
authorAndreea Pavel <apavel@mozilla.com>
Tue, 14 May 2019 00:45:02 +0300
changeset 535562 3c7f3988e7045742252732b4948b6fa851ff6057
parent 535457 cb5734727c0a9d88e3e236a3e4a741a051509e0e (current diff)
parent 535523 027695189d656e036946b9774921fb8e2e09a1f8 (diff)
child 535563 a0403c2bfae40a8b3cebd532ce730fa824181fee
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central a=merge
browser/extensions/screenshots/_locales/en_GB/messages.json
devtools/shared/specs/script.js
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<blocklist lastupdate="1557222511299" xmlns="http://www.mozilla.org/2006/addons-blocklist">
+<blocklist lastupdate="1557497630665" xmlns="http://www.mozilla.org/2006/addons-blocklist">
   <emItems>
     <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i1211" id="flvto@hotger.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
@@ -2899,16 +2899,48 @@
     <emItem blockID="d7ca07b4-9c97-4f49-a304-117c874ff073" id="artur.dubovoy@gmail.com">
       <prefs/>
       <versionRange minVersion="16.3.5" maxVersion="16.3.9" severity="3"/>
     </emItem>
     <emItem blockID="b62c9ee1-d66f-4964-906e-2a9b07e3fdc1" id="/^((adsmin@vietbacsecurity\.com)|(\{efdefbd4-5c30-42c3-ad2b-4c49082ec4cd\})|(\{63d83b36-a85c-4b51-8f68-8eb6c0ea6922\})|(\{4613a1ed-6cb1-410b-a8b1-3f81f73b6e00\})|(\{90b1aef7-7a52-4649-b5ca-91b5e81b5eab\})|(\{d6e2e76d-edff-416b-8c04-53052ff9fec7\})|(\{43af2e0f-b5ce-409b-9ee6-5360785c9b08\})|(\{e45fa96d-8b74-4666-86de-3bbfb774a74f\})|(\{4f8332b6-6167-4b7f-a1f9-61d8eb89b102\})|(cpcnbnofbhmpimepokdpmoomejafefhb@chrome-store-foxified-14654081)|(developios89@gmail\.com)|(\{d82da356-1fa8-4550-958a-bd2472972314\})|(\{1dfbd1c3-a8ca-4eb3-8747-d30bfd20ecd5\})|(\{6f9fa22a-128f-4d1b-8ef5-d20a44d24245\})|(\{5f6af572-35c1-44d7-9d0f-dffbb62fcafe\})|(developper@avast\.com)|(\{886a6486-37b3-4bcd-891b-fd0e335e7b1a\})|(\{886a6486-37b3-4bcd-891b-fd0e355e7b1a\})|(\{d1cd26ff-fde7-46a4-85cc-48e3bb7e9e8d\})|(\{ae11d5cc-8efb-43a0-89bf-e5a779b4fa40\})|(\{aca140ce-8249-4e6e-8e2c-cd5b1c987441\})|(\{f68b2ca7-0d2c-44cc-afc8-a606a896c467\})|(\{321db3c3-8cfd-49f1-99de-fcdc3485b379\}))$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="2feeb46a-6784-4c6e-8c07-e120bec00b14" id="/^((\{d389cdfe-843e-44cb-b127-441492e46e63\})|(\{1340c760-3f4c-4428-b2c0-88821a84de2b\})|(\{38524a16-a73d-4a8f-8111-f9347bb5266c\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="b9686c72-1902-4868-88d1-6587fd24a57c" id="/^((\{c8d0fea0-d7b7-4f6f-b9bc-9df6722d9d18\})|(\{bed8e1f2-b00b-44e3-8cf0-5335080d0003\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="312e30b0-0b4c-4a43-8f6c-8b8447a20f6a" id="{5308dcd8-f3c7-4b85-ad66-54a120243594}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="04a300c2-04fc-401e-a428-c7c887bf2bff" id="/^((\{4e84c504-10e8-4e75-8885-dcc0c90999b9\})|(\{8ce99d6d-8d0d-4420-bd17-c303bd8a763e\})|(\{16de314a-56cd-4175-9baf-bbe0b09dfed3\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="12a0c69f-e755-428b-97dc-229bccb8a5b0" id="{2b10c1c8-a11f-4bad-fe9c-1c11e82cac42}">
+      <prefs/>
+      <versionRange minVersion="0.9.5.11" maxVersion="0.9.5.14" severity="1"/>
+    </emItem>
+    <emItem blockID="f0fc8d21-d0ec-4285-82d7-d482dae772bc" id="{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}">
+      <prefs/>
+      <versionRange minVersion="3.2" maxVersion="3.5.1" severity="1"/>
+    </emItem>
+    <emItem blockID="8ff19ad3-e4e0-40e3-8f02-fd80d18f63b5" id="jid1-NIfFY2CA8fy1tg@jetpack">
+      <prefs/>
+      <versionRange minVersion="3.19.0" maxVersion="3.28.0" severity="1"/>
+    </emItem>
+    <emItem blockID="fee4b92e-146b-437d-9cc0-95cfc800f0e0" id="/^((\{da61a3e5-5a98-4c47-ae6c-f4db738f1133\})|(\{b0e13c2b-c1cd-426b-bed9-905bf9557fbf\})|(\{328c22c5-5f1c-4eb7-95a3-148fd4ad429d\})|(\{f6cca5fb-5aa1-471f-88f3-e2ffa87281ef\})|(\{d342bf37-554e-41c9-b67b-72769e59b82b\})|(\{03ec69b5-3e8e-4bb8-8eda-28f12c54bff8\})|(\{a8c876cb-af13-4ad9-9a86-fc3c0722b48c\})|(\{56136c32-0159-4368-9d28-c1b8b1515c89\})|(\{79bf4660-9729-444b-ae03-6c8005869611\})|(\{aa7fdaa5-d888-47e2-b27b-4fa4b3225339\})|(\{31e0d180-52b1-4c1d-8f84-7e625715edc4\})|(\{f7d20549-e5ee-4045-9e8f-9705bb10c104\})|(\{303abacb-760b-43c3-9640-5b456d92db78\})|(\{debabd67-2e0a-485e-8213-ac081065a027\})|(\{971e739b-c528-41b6-a60c-48fc3cdb52d9\})|(\{ffb3a485-2723-4a88-b3ad-8b29773759c4\})|(\{b076177a-a5c4-4652-9f6d-953f89f9a81a\})|(\{66210cb7-6352-45d5-9d22-ad7a0fb5e247\})|(\{8053ad7b-5129-4c74-ade9-8166c38e8636\})|(\{1a435c36-133e-4163-ac71-8701a147880c\})|(\{8c40c6df-7c9d-4876-bcbe-0621734aba45\})|(\{40e1e7d9-ae29-4aec-9465-5e0d49859583\})|(\{74eab03b-35cd-4950-b436-7afce3876e58\})|(\{95839c11-63a7-4b2b-b3d3-eee9d2c5c42d\})|(\{bfaa03c3-744e-48eb-8fb6-4ad61791d4d8\})|(\{f123e726-9396-4899-822a-172b8bcb2c5f\})|(\{157e255b-2053-4140-b95c-ff003b62bf17\})|(\{3e49a17b-b58e-417b-9ebb-a7e8c2317893\})|(\{4df1d536-e30f-4344-bee6-6ef2def890c2\})|(\{f33ce070-63f4-4d2b-823e-d52fc7a30ba7\})|(\{2003e2a5-e848-4fc5-8e7d-3af1efe4f992\})|(\{ff2157da-6981-40b6-aa60-d8125e73868e\})|(\{d89fa1e5-c9d4-4104-ad8e-00b39e5c6d15\})|(\{66e45d14-550f-4489-98c6-8a0caed33375\})|(\{86e6d45f-1dfe-4e53-bf52-22bf65b9ae6d\})|(\{e71407fe-e1ed-4755-af8f-dd64a952ce1a\})|(\{b67b3615-d8fe-4961-a41e-391864afde2d\})|(\{5785789b-ccba-44a1-9018-1135b56bd37f\})|(\{6dfb93d1-2add-471c-bbbc-b6164b4c1d94\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
   </emItems>
   <pluginItems>
     <pluginItem blockID="p332">
       <match exp="libflashplayer\.so" name="filename"/>
       <match exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" name="description"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange severity="0" vulnerabilitystatus="1">
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -8,18 +8,20 @@ tags=quantumbar
 support-files =
   dummy_page.html
   head.js
   head-common.js
 
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
 [browser_autocomplete_a11y_label.js]
-skip-if = true # Bug 1524539 - need to fix a11y. When removing this line, uncomment the next (or fix it!).
-# skip-if = (verify && !debug && (os == 'win'))
+skip-if = (verify && !debug && (os == 'win'))
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_cursor.js]
 [browser_autocomplete_edit_completed.js]
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_readline_navigation.js]
 skip-if = os != "mac" # Mac only feature
 [browser_autocomplete_tag_star_visibility.js]
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
@@ -4,29 +4,70 @@
 /**
  * This test ensures that we produce good labels for a11y purposes.
  */
 
 const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
 const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
+async function getResultText(element) {
+  await initAccessibilityService();
+  await BrowserTestUtils.waitForCondition(() => accService.getAccessibleFor(element));
+  let accessible = accService.getAccessibleFor(element);
+  return accessible.name;
+}
+
+let accService;
+async function initAccessibilityService() {
+  if (accService) {
+    return;
+  }
+  accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+    Ci.nsIAccessibilityService);
+  if (Services.appinfo.accessibilityEnabled) {
+    return;
+  }
+
+  async function promiseInitOrShutdown(init = true) {
+    await new Promise(resolve => {
+      let observe = (subject, topic, data) => {
+        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+        // "1" indicates that the accessibility service is initialized.
+        if (data === (init ? "1" : "0")) {
+          resolve();
+        }
+      };
+      Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+    });
+  }
+  await promiseInitOrShutdown(true);
+  registerCleanupFunction(async () => {
+    accService = null;
+    await promiseInitOrShutdown(false);
+  });
+}
+
 add_task(async function switchToTab() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:about");
 
   await promiseAutocompleteResultPopup("% about");
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
   Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
     "Should have a switch tab result");
 
-  // XXX Bug 1524539. This fails on QuantumBar because we're producing different
-  // outputs. Once we confirm accessibilty is ok with the new format, we
-  // should update and have this test running on QuantumBar.
   let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
-  is(element.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab");
+  is(await getResultText(element),
+     UrlbarPrefs.get("quantumbar") ?
+       // The extra spaces are here due to bug 1550644.
+       "about : about— Switch to Tab" :
+       "about:about about:about Tab",
+     UrlbarPrefs.get("quantumbar") ?
+       "Result a11y label should be: <title>— Switch to Tab" :
+       "Result a11y label should be: <title> <url> Tab");
 
   await UrlbarTestUtils.promisePopupClose(window);
   gBrowser.removeTab(tab);
 });
 
 add_task(async function searchSuggestions() {
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
@@ -47,27 +88,37 @@ add_task(async function searchSuggestion
   // by earlier tests.
   Assert.greaterOrEqual(length, 3,
     "Should get at least heuristic result + two search suggestions");
   // The first expected search is the search term itself since the heuristic
   // result will come before the search suggestions.
   let expectedSearches = [
     "foo",
     "foofoo",
-    "foobar",
+    // The extra spaces is here due to bug 1550644.
+    UrlbarPrefs.get("quantumbar") ? "foo bar " : "foobar",
   ];
   for (let i = 0; i < length; i++) {
     let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
     if (result.type === UrlbarUtils.RESULT_TYPE.SEARCH) {
       Assert.greaterOrEqual(expectedSearches.length, 0,
         "Should still have expected searches remaining");
       let suggestion = expectedSearches.shift();
-      // XXX Bug 1524539. This fails on QuantumBar because we're producing different
-      // outputs. Once we confirm accessibilty is ok with the new format, we
-      // should update and have this test running on QuantumBar.
       let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, i);
-      Assert.equal(element.label,
-        suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search",
-        "Result label should be: <search term> <engine name> Search");
+      let selected = element.hasAttribute("selected");
+      if (!selected) {
+        // Simulate the result being selected so we see the expanded text.
+        element.toggleAttribute("selected", true);
+      }
+      Assert.equal(await getResultText(element),
+        UrlbarPrefs.get("quantumbar") ?
+          suggestion + "— Search with browser_searchSuggestionEngine searchSuggestionEngine.xml" :
+          suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search",
+        UrlbarPrefs.get("quantumbar") ?
+          "Result label should be: <search term>— Search with <engine name>" :
+          "Result label should be: <search term> <engine name> Search");
+      if (!selected) {
+        element.toggleAttribute("selected", false);
+      }
     }
   }
   Assert.ok(expectedSearches.length == 0);
 });
--- a/browser/config/mozconfigs/linux64/noopt-debug
+++ b/browser/config/mozconfigs/linux64/noopt-debug
@@ -1,9 +1,7 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-
 # Developers often build with these options for a better debugging experience.
 . "$topsrcdir/browser/config/mozconfigs/linux64/debug"
 
 # We add this last to guard against inadvertent changes in the debug config.
 # It may conflict with settings from mozconfig.override, but that seems
 # unlikely.
 ac_add_options --disable-optimize
--- a/browser/extensions/screenshots/_locales/ach/messages.json
+++ b/browser/extensions/screenshots/_locales/ach/messages.json
@@ -12,48 +12,71 @@
     "message": "Cal Na"
   },
   "screenshotInstructions": {
     "message": "Ywar onyo dii ii potbuk me yero bute. Dii ESC me juko."
   },
   "saveScreenshotSelectedArea": {
     "message": "Gwoki"
   },
+  "uploadScreenshotSelectedArea": {
+    "message": "Keti"
+  },
   "saveScreenshotVisibleArea": {
     "message": "Gwok ma nen"
   },
   "saveScreenshotFullPage": {
     "message": "Gwok potbuk weng"
   },
   "cancelScreenshot": {
     "message": "Juki"
   },
   "downloadScreenshot": {
     "message": "Gam"
   },
   "downloadOnlyNotice": {
     "message": "Kombedi itye i kit me gam keken."
   },
+  "downloadOnlyDetailsPrivate": {
+    "message": "I dirica me Yeny i Mung."
+  },
+  "downloadOnlyDetailsNeverRemember": {
+    "message": "“Pe ipoo ikom gin mukato” tye ma kicako."
+  },
   "downloadOnlyDetailsESR": {
     "message": "Itye ka tic ki Firefox pi ESR."
   },
+  "downloadOnlyDetailsNoUploadPref": {
+    "message": "Kijuko woko keto."
+  },
   "notificationLinkCopiedTitle": {
     "message": "Ki loko kakube"
   },
   "notificationLinkCopiedDetails": {
     "message": "Ki loko kakube me cal mamegi i bao me coc. Dii $META_KEY$-V me mwono ne.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Loki"
   },
+  "notificationImageCopiedTitle": {
+    "message": "Kiloko Cal"
+  },
+  "notificationImageCopiedDetails": {
+    "message": "Ki loko cal mamegi i bao coc. Dii $META_KEY$-V me mwono ne.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
   "requestErrorTitle": {
     "message": "Pe tye katic."
   },
   "requestErrorDetails": {
     "message": "Timwa kica! Pe onongo wa twero gwoko cal mamegi. Tim ber item doki lacen."
   },
   "connectionErrorTitle": {
     "message": "Pe watwero kube ki cal me wang kio mamegi."
@@ -77,40 +100,31 @@
     "message": "Yer mamegi tidi tutwal"
   },
   "genericErrorTitle": {
     "message": "Woo! Firefox Screenshots opo oo."
   },
   "genericErrorDetails": {
     "message": "Pe wa ngeyo ngo ma otime kombedi. Iromo temo ne doki onyo mako cal pa potbuk mukene?"
   },
-  "tourBodyIntro": {
-    "message": "Maki, gwoki, ki nywak cal me wang kio labongo weko Firefox."
-  },
   "tourHeaderPageAction": {
     "message": "Yoo manyen me gwoko"
   },
   "tourHeaderClickAndDrag": {
     "message": "Mak ngo ma imito keken"
   },
   "tourBodyClickAndDrag": {
     "message": "Dii ka i ywar me mako cal pa but potbuk keken. Itwero bene wot iwiye me wero yer mamegi."
   },
   "tourHeaderFullPage": {
     "message": "Mak dirica onyo Potbuk weng"
   },
   "tourBodyFullPage": {
     "message": "Yer mapeca ma i tung lacuc malo me mako kabedo ma nen i dirica onyo me mako potbuk weng."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Kit ma imito"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Gwok cal mamegi ma ki ngolo ii Kakube pi nywako i yoo ma yot, onyo gamo gi i kompiuta ni. Itwero bene diyo mapeca me Cal Na me nongo cal ma i mako weng."
-  },
   "tourSkip": {
     "message": "Kal"
   },
   "tourNext": {
     "message": "Cal malubo"
   },
   "tourPrevious": {
     "message": "Cal mukato"
--- a/browser/extensions/screenshots/_locales/az/messages.json
+++ b/browser/extensions/screenshots/_locales/az/messages.json
@@ -27,16 +27,19 @@
     "message": "Tam səhifəni saxla"
   },
   "cancelScreenshot": {
     "message": "Ləğv et"
   },
   "downloadScreenshot": {
     "message": "Endir"
   },
+  "downloadScreenshotTitle": {
+    "message": "Ekran görüntüsünü endir"
+  },
   "downloadOnlyNotice": {
     "message": "Hazırda Ancaq-Endirmə rejimindəsiniz."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots bu vəziyyətlərdə avtomatik olaraq Ancaq-Endirmə rejiminə keçir:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Məxfi Səyahət pəncərəsində."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Köçür"
   },
+  "copyScreenshotTitle": {
+    "message": "Ekran görüntüsünü mübadilə buferinə köçür"
+  },
   "notificationImageCopiedTitle": {
     "message": "Görüntü Köçürüldü"
   },
   "notificationImageCopiedDetails": {
     "message": "Görüntünüz mübadilə buferinə köçürüldü. Yapışdırmaq üçün $META_KEY$-V basın.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Narahatlıq üçün üzr istəyirik. Gələcək buraxılışlarda bu özəllik üzərində işləyirik."
   },
   "genericErrorTitle": {
     "message": "Off! Firefox Screenshots dəli olub."
   },
   "genericErrorDetails": {
     "message": "Nə baş verdiyindən əmin deyilik. Bir daha yoxlayın və ya başqa səhifənin ekran görüntüsünü alaraq işləyib işləmədiyinə əmin olun."
   },
-  "tourBodyIntro": {
-    "message": "Firefoxu tərk etmədən ekran görüntüləri alın, saxlayın və paylaşın."
+  "tourBodyIntroServerless": {
+    "message": "Firefox səyyahınızı tərk etmədən ekran görüntülərinizi çəkin, köçürün və endirin."
   },
   "tourHeaderPageAction": {
     "message": "Saxlamağın yeni yolu"
   },
   "tourBodyPageAction": {
     "message": "Ekran görüntüsü almaq istədiyinizdə ünvan sətrindəki səhifə əməliyyatları menyusunu açın."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Səhifənin hər hansı bir hissəsini almaq üçün basın və ya sürüşdürün. Seçiminizi işıqlandırmaq üçün üzərinə gedin."
   },
   "tourHeaderFullPage": {
     "message": "Pəncərəni və ya bütün səhifəni çəkin"
   },
   "tourBodyFullPage": {
     "message": "Sadəcə pəncərədə görünən hissəni və ya bütün səhifəni çəkmək üçün sağ üstdəki düymələrdən birini seçin."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "İstədiyiniz kimi"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Kəsdiyiniz hissələri rahat paylaşmaq üçün internetdə saxlayın və ya kompüterinizə endirin. Həmçinin Ekran Görüntülərim düyməsinə basaraq çəkdiyiniz bütün ekran görüntülərini görə bilərsiz."
-  },
-  "tourHeaderAccounts": {
-    "message": "Ekran Görüntülərin həmişə yanında"
-  },
-  "tourBodyAccounts": {
-    "message": "Firefox Hesabınız ilə daxil olun və istənilən yerdə bütün alətlərinizdən görüntülərinizi görün və seçdiklərinizi daimi olaraq saxlayın."
-  },
   "tourSkip": {
     "message": "Ötür"
   },
   "tourNext": {
     "message": "Növbəti Slayd"
   },
   "tourPrevious": {
     "message": "Əvvəlki Slayd"
--- a/browser/extensions/screenshots/_locales/bg/messages.json
+++ b/browser/extensions/screenshots/_locales/bg/messages.json
@@ -27,16 +27,19 @@
     "message": "Запазване на цялата страница"
   },
   "cancelScreenshot": {
     "message": "Отказ"
   },
   "downloadScreenshot": {
     "message": "Изтегляне"
   },
+  "downloadScreenshotTitle": {
+    "message": "Изтегляне на екранна снимка"
+  },
   "downloadOnlyNotice": {
     "message": "В момента сте в режим на изтегляне."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots автоматично преминава в режим на изтегляне в следните ситуации"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "в поверителен прозорец"
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Копиране"
   },
+  "copyScreenshotTitle": {
+    "message": "Копиране в системния буфер"
+  },
   "notificationImageCopiedTitle": {
     "message": "Снимката е копирана"
   },
   "notificationImageCopiedDetails": {
     "message": "Снимката е копирана в системния буфер. За да я поставите натиснете $META_KEY$-V.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Съжаляваме за неудобството. Очаквайте тази възможност в бъдещите версии."
   },
   "genericErrorTitle": {
     "message": "Леле! Нещо се обърка с Firefox Screenshots."
   },
   "genericErrorDetails": {
     "message": "Не сме сигурни какво точно се случи. Може да опитате отново, както и да снимате друга страница."
   },
-  "tourBodyIntro": {
-    "message": "Създавайте, запазвайте и споделяйте снимки на екрана без да напускате Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Създавайте, копирайте и изтегляйте снимки на екрана без да напускате Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Нов начин за запазване"
   },
   "tourBodyPageAction": {
     "message": "Отворете менюто за действия със страницата, което се намира в адресната лента, когато желаете да направите снимка на екрана."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Щракнете с мишката или влачете, за да уловите части от страницата. А когато посочите елементи от страницата – те се осветяват."
   },
   "tourHeaderFullPage": {
     "message": "Улавяйте прозорци и цели страници"
   },
   "tourBodyFullPage": {
     "message": "Използвайте бутоните в горния десен ъгъл, за да уловите само видимата част или цялата страница."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Както ви харесва"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Запазвайте снимките на страници от Мрежата, за да ги споделяте по-лесно или ги изтегляйте на компютъра си. А бутонът „Моите снимки“ ще ви покаже всички направени от вас снимки."
-  },
-  "tourHeaderAccounts": {
-    "message": "Снимки на екрана за из път"
-  },
-  "tourBodyAccounts": {
-    "message": "Впишете се в Снимки на екрана с вашия Firefox Account, за да получите достъп до вашите снимки на всички ваши устройства и за да запазите вашите любими снимки завинаги."
-  },
   "tourSkip": {
     "message": "Пропускане"
   },
   "tourNext": {
     "message": "Напред"
   },
   "tourPrevious": {
     "message": "Назад"
--- a/browser/extensions/screenshots/_locales/bn_BD/messages.json
+++ b/browser/extensions/screenshots/_locales/bn_BD/messages.json
@@ -4,32 +4,32 @@
   },
   "addonAuthorsList": {
     "message": "Mozilla <screenshots-feedback@mozilla.com>"
   },
   "contextMenuLabel": {
     "message": "একটি স্ক্রিনশট নিন"
   },
   "myShotsLink": {
-    "message": "আমার সটসমূহ"
+    "message": "আমার সট"
   },
   "screenshotInstructions": {
-    "message": "ড্রাগ করে অথবা পেজে ক্লিক করে একটি অংশ নির্বাচন করুন। বাতিল করতে ESC টিপুন।"
+    "message": "ড্রাগ করে অথবা পাতায় ক্লিক করে একটি অংশ নির্বাচন করুন। বাতিল করতে ESC চাপুন।"
   },
   "saveScreenshotSelectedArea": {
     "message": "সংরক্ষণ"
   },
   "uploadScreenshotSelectedArea": {
     "message": "আপলোড"
   },
   "saveScreenshotVisibleArea": {
     "message": "যতটুকু দেখা যাচ্ছে সংরক্ষণ করুন"
   },
   "saveScreenshotFullPage": {
-    "message": "সম্পূর্ণ পেজ সংরক্ষণ করুন"
+    "message": "সম্পূর্ণ পাতা সংরক্ষণ করুন"
   },
   "cancelScreenshot": {
     "message": "বাতিল"
   },
   "downloadScreenshot": {
     "message": "ডাউনলোড"
   },
   "downloadScreenshotTitle": {
@@ -46,23 +46,23 @@
   },
   "downloadOnlyDetailsThirdParty": {
     "message": "তৃতীয়-পক্ষীয় কুকি নিষ্ক্রিয় আছে।"
   },
   "downloadOnlyDetailsNeverRemember": {
     "message": "“ইতিহাস কখনো মনে রাখবেন না” সক্রিয় হয়েছে।"
   },
   "downloadOnlyDetailsESR": {
-    "message": "অাপনি Firefox ESR ব্যবহার করছেন।"
+    "message": "আপনি Firefox ESR ব্যবহার করছেন।"
   },
   "downloadOnlyDetailsNoUploadPref": {
     "message": "আপলোড নিষ্ক্রিয় করা হয়েছে।"
   },
   "notificationLinkCopiedTitle": {
-    "message": "লিঙ্ক কপি করা হয়েছে"
+    "message": "লিঙ্ক অনুলিপি করা হয়েছে"
   },
   "notificationLinkCopiedDetails": {
     "message": "আপার সট এর লিংক ক্লিপবোর্ডে কপি করা হয়েছে। পেস্ট করতে $META_KEY$-V চাপুন।",
     "placeholders": {
       "meta_key": {
         "content": "$1"
       }
     }
@@ -80,17 +80,17 @@
     "message": "আপনার শট ক্লিপবোর্ডে অনুলিপি করা হয়েছে। প্রতিলেপন করতে $META_KEY$-V চাপুন।",
     "placeholders": {
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "imageCropPopupWarning": {
-    "message": "সংরক্ষিত ইমেজ $PIXELS$পিক্সেল উচ্চতায় ক্রপ করা হবে।",
+    "message": "সংরক্ষিত চিত্র $PIXELS$পিক্সেল উচ্চতায় কাটা হবে।",
     "placeholders": {
       "pixels": {
         "content": "$1"
       }
     }
   },
   "requestErrorTitle": {
     "message": "বিকল।"
@@ -103,23 +103,23 @@
   },
   "connectionErrorDetails": {
     "message": "অনুগ্রহ করে আপনার ইন্টারনেট সংযোগ পরীক্ষা করুন। আর যদি আপনার ইন্টারনেট সংযোগ ঠিক থাকে, তাহলে Firefox স্ক্রিনশট সেবাটিতে সাময়িক সমস্যা দেখা দিয়েছে।"
   },
   "loginErrorDetails": {
     "message": "আমরা আপনার শট সংরক্ষণ করতে পারিনি কারণ সেখানে Firefox স্ক্রিণশট সেবার সমস্যা আছে। অনুগ্রহ করে আবার চেস্টা করুন।"
   },
   "unshootablePageErrorTitle": {
-    "message": "আমার এই পেজের স্ক্রিনশট নিতে পারব না।"
+    "message": "আমরা এই পাতার স্ক্রিনশট নিতে পারব না।"
   },
   "unshootablePageErrorDetails": {
-    "message": "এটা কোন প্রমিত ওয়েব পেজ না, তাই আপনি এটার স্ক্রিনশট তুলতে পারবেন না।"
+    "message": "এটা কোন আদর্শ ওয়েব পাতা না, তাই আপনি এটার স্ক্রিনশট নিতে পারবেন না।"
   },
   "selfScreenshotErrorTitle": {
-    "message": "আপনি Firefox স্ক্রিনশটের পেজের শট নিতে পারেন না!"
+    "message": "আপনি Firefox Screenshots পাতার শট নিতে পারবেন না!"
   },
   "emptySelectionErrorTitle": {
     "message": "আপনি অল্প স্থান নির্বাচন করেছেন"
   },
   "privateWindowErrorTitle": {
     "message": "ব্যক্তিগত ব্রাউজিং মোডে স্ক্রিনশট নেওয়া নিস্ক্রিয় করা হয়েছে"
   },
   "privateWindowErrorDetails": {
@@ -145,17 +145,17 @@
   },
   "tourBodyClickAndDrag": {
     "message": "পাতার কিয়দংশ ক্যাপচার করতে ক্লিক করে ড্রাগ করুন। অতঃপর আপনি মাউস হোভার করে আপনার নির্বাচিত অংশ হাইলাইট করতে পারবেন।"
   },
   "tourHeaderFullPage": {
     "message": "উইন্ডো অথবা সম্পূর্ণ পাতা ক্যাপচার করুন"
   },
   "tourBodyFullPage": {
-    "message": "ইউন্ডোতে দৃশ্যমান অংশ অথবা সম্পূর্ণ পাতা ক্যাপচার করতে উপরে ডানদিকের বাটনগুলো থেকে নির্বাচন করুন।"
+    "message": "উইন্ডোতে দৃশ্যমান অংশ অথবা সম্পূর্ণ পাতা ক্যাপচার করতে উপরে ডানদিকের বোতাম থেকে নির্বাচন করুন।"
   },
   "tourSkip": {
     "message": "এড়িয়ে যান"
   },
   "tourNext": {
     "message": "পরবর্তী স্লাইড"
   },
   "tourPrevious": {
--- a/browser/extensions/screenshots/_locales/cak/messages.json
+++ b/browser/extensions/screenshots/_locales/cak/messages.json
@@ -12,16 +12,19 @@
     "message": "Taq Nuwachib'al"
   },
   "screenshotInstructions": {
     "message": "Taqirirej o tapitz'a' ri ruxaq richin nacha' ri k'ojlem. Tapitz'a' ESC richin niq'at."
   },
   "saveScreenshotSelectedArea": {
     "message": "Tiyak"
   },
+  "uploadScreenshotSelectedArea": {
+    "message": "Tijotob'äx"
+  },
   "saveScreenshotVisibleArea": {
     "message": "Tiyak wachel"
   },
   "saveScreenshotFullPage": {
     "message": "Tiyak chijun ruxaq"
   },
   "cancelScreenshot": {
     "message": "Tiq'at"
@@ -117,19 +120,16 @@
     "message": "Takuyu' chi qe ruma ri k'ayewal. Tajin niqasamajij re rub'anikil re' richin ri ch'aqa' chik taq ruwäch."
   },
   "genericErrorTitle": {
     "message": "¡Itz! Itzel xe'el ri Firefox Chapoj Wachib'äl."
   },
   "genericErrorDetails": {
     "message": "Man öj jikïl chi rij ri xk'ulwachitäj. ¿La nawajo' natojtob'ej chik o nachäp ruwachib'al jun chik ruxaq?"
   },
-  "tourBodyIntro": {
-    "message": "Ke'achapa', ke'ayaka', chuqa' ke'akomonij chapoj taq wachib'äl rik'in man yatel ta el pa Firefox."
-  },
   "tourHeaderPageAction": {
     "message": "Jun k'ak'a' rub'anikil richin niyak"
   },
   "tourBodyPageAction": {
     "message": "Tarik'a' ri ruk'utsamaj kisamaj taq ruxaq pa kik'ajtz'ik ochochib'äl xab'achike ramaj toq nawajo' nawelesaj jun chapoj wachib'äl."
   },
   "tourHeaderClickAndDrag": {
     "message": "Tachapa' ri Nawajo'"
@@ -138,22 +138,16 @@
     "message": "Tapitz'a' chuqa' taqirirej richin nacha' xa jun peraj ruxaq. Chuqa' yatikïr yaq'axaj richin nipe retal ri acha'oj."
   },
   "tourHeaderFullPage": {
     "message": "Chapoj Tzuwäch o Tz'aqät taq Ruxaq"
   },
   "tourBodyFullPage": {
     "message": "Ke'acha' ri ikim ajkiq'a' taq pitz'b'äl richin nachäp ri tz'etel ruk'ojlem tzuwäch o richin nachäp jun tz'aqät ruxaq."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Achi'el Niqa Chawäch"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Ke'ayaka' ri qupin taq awachib'al pa ajk'amaya'l richin man k'ayew ta ye'akomonij o ye'aqasaj pan akematz'ib'. Chuqa' yatikïr napïtz ri Taq Nuwachib'al richin ye'awïl konojel ri taq wachib'al e'elesan."
-  },
   "tourSkip": {
     "message": "SKIP"
   },
   "tourNext": {
     "message": "Jun chik Q'axewäch"
   },
   "tourPrevious": {
     "message": "Jun kan Q'axewäch"
deleted file mode 100644
--- a/browser/extensions/screenshots/_locales/en_GB/messages.json
+++ /dev/null
@@ -1,193 +0,0 @@
-{
-  "addonDescription": {
-    "message": "Take clips and screenshots from the Web and save them temporarily or permanently."
-  },
-  "addonAuthorsList": {
-    "message": "Mozilla <screenshots-feedback@mozilla.com>"
-  },
-  "contextMenuLabel": {
-    "message": "Take a Screenshot"
-  },
-  "myShotsLink": {
-    "message": "My Shots"
-  },
-  "screenshotInstructions": {
-    "message": "Drag or click on the page to select a region. Press ESC to cancel."
-  },
-  "saveScreenshotSelectedArea": {
-    "message": "Save"
-  },
-  "uploadScreenshotSelectedArea": {
-    "message": "Upload"
-  },
-  "saveScreenshotVisibleArea": {
-    "message": "Save visible"
-  },
-  "saveScreenshotFullPage": {
-    "message": "Save full page"
-  },
-  "cancelScreenshot": {
-    "message": "Cancel"
-  },
-  "downloadScreenshot": {
-    "message": "Download"
-  },
-  "downloadOnlyNotice": {
-    "message": "You are currently in Download-Only mode."
-  },
-  "downloadOnlyDetails": {
-    "message": "Firefox Screenshots automatically changes to Download-Only mode in these situations:"
-  },
-  "downloadOnlyDetailsPrivate": {
-    "message": "In a Private Browsing window."
-  },
-  "downloadOnlyDetailsThirdParty": {
-    "message": "Third-party cookies are disabled."
-  },
-  "downloadOnlyDetailsNeverRemember": {
-    "message": "“Never remember history” is enabled."
-  },
-  "downloadOnlyDetailsESR": {
-    "message": "You are using Firefox ESR."
-  },
-  "downloadOnlyDetailsNoUploadPref": {
-    "message": "Uploads have been disabled."
-  },
-  "notificationLinkCopiedTitle": {
-    "message": "Link Copied"
-  },
-  "notificationLinkCopiedDetails": {
-    "message": "The link to your shot has been copied to the clipboard. Press $META_KEY$-V to paste.",
-    "placeholders": {
-      "meta_key": {
-        "content": "$1"
-      }
-    }
-  },
-  "copyScreenshot": {
-    "message": "Copy"
-  },
-  "notificationImageCopiedTitle": {
-    "message": "Shot Copied"
-  },
-  "notificationImageCopiedDetails": {
-    "message": "Your shot has been copied to the clipboard. Press $META_KEY$-V to paste.",
-    "placeholders": {
-      "meta_key": {
-        "content": "$1"
-      }
-    }
-  },
-  "imageCropPopupWarning": {
-    "message": "Saved image will be cropped to $PIXELS$px in height.",
-    "placeholders": {
-      "pixels": {
-        "content": "$1"
-      }
-    }
-  },
-  "requestErrorTitle": {
-    "message": "Out of order."
-  },
-  "requestErrorDetails": {
-    "message": "Sorry! We couldn’t save your shot. Please try again later."
-  },
-  "connectionErrorTitle": {
-    "message": "We can’t connect to your screenshots."
-  },
-  "connectionErrorDetails": {
-    "message": "Please check your Internet connection. If you are able to connect to the Internet, there may be a temporary problem with the Firefox Screenshots service."
-  },
-  "loginErrorDetails": {
-    "message": "We couldn’t save your shot because there is a problem with the Firefox Screenshots service. Please try again later."
-  },
-  "unshootablePageErrorTitle": {
-    "message": "We can’t screenshot this page."
-  },
-  "unshootablePageErrorDetails": {
-    "message": "This isn’t a standard Web page, so you can’t take a screenshot of it."
-  },
-  "selfScreenshotErrorTitle": {
-    "message": "You can’t take a shot of a Firefox Screenshots page!"
-  },
-  "emptySelectionErrorTitle": {
-    "message": "Your selection is too small"
-  },
-  "privateWindowErrorTitle": {
-    "message": "Screenshots is disabled in Private Browsing Mode"
-  },
-  "privateWindowErrorDetails": {
-    "message": "Sorry for the inconvenience. We are working on this feature for future releases."
-  },
-  "genericErrorTitle": {
-    "message": "Whoa! Firefox Screenshots went haywire."
-  },
-  "genericErrorDetails": {
-    "message": "We’re not sure what just happened. Care to try again or take a shot of a different page?"
-  },
-  "tourBodyIntro": {
-    "message": "Take, save, and share screenshots without leaving Firefox."
-  },
-  "tourHeaderPageAction": {
-    "message": "A new way to save"
-  },
-  "tourBodyPageAction": {
-    "message": "Expand the page actions menu in the address bar any time you want to take a screenshot."
-  },
-  "tourHeaderClickAndDrag": {
-    "message": "Capture Just What You Want"
-  },
-  "tourBodyClickAndDrag": {
-    "message": "Click and drag to capture just a portion of a page. You can also hover to highlight your selection."
-  },
-  "tourHeaderFullPage": {
-    "message": "Capture Windows or Entire Pages"
-  },
-  "tourBodyFullPage": {
-    "message": "Select the buttons in the upper right to capture the visible area in the window or to capture an entire page."
-  },
-  "tourHeaderDownloadUpload": {
-    "message": "As You Like It"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Save your cropped shots to the web for easier sharing, or download them to your computer. You also can click on the My Shots button to find all the shots you’ve taken."
-  },
-  "tourHeaderAccounts": {
-    "message": "Screenshots to Go"
-  },
-  "tourBodyAccounts": {
-    "message": "Sign in with your Firefox Account to access your shots on all of your devices and save your favourite shots forever."
-  },
-  "tourSkip": {
-    "message": "SKIP"
-  },
-  "tourNext": {
-    "message": "Next Slide"
-  },
-  "tourPrevious": {
-    "message": "Previous Slide"
-  },
-  "tourDone": {
-    "message": "Done"
-  },
-  "termsAndPrivacyNotice2": {
-    "message": "By using Firefox Screenshots, you agree to our $TERMSANDPRIVACYNOTICETERMSLINK$ and $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
-    "placeholders": {
-      "termsandprivacynoticetermslink": {
-        "content": "$1"
-      },
-      "termsandprivacynoticeprivacylink": {
-        "content": "$2"
-      }
-    }
-  },
-  "termsAndPrivacyNoticeTermsLink": {
-    "message": "Terms"
-  },
-  "termsAndPrivacyNoticyPrivacyLink": {
-    "message": "Privacy Notice"
-  },
-  "libraryLabel": {
-    "message": "Screenshots"
-  }
-}
\ No newline at end of file
--- a/browser/extensions/screenshots/_locales/es_CL/messages.json
+++ b/browser/extensions/screenshots/_locales/es_CL/messages.json
@@ -65,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Copiar"
   },
+  "copyScreenshotTitle": {
+    "message": "Copiar captura de pantalla al portapapeles"
+  },
   "notificationImageCopiedTitle": {
     "message": "Captura copiada"
   },
   "notificationImageCopiedDetails": {
     "message": "Tu captura ha sido copiada al portapapeles. Presiona $META_KEY$-V para pegarla.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -123,16 +126,19 @@
     "message": "Disculpa las molestias. Estamos trabajando en esta función para una futura versión."
   },
   "genericErrorTitle": {
     "message": "¡Guau! Firefox Screenshots se copetió."
   },
   "genericErrorDetails": {
     "message": "No estamos seguros de lo que sucedió. ¿Te importaría volver a intentarlo o tomar una captura de una página diferente?"
   },
+  "tourBodyIntroServerless": {
+    "message": "Toma, copia y descarga capturas de pantalla sin salir de Firefox."
+  },
   "tourHeaderPageAction": {
     "message": "Una nueva forma de guardar"
   },
   "tourBodyPageAction": {
     "message": "Expande el menú de acciones de página en la barra de direcciones en cualquier momento en que quieras tomar una captura."
   },
   "tourHeaderClickAndDrag": {
     "message": "Captura lo que necesitas"
--- a/browser/extensions/screenshots/_locales/es_MX/messages.json
+++ b/browser/extensions/screenshots/_locales/es_MX/messages.json
@@ -27,16 +27,19 @@
     "message": "Guardar página completa"
   },
   "cancelScreenshot": {
     "message": "Cancelar"
   },
   "downloadScreenshot": {
     "message": "Descarga"
   },
+  "downloadScreenshotTitle": {
+    "message": "Descargar captura de pantalla"
+  },
   "downloadOnlyNotice": {
     "message": "Estás en modo de Solo-Descargas."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots automáticamente cambia al modo Solo-Descarga en estas situaciones:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "En una ventana de navegación privada."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Copiar"
   },
+  "copyScreenshotTitle": {
+    "message": "Copiar la captura de pantalla al portapapeles"
+  },
   "notificationImageCopiedTitle": {
     "message": "Captura copiada"
   },
   "notificationImageCopiedDetails": {
     "message": "Tu captura ha sido copiada al portapapeles. Presiona $META_KEY$-V para pegar.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Disculpen las molestias. Estamos trabajando en esta característica para las versiones futuras."
   },
   "genericErrorTitle": {
     "message": "¡Oye! Las capturas de pantalla de Firefox salieron mal."
   },
   "genericErrorDetails": {
     "message": "No estamos seguros qué pasó. ¿Te importaría intentarlo de nuevo o tomar una captura de una página diferente?"
   },
-  "tourBodyIntro": {
-    "message": "Toma, guarda y comparte capturas de pantalla sin dejar Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Toma, copia y descarga capturas de pantalla sin salir de Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Una nueva forma de guardar"
   },
   "tourBodyPageAction": {
     "message": "Expande el menú de acciones de la página en la barra de direcciones en cualquier momento que quieras tomar una captura de pantalla."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Haz clic y arrastra para capturas sólo una parte de la página. También puedes desplazarte para resaltar tu selección."
   },
   "tourHeaderFullPage": {
     "message": "Captura ventanas o páginas enteras"
   },
   "tourBodyFullPage": {
     "message": "Selecciona los botones en la parte superior derecha para capturar el área visible en la ventana o para capturar una página completa."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Como te gusta"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Guarda tus capturas recortadas en la Web para compartirlas más fácilmente o descárgalas en tu computadora. También puedes hacer clic en el botón Mis Capturas para encontrar todas las fotos que has tomado."
-  },
-  "tourHeaderAccounts": {
-    "message": "Capturas en todos los dispositivos"
-  },
-  "tourBodyAccounts": {
-    "message": "Inicia sesión con tu cuenta de Firefox para acceder a tus capturas en todos tus dispositivos y guarda tus capturas favoritas para siempre."
-  },
   "tourSkip": {
     "message": "Ignorar"
   },
   "tourNext": {
     "message": "Siguiente diapositiva"
   },
   "tourPrevious": {
     "message": "Diapositiva anterior"
--- a/browser/extensions/screenshots/_locales/fa/messages.json
+++ b/browser/extensions/screenshots/_locales/fa/messages.json
@@ -27,16 +27,19 @@
     "message": "ذخیره صفحه کامل"
   },
   "cancelScreenshot": {
     "message": "لغو"
   },
   "downloadScreenshot": {
     "message": "دریافت"
   },
+  "downloadScreenshotTitle": {
+    "message": "دریافت تصاویرگرفته شده از صفحه"
+  },
   "downloadOnlyNotice": {
     "message": "شما در حال حاضر در حالت فقط-دریافت هستید."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots در خصوص چنین موقعیتی به صورت خودکارحالت را به تنها دریافت تبدیل می‌کند:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "در پنجره‌های مرور ناشناس."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "رونوشت"
   },
+  "copyScreenshotTitle": {
+    "message": "برداشت تصاویرگرفته شده از صفحه به کلیپ بورد"
+  },
   "notificationImageCopiedTitle": {
     "message": "رونوشت تصویر تهیه شد"
   },
   "notificationImageCopiedDetails": {
     "message": "عکس شما در کلیپ‌بورد رونوشت شد. $META_KEY$-V را برای جای‌گذاری فشار دهید.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "به خاطر مزاحمت متاسفیم. ما در حال کار روی این ویژگی برای انتشار‌های آینده هستیم."
   },
   "genericErrorTitle": {
     "message": "اوه! سرویس تصاویر صفحه فایرفاکس قاطی کرده."
   },
   "genericErrorDetails": {
     "message": "مطمئن نیستیم چه اتفاقی افتاده است. می‌خواهید دوباره امتحان کنید یا از یک صفحهٔ دیگر عکس بگیرید؟"
   },
-  "tourBodyIntro": {
-    "message": "بدون خارج شدن از فایرفاکس، عکس بگیرید، ذخیره کنید و به اشتراک بگذارید."
+  "tourBodyIntroServerless": {
+    "message": "گرفتن،‌ برداشت و دریافت تصاویر گرفته شده از صفحه بدون ترک کردن فایرفاکس."
   },
   "tourHeaderPageAction": {
     "message": "روش جدیدی برای ذخیره کردن"
   },
   "tourBodyPageAction": {
     "message": "بازکردن صفحه اقدامات فهرست در آدرس بار هر زمانی که شما تمایل داشته باشید از صفحه عکس بگیرید."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "کلیک کنید و بکشید تا فقط از قسمتی از صفحه عکس بگیرید. می‌توانید برای برجسته کردن روی ناحیه انتخاب شده حرکت کنید."
   },
   "tourHeaderFullPage": {
     "message": "ضبط پنجره یا کل صفحه‌ها"
   },
   "tourBodyFullPage": {
     "message": "برای گرفتن عکس از ناحیه قابل مشاهده در پنجره یا تمام صفحه از دکمه‌های بالا سمت راست استفاده کنید."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "همانطور که می‌پسندید"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "عکس‌های بریده شده خود را برای به اشتراک‌گذاری راحت‌تر روی وب ذخیره کنید، یا آن‌ها را روی رایانه خود دریافت کنید. همچنین برای دیدن همهٔ عکس‌هایی که گرفتید می‌توانید روی دکمه «عکس‌های من» کلیک کنید."
-  },
-  "tourHeaderAccounts": {
-    "message": "عکس از صفحه بلافاصله آماده برای استفاده"
-  },
-  "tourBodyAccounts": {
-    "message": "وارد حساب فایرفاکس خودتون بشید و به تمام تصاویر که توسط دستگاه‌های خودتون گرفتید دسترسی داشته باشید و تصویر مورد علاقه خودتون را برای همیشه ذخیره کنید."
-  },
   "tourSkip": {
     "message": "رد کردن"
   },
   "tourNext": {
     "message": "اسلاید بعدی"
   },
   "tourPrevious": {
     "message": "اسلاید قبلی"
--- a/browser/extensions/screenshots/_locales/fi/messages.json
+++ b/browser/extensions/screenshots/_locales/fi/messages.json
@@ -126,16 +126,19 @@
     "message": "Anteeksi häiriö. Tämä ominaisuus on vielä työn alla."
   },
   "genericErrorTitle": {
     "message": "Oho! Firefox Screenshots meni päin prinkkalaa."
   },
   "genericErrorDetails": {
     "message": "Emme oikein tiedä, mitä tapahtui. Haluatko yrittää uudestaan tai ottaa kuvan eri sivusta?"
   },
+  "tourBodyIntroServerless": {
+    "message": "Ota, kopioi ja lataa kuvakaappauksia poistumatta Firefoxista."
+  },
   "tourHeaderPageAction": {
     "message": "Uusi tapa tallentaa"
   },
   "tourBodyPageAction": {
     "message": "Avaa osoitepalkissa oleva Sivun toiminnot -valikko milloin vain, kun haluat ottaa kuvakaappauksen."
   },
   "tourHeaderClickAndDrag": {
     "message": "Kaappaa mitä haluat"
--- a/browser/extensions/screenshots/_locales/fr/messages.json
+++ b/browser/extensions/screenshots/_locales/fr/messages.json
@@ -27,16 +27,19 @@
     "message": "Capturer la page complète"
   },
   "cancelScreenshot": {
     "message": "Annuler"
   },
   "downloadScreenshot": {
     "message": "Télécharger"
   },
+  "downloadScreenshotTitle": {
+    "message": "Télécharger la capture d’écran"
+  },
   "downloadOnlyNotice": {
     "message": "Vous êtes actuellement dans un mode ne permettant que le téléchargement."
   },
   "downloadOnlyDetails": {
     "message": "Dans les situations suivantes, Firefox Screenshots permet uniquement les téléchargements :"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "lorsque vous naviguez en navigation privée."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Copier"
   },
+  "copyScreenshotTitle": {
+    "message": "Copier la capture d’écran dans le presse-papiers"
+  },
   "notificationImageCopiedTitle": {
     "message": "Capture copiée"
   },
   "notificationImageCopiedDetails": {
     "message": "Votre capture a été copiée dans le presse-papiers. Appuyez sur $META_KEY$-V pour la coller.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Désolé pour la gêne occasionnée. Nous travaillons sur cette fonctionnalité pour de prochaines versions."
   },
   "genericErrorTitle": {
     "message": "Firefox Screenshots semble avoir un problème."
   },
   "genericErrorDetails": {
     "message": "Un problème non identifié est survenu. Vous pouvez réessayer ou effectuer une capture d’écran d’une autre page."
   },
-  "tourBodyIntro": {
-    "message": "Effectuez des captures d’écran, enregistrez et partagez-les sans quitter Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Prenez, copiez et téléchargez des captures d’écran sans quitter Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Une nouvelle façon d’enregistrer ses captures"
   },
   "tourBodyPageAction": {
     "message": "Dès que vous voulez effectuer une capture d’écran, il vous suffit d’ouvrir le menu d’actions de la page, depuis la barre d’adresse."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Cliquez et glissez pour capturer seulement une partie de la page. Vous pouvez aussi survoler une zone avec votre curseur pour surligner votre sélection."
   },
   "tourHeaderFullPage": {
     "message": "Effectuez des captures d’écran de fenêtres ou de pages entières"
   },
   "tourBodyFullPage": {
     "message": "Utilisez les boutons en haut à droite pour capturer au choix la zone visible dans la fenêtre ou la page entière."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "À votre guise"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Sauvegardez en ligne vos captures recadrées pour les partager plus facilement, ou téléchargez-les sur votre ordinateur. Vous pouvez aussi cliquer sur « Mes captures d’écran » pour retrouver toutes vos captures."
-  },
-  "tourHeaderAccounts": {
-    "message": "Captures à emporter"
-  },
-  "tourBodyAccounts": {
-    "message": "Connectez-vous avec votre compte Firefox pour accéder à vos captures sur tous vos appareils et enregistrer définitivement vos préférées."
-  },
   "tourSkip": {
     "message": "IGNORER"
   },
   "tourNext": {
     "message": "Écran suivant"
   },
   "tourPrevious": {
     "message": "Écran précédent"
--- a/browser/extensions/screenshots/_locales/gd/messages.json
+++ b/browser/extensions/screenshots/_locales/gd/messages.json
@@ -27,16 +27,19 @@
     "message": "Sàbhail an duilleag shlàn"
   },
   "cancelScreenshot": {
     "message": "Sguir dheth"
   },
   "downloadScreenshot": {
     "message": "Luchdaich a-nuas"
   },
+  "downloadScreenshotTitle": {
+    "message": "Luchdaich a-nuas an glacadh-sgrìn"
+  },
   "downloadOnlyNotice": {
     "message": "Tha thu sa mhodh luchdaidh a-nuas a-mhàin."
   },
   "downloadOnlyDetails": {
     "message": "Bidh gleus glacaidhean-sgrìn Firefox sa mhodh luchdaidh a-nuas gu fèin-obrachail sna suidheachaidhean a leanas:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Ann an uinneag brabhsaidh phrìobhaidich."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Dèan lethbhreac"
   },
+  "copyScreenshotTitle": {
+    "message": "Cuir lethbhreac dhen ghlacadh-sgrìn air an stòr-bhòrd"
+  },
   "notificationImageCopiedTitle": {
     "message": "Chaidh lethbhreac a dhèanamh dhen ghlacadh"
   },
   "notificationImageCopiedDetails": {
     "message": "Chaidh lethbhreac dhen ghlacadh agad a chur air an stòr-bhòrd. Brùth $META_KEY$-V airson a chur ann.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Tha sinn duilich mu dhèidhinn. Tha sinn ag obair air agus an dòchas gum bi e ri làimh a dh’aithghearr."
   },
   "genericErrorTitle": {
     "message": "Ìoc! Sin glacaidhean-sgrìn Firefox air feadh na fìdhle."
   },
   "genericErrorDetails": {
     "message": "Chan eil sinn cinnteach dè thachair. A bheil thu airson feuchainn ris a-rithist no glacadh a thogail de dhuilleag eile?"
   },
-  "tourBodyIntro": {
-    "message": "Tog, sàbhail is co-roinn glacadh-sgrìn gun Firefix fhàgail."
+  "tourBodyIntroServerless": {
+    "message": "Tog glacaidhean-sgrìn, dèan lethbhreac dhiubh is luchdaich a-nuas iad gun Firefox fhàgail."
   },
   "tourHeaderPageAction": {
     "message": "Dòigh ùr airson sàbhaladh"
   },
   "tourBodyPageAction": {
     "message": "Leudaich clàr-taice gnìomhan na duilleige ann am bàr an t-seòlaidh uair sam bith a tha thu airson glacadh-sgrìn a thogail."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Dèan briogadh is slaodadh airson earrann de dhuilleag a ghlacadh. ’S urrainn dhut fantainn os cionn rud cuideachd airson na thagh thu a shoillseachadh."
   },
   "tourHeaderFullPage": {
     "message": "Glac uinneagan no duilleagan slàna"
   },
   "tourBodyFullPage": {
     "message": "Tagh na putanan air an taobh deas gu h-àrd airson na tha ri fhaicinn san uinneag a ghlacadh no airson duilleag shlàn a ghlacadh."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Do thoil fhèin"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Sàbhail na glacaidhean bearrte air an lìon ach am bi e furasta an co-roinneadh no luchdaich a-nuas iad dhan choimpiutair agad. ’S urrainn dhut briogadh air a’ phutan “Na glacaidhean agam” cuideachd is chì thu gach glacadh a thog thu."
-  },
-  "tourHeaderAccounts": {
-    "message": "Thoir leat glacadh"
-  },
-  "tourBodyAccounts": {
-    "message": "Clàraich a-steach leis a’ chunntas Firefox agad a dh’fhaighinn greim air na glacaidhean uile agad air feadh nan uidheaman agad is sàbhail an fheadhainn chudromach gu buan."
-  },
   "tourSkip": {
     "message": "LEUM SEACHAD"
   },
   "tourNext": {
     "message": "An ath-shleamhnag"
   },
   "tourPrevious": {
     "message": "An t-sleamhnag roimhe"
--- a/browser/extensions/screenshots/_locales/gu_IN/messages.json
+++ b/browser/extensions/screenshots/_locales/gu_IN/messages.json
@@ -27,16 +27,19 @@
     "message": "સંપૂર્ણ પૃષ્ઠ સાચવો"
   },
   "cancelScreenshot": {
     "message": "રદ"
   },
   "downloadScreenshot": {
     "message": "ડાઉનલોડ"
   },
+  "downloadScreenshotTitle": {
+    "message": "સ્ક્રીનશૉટ ડાઉનલોડ કરો"
+  },
   "downloadOnlyNotice": {
     "message": "તમે હાલમા ફક્ત ડાઉનલોડ-કરો પ્રકારમાં છો."
   },
   "downloadOnlyDetails": {
     "message": "આ પરિસ્થિતિઓ માં Firefox સ્ક્રિનશોટસ આપમેળે જ ફક્ત-ડાઉનલોડ પ્રકારમાં જતું રહેશે:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "ખાનગી બ્રાઉઝિંગ વિન્ડો માં."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "નકલ કરો"
   },
+  "copyScreenshotTitle": {
+    "message": "ક્લિપબોર્ડ પર સ્ક્રીનશોટ કૉપિ કરો"
+  },
   "notificationImageCopiedTitle": {
     "message": "શોટ નકલ કર્યો"
   },
   "notificationImageCopiedDetails": {
     "message": "તમારા શોટ ક્લિપબોર્ડ પર નકલ કરવામાં આવ્યાં છે. પેસ્ટ કરવા માટે $META_KEY$-V દબાવો.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "અસુવીધી બદલ માફી. અમે ભવિષ્યના પ્રકાશનો માટે આ સુવિધા પર કામ કરી રહ્યા છીએ."
   },
   "genericErrorTitle": {
     "message": "થોભો! Firefox સ્ક્રીનશોટ્સ અવ્યવસ્થિત થઈ ગયા."
   },
   "genericErrorDetails": {
     "message": "અમે ખાતરી નથીકે શું માત્ર થયું છે . ફરી પ્રયાસ કરો અથવા એક અલગ પૃષ્ઠ એક શોટ લેવા માટે કાળજી કરો?"
   },
-  "tourBodyIntro": {
-    "message": "લેવા, સાચવેલા, અને વહેંચાયેલ સ્ક્રીનશૉટ્સ Firefox છોડ્યાં વિના."
+  "tourBodyIntroServerless": {
+    "message": "Firefox છોડ્યાં વિના સ્ક્રીનશોટ લો, કૉપિ કરો અને ડાઉનલોડ કરો."
   },
   "tourHeaderPageAction": {
     "message": "સાચવવાનો એક નવો રસ્તો"
   },
   "tourBodyPageAction": {
     "message": "જ્યારે પણ સ્ક્રીનશૉટ લેવા માંગો ત્યારે સરનામાં બારમાં પૃષ્ઠ ક્રિયાઓ મેનૂને વિસ્તૃત કરો."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "પાનાંના માત્ર એક ભાગ મેળવવા માટે ક્લિક કરો અને ખેંચો. તમે પણ તમારી પસંદગી પ્રકાશિત કરવા માટે હૉવર કરી શકો છો."
   },
   "tourHeaderFullPage": {
     "message": "વિન્ડોઝ અથવા સમગ્ર પાના કેદ કરો"
   },
   "tourBodyFullPage": {
     "message": "ઉપર જમણા બટનો પસંદ કરો વિન્ડોમાં દૃશ્યમાન વિસ્તાર મેળવવા માટે અથવા આખુ પાનું કેપ્ચર કરવા માટે."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "તમને જે ગમે"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "સરળ શેરિંગ માટે વેબ પર તમારા કપાઈ શોટ સાચવો, અથવા તેમને તમારા કમ્પ્યુટર પર ડાઉનલોડ કરો. તમે બધા શોટ મેળવવા માટે મારું શોટ્સ બટન પર ક્લિક કરી પણ શકો છો બધા શોટ તમે લીધેલા શોધવા માટે."
-  },
-  "tourHeaderAccounts": {
-    "message": "સ્ક્રીનશોટસ ઉપયોગ માટે તૈયાર છે"
-  },
-  "tourBodyAccounts": {
-    "message": "તમારા બધા ઉપકરણો પર તમારા શોટ્સને ઍક્સેસ કરવા માટે અને તમારા મનપસંદ શોટ્સ કાયમ માટે સાચવવા માટે તમારા Firefox એકાઉન્ટથી સાઇન ઇન કરો."
-  },
   "tourSkip": {
     "message": "છોડવા"
   },
   "tourNext": {
     "message": "આગલી સ્લાઇડ"
   },
   "tourPrevious": {
     "message": "પહેલાની સ્લાઇડ"
--- a/browser/extensions/screenshots/_locales/ia/messages.json
+++ b/browser/extensions/screenshots/_locales/ia/messages.json
@@ -37,17 +37,17 @@
   },
   "downloadOnlyNotice": {
     "message": "Tu es actualmente in modo solo-discargamento."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots automaticamente se converte al modo de solo discargamento in le situationes sequente:"
   },
   "downloadOnlyDetailsPrivate": {
-    "message": "In un fenestra de Navigation Private."
+    "message": "In un Fenestra de navigation private."
   },
   "downloadOnlyDetailsThirdParty": {
     "message": "Cookies de tertie parte disactivate."
   },
   "downloadOnlyDetailsNeverRemember": {
     "message": "“Oblidar le chronologia” activate."
   },
   "downloadOnlyDetailsESR": {
@@ -115,20 +115,20 @@
   },
   "selfScreenshotErrorTitle": {
     "message": "Tu non pote prender un instantaneo de un pagina de Firefox Screenshots!"
   },
   "emptySelectionErrorTitle": {
     "message": "Tu selection es troppo micre"
   },
   "privateWindowErrorTitle": {
-    "message": "Le instantaneos es disactivate durante le navigation private"
+    "message": "Le instantaneos es disactivate durante le Navigation private"
   },
   "privateWindowErrorDetails": {
-    "message": "Pardono pro le incommoditate. Nos labora sur iste functionalitate pro futur publicationes."
+    "message": "Pardono pro le incommoditate. Nos labora sur iste functionalitate pro futur editiones."
   },
   "genericErrorTitle": {
     "message": "Problemas de Firefox Screenshots!"
   },
   "genericErrorDetails": {
     "message": "Nos non sape lo que occurreva. Reprobar o capturar un instantaneo de un altere pagina?"
   },
   "tourBodyIntroServerless": {
--- a/browser/extensions/screenshots/_locales/id/messages.json
+++ b/browser/extensions/screenshots/_locales/id/messages.json
@@ -27,16 +27,19 @@
     "message": "Simpan laman sepenuhnya"
   },
   "cancelScreenshot": {
     "message": "Batal"
   },
   "downloadScreenshot": {
     "message": "Unduh"
   },
+  "downloadScreenshotTitle": {
+    "message": "Unduh tangkapan layar"
+  },
   "downloadOnlyNotice": {
     "message": "Anda saat ini berada di mode Hanya-Unduh."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots secara otomatis berganti ke mode Hanya-Unduh pada situasi berikut:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Di jendela Penjelajahan Pribadi."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Salin"
   },
+  "copyScreenshotTitle": {
+    "message": "Salin tangkapan layar ke papan klip"
+  },
   "notificationImageCopiedTitle": {
     "message": "Tangkapan Disalin"
   },
   "notificationImageCopiedDetails": {
     "message": "Tangkapan Anda telah disalin ke papan klip. Tekan $META_KEY$-V untuk menempelkan.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Maaf atas ketidaknyamanannya. Kami sedang mengerjakan fitur ini untuk peluncuran masa mendatang."
   },
   "genericErrorTitle": {
     "message": "Wah! Firefox Screenshots mendadak kacau."
   },
   "genericErrorDetails": {
     "message": "Kami tidak yakin akan apa yang terjadi. Ingin mencoba lagi atau merekam gambar dari laman yang berbeda?"
   },
-  "tourBodyIntro": {
-    "message": "Ambil, simpan, dan bagikan tangkapan layar tanpa meninggalkan Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Ambil, salin, dan unduh tangkapan layar tanpa meninggalkan Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Cara baru untuk menyimpan"
   },
   "tourBodyPageAction": {
     "message": "Bentangkan menu tindakan laman di bilah alamat setiap kali Anda ingin buat tangkapan layar."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Klik dan seret untuk merekam sebagian area laman. Anda juga dapat menggeser kursor untuk menyoroti pilihan Anda."
   },
   "tourHeaderFullPage": {
     "message": "Rekam Jendela atau Seluruh Laman"
   },
   "tourBodyFullPage": {
     "message": "Pilih tombol di kanan atas untuk merekam area yang terlihat pada jendela atau rekam seluruh laman."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Sesuka Anda"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Simpan potongan tangkapan Anda ke Web agar mudah dibagikan, atau unduh ke komputer. Anda pun dapat mengeklik pada tombol Gambar Saya untuk menemukan semua tangkapan yang pernah Anda rekam."
-  },
-  "tourHeaderAccounts": {
-    "message": "Screenshots to Go"
-  },
-  "tourBodyAccounts": {
-    "message": "Masuk dengan Firefox Account untuk mengakses tangkapan Anda di semua peranti Anda dan menyimpan tangkapan favorit Anda selamanya."
-  },
   "tourSkip": {
     "message": "LEWATI"
   },
   "tourNext": {
     "message": "Salindia Selanjutnya"
   },
   "tourPrevious": {
     "message": "Salindia Sebelumnya"
--- a/browser/extensions/screenshots/_locales/ja/messages.json
+++ b/browser/extensions/screenshots/_locales/ja/messages.json
@@ -27,16 +27,19 @@
     "message": "ページ全体を保存"
   },
   "cancelScreenshot": {
     "message": "キャンセル"
   },
   "downloadScreenshot": {
     "message": "ダウンロード"
   },
+  "downloadScreenshotTitle": {
+    "message": "スクリーンショットをダウンロード"
+  },
   "downloadOnlyNotice": {
     "message": "ダウンロード専用モードが有効になっています。"
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots は以下のような状況では自動的にダウンロード専用モードへ切り替わります。"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "プライベートブラウジングを使用している場合。"
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "コピー"
   },
+  "copyScreenshotTitle": {
+    "message": "スクリーンショットをクリップボードにコピー"
+  },
   "notificationImageCopiedTitle": {
     "message": "ショットをコピーしました"
   },
   "notificationImageCopiedDetails": {
     "message": "ショットがクリップボードへコピーされました。$META_KEY$+V キーで貼り付けられます。",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "ご不便をおかけして申し訳ありません。今後のリリースでこの機能を提供できるよう取り組んでいます。"
   },
   "genericErrorTitle": {
     "message": "Firefox Screenshots に問題が発生しました。"
   },
   "genericErrorDetails": {
     "message": "何か問題が発生したようです。再度試すか、別のページのショットを撮ってみてください。"
   },
-  "tourBodyIntro": {
-    "message": "Firefox を離れることなく、スクリーンショットを撮影、保存、共有。"
+  "tourBodyIntroServerless": {
+    "message": "Firefox だけでスクリーンショットの撮影、コピー、ダウンロードができます。"
   },
   "tourHeaderPageAction": {
     "message": "新たな保存方法"
   },
   "tourBodyPageAction": {
     "message": "スクリーンショットを撮りたいときは、いつでもアドレスバー内のページアクションメニューを開いてください。"
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "クリック&ドラッグでページの一部だけをキャプチャできます。また、マウスを当てれば選択範囲が強調表示されます。"
   },
   "tourHeaderFullPage": {
     "message": "ウィンドウもしくはページ全体をキャプチャ"
   },
   "tourBodyFullPage": {
     "message": "右上のボタンを選択して、ウィンドウ内の表示範囲もしくはページ全体をキャプチャしましょう。"
   },
-  "tourHeaderDownloadUpload": {
-    "message": "お好きなように"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "切り取ったショットを簡単に共有できるようウェブ上に保存したり、手元へダウンロードしたり。また「自分のショット」ボタンをクリックすれば、これまでに撮ったすべてのショットを見られます。"
-  },
-  "tourHeaderAccounts": {
-    "message": "Screenshots to Go"
-  },
-  "tourBodyAccounts": {
-    "message": "Firefox アカウントでログインすれば、お持ちのすべての端末からショットにアクセスでき、お気に入りのショットを保存しておけます。"
-  },
   "tourSkip": {
     "message": "スキップ"
   },
   "tourNext": {
     "message": "次のスライド"
   },
   "tourPrevious": {
     "message": "前のスライド"
--- a/browser/extensions/screenshots/_locales/ka/messages.json
+++ b/browser/extensions/screenshots/_locales/ka/messages.json
@@ -27,30 +27,33 @@
     "message": "მთლიანი გვერდის შენახვა"
   },
   "cancelScreenshot": {
     "message": "გაუქმება"
   },
   "downloadScreenshot": {
     "message": "ჩამოტვირთვა"
   },
+  "downloadScreenshotTitle": {
+    "message": "ეკრანის სურათის ჩამოტვირთვა"
+  },
   "downloadOnlyNotice": {
     "message": "თქვენ ახლა იმყოფებით „მხოლოდ ჩამოტვირთვის“ რეჟიმში."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots გადადის „მხოლოდ ჩამოტვირთვის“ რეჟიმზე, შემდეგ შემთხვევებში:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "პირადი ფანჯრით სარგებლობისას."
   },
   "downloadOnlyDetailsThirdParty": {
     "message": "მესამე მხარის ფუნთუშების შენახვა, შეზღუდულია."
   },
   "downloadOnlyDetailsNeverRemember": {
-    "message": "მითითებულია, რომ “არასოდეს დაიმახსოვრებს ისტორიას” ბრაუზერი."
+    "message": "მითითებულია, რომ ბრაუზერი „არასოდეს დაიმახსოვრებს ისტორიას“."
   },
   "downloadOnlyDetailsESR": {
     "message": "თქვენ იყენებთ Firefox ESR-ს."
   },
   "downloadOnlyDetailsNoUploadPref": {
     "message": "ატვირთვა შეზღუდულია."
   },
   "notificationLinkCopiedTitle": {
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "ასლი"
   },
+  "copyScreenshotTitle": {
+    "message": "სურათის ასლის აღება"
+  },
   "notificationImageCopiedTitle": {
     "message": "სურათის ასლი მზადაა"
   },
   "notificationImageCopiedDetails": {
     "message": "თქვენი სურათის ასლი მზადაა. ჩასმისთვის დააწექით $META_KEY$-V.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "ბოდიშს გიხდით გაუგებრობის გამო. ჩვენ ვმუშაობთ ამ შესაძლებლობის დამატებაზე, სამომავლო ვერსიებში."
   },
   "genericErrorTitle": {
     "message": "ვაი! Firefox Screenshots მწყობრიდან გამოვიდა."
   },
   "genericErrorDetails": {
     "message": "გაუგებარია რა მოხდა. ისევ ცდით ხელახლა, თუ სხვა ვებგვერდს გადაუღებთ სურათს?"
   },
-  "tourBodyIntro": {
-    "message": "გადაიღეთ, შეინახეთ და გააზიარეთ ეკრანის სურათები Firefox-იდან გაუსვლელად."
+  "tourBodyIntroServerless": {
+    "message": "გადაიღეთ, გააკეთეთ ასლი და ჩამოტვირთეთ ეკრანის სურათები Firefox-იდან გაუსვლელად."
   },
   "tourHeaderPageAction": {
     "message": "შენახვის ახალი ხერხი"
   },
   "tourBodyPageAction": {
     "message": "როცა მოგესურვებათ ეკრანისთვის სურათის გადაღება, ჩამოშალეთ გვერდზე მოქმედებების მენიუ, რომელიც მდებარეობს მისამართების ველში."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "გადაადგილეთ ან დააწკაპეთ გვერდზე გადასაღები სივრცის შესარჩევად. ასევე, მაჩვენებელი ისრის გადატარებით შეგიძლიათ მონიშნოთ სასურველი არე."
   },
   "tourHeaderFullPage": {
     "message": "გადაუღეთ სურათები ფანჯრებს ან მთლიან ვებგვერდებს"
   },
   "tourBodyFullPage": {
     "message": "მარჯვენა ზედა კუთხეში არსებული ღილაკების საშუალებით, შეგიძლიათ გადაუღოთ სურათი ხილულ ნაწილს ან მთლიან გვერდს."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "როგორც გენებოთ"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "ამოჭრილი სურათები შეგიძლიათ განათავსოთ ინტერნეტში, მარტივად გასაზიარებლად, ან ჩამოტვირთოთ კომპიუტერში. ასევე, „ჩემი გადაღებულების“ ღილაკზე დაწკაპებით, იხილავთ თქვენ მიერ გადაღებულ ყველა სურათს."
-  },
-  "tourHeaderAccounts": {
-    "message": "თან წაიყოლეთ Screenshots"
-  },
-  "tourBodyAccounts": {
-    "message": "შედით Firefox-ანგარიშზე თქვენს გადაღებულ სურათებთან წვდომის მისაღებად ყველა თქვენი მოწყობილობიდან და სასურველი სურათების სამუდამოდ შესანახად."
-  },
   "tourSkip": {
     "message": "გამოტოვება"
   },
   "tourNext": {
     "message": "შემდეგი"
   },
   "tourPrevious": {
     "message": "წინა"
--- a/browser/extensions/screenshots/_locales/kab/messages.json
+++ b/browser/extensions/screenshots/_locales/kab/messages.json
@@ -27,16 +27,19 @@
     "message": "Sekles asebter meṛṛa"
   },
   "cancelScreenshot": {
     "message": "Sefsex"
   },
   "downloadScreenshot": {
     "message": "Sider"
   },
+  "downloadScreenshotTitle": {
+    "message": "Sider tuṭṭfa n ugdil"
+  },
   "downloadOnlyNotice": {
     "message": "Aql-ak tura deg umskar n usider kan."
   },
   "downloadOnlyDetails": {
     "message": "Deg isekaren-agi, Firefox Screenshots ad k-yeǧǧ kan ad tsidreḍ:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Deg iccer n tungin uslig."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Nγel"
   },
+  "copyScreenshotTitle": {
+    "message": "Nɣel tuṭṭfa ɣef afus"
+  },
   "notificationImageCopiedTitle": {
     "message": "Tuṭṭfa tettwanγel"
   },
   "notificationImageCopiedDetails": {
     "message": "Tuṭṭfa-inek tettwanγel yer ufus. Senned yef $META_KEY$-V akken ad tsenṭḍeḍ.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,19 +126,16 @@
     "message": "Suref-aɣ ɣef aya. Aqlaɣ nxeddem ɣef tmahilt i yileqman d-iteddun."
   },
   "genericErrorTitle": {
     "message": "Ihuh! Firefox Screenshots ur iteddu ara."
   },
   "genericErrorDetails": {
     "message": "Ur neẓri ara acu yeḍran. Ɛreḍ tikelt-nniḍen neɣ ṭṭef agdil n usebter-nniḍen?"
   },
-  "tourBodyIntro": {
-    "message": "Ṭṭef, sekles, bḍu igdilen war ma teffɣeḍ si Firefox."
-  },
   "tourHeaderPageAction": {
     "message": "Abrid amaynut i wsekles"
   },
   "tourBodyPageAction": {
     "message": "Mi tebγiḍ ad teṭṭfeḍ agdil ldi umuγ n tigawin n usebter illan deg ufeggag n tansiwin."
   },
   "tourHeaderClickAndDrag": {
     "message": "Ṭṭef kan ayen tebγiḍ"
@@ -141,28 +144,16 @@
     "message": "Sit sakin zuɣer akken ad teṭṭfeḍ aḥric seg usebter. Tzemreḍ daɣen ad tesrifgeḍ akken ad tsebṛuṛqeḍ afran-ik."
   },
   "tourHeaderFullPage": {
     "message": "Ṭṭef isfuyla neγ isebtar meṛṛa"
   },
   "tourBodyFullPage": {
     "message": "Fren tiqeffalin s afella ayeffus akken ad teṭṭfeḍ tamnaṭ yettbanen deg usfaylu neɣ asebter i meṛṛa."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Akken tebγiḍ"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Sekles tuṭṭfiwin-ik ar Web i beṭṭu fessusen, neɣ sider-itent-id ar uselkim-ik. Tzemr€d daɣen ad tiseḍ ɣef tqeffalt Tiṭṭfiwin-iw akken ad tafeḍ akk tuṭṭfiwin n ugdil i teggid."
-  },
-  "tourHeaderAccounts": {
-    "message": "Tuṭṭfiwin n wegdil ara yeddun"
-  },
-  "tourBodyAccounts": {
-    "message": "Jerred s umiḍan-ik n Firefox akken ad tkecmeḍ ɣer tuṭṭfiwin-inek deg ibenkan-ik meṛṛa  wa ad tkelseḍ tuṭṭfiwin-inek i tḥemleḍ i lebda."
-  },
   "tourSkip": {
     "message": "Zgel"
   },
   "tourNext": {
     "message": "Tigri n zdat"
   },
   "tourPrevious": {
     "message": "Tigri n deffir"
--- a/browser/extensions/screenshots/_locales/kk/messages.json
+++ b/browser/extensions/screenshots/_locales/kk/messages.json
@@ -126,16 +126,19 @@
     "message": "Қолайсыздық үшін кешірім сұраймыз. Бұл мүмкіндікті болашақ шығарылымдарда іске асыруға жұмысты жасаймыз."
   },
   "genericErrorTitle": {
     "message": "Қап! Firefox скриншоттары жасамай қалған сияқты."
   },
   "genericErrorDetails": {
     "message": "Не болғанын білмейміз. Қайталап көресіз бе, немесе басқа парақтың скриншотын түсіріп көресіз бе?"
   },
+  "tourBodyIntroServerless": {
+    "message": "Firefox-тан шықпай-ақ, скриншоттарды түсіріп, көшіріп, жүктеп алыңыз."
+  },
   "tourHeaderPageAction": {
     "message": "Сақтаудың жаңа жолы"
   },
   "tourBodyPageAction": {
     "message": "Скриншотты жасағыңыз келген уақытта адрестік жолақтың бет әрекеттері мәзірін ашыңыз."
   },
   "tourHeaderClickAndDrag": {
     "message": "Тек керек нәрсені түсіріңіз"
--- a/browser/extensions/screenshots/_locales/ml/messages.json
+++ b/browser/extensions/screenshots/_locales/ml/messages.json
@@ -12,48 +12,91 @@
     "message": "എന്റെ ഷോട്ടുകള്‍"
   },
   "screenshotInstructions": {
     "message": "ഒരു പ്രദേശം തിരഞ്ഞെടുക്കാൻ താളില്‍ ഡ്രാഗ് ചെയ്യുക അല്ലെങ്കിൽ ക്ലിക്കുചെയ്യുക. റദ്ദാക്കാൻ ESC അമർത്തുക."
   },
   "saveScreenshotSelectedArea": {
     "message": "സംരക്ഷിക്കുക"
   },
+  "uploadScreenshotSelectedArea": {
+    "message": "അപ്‌ലോഡ്"
+  },
   "saveScreenshotVisibleArea": {
     "message": "കാണുന്നതു സംരക്ഷിക്കുക"
   },
   "saveScreenshotFullPage": {
     "message": "താള്‍ മുഴുവന്‍ സംരക്ഷിക്കുക"
   },
   "cancelScreenshot": {
     "message": "റദ്ദാക്കുക"
   },
   "downloadScreenshot": {
     "message": "ഡൗണ്‍ലോഡ് ചെയ്യുക"
   },
+  "downloadScreenshotTitle": {
+    "message": "സ്ക്രീൻഷോട്ട് ഡൗൺലോഡ് ചെയ്യുക"
+  },
+  "downloadOnlyNotice": {
+    "message": "നിങ്ങൾ ഇപ്പോൾ ഡൗൺലോഡ് മാത്രം ഉള്ള മോഡിൽ ആണ്."
+  },
+  "downloadOnlyDetails": {
+    "message": "ഫയർഫോക്സ് സ്ക്രീൻഷോട്ടുകൾ ഈ സാഹചര്യങ്ങളിൽ ഡൗൺലോഡ്-മാത്രം മോഡിലേക് സ്വയം മാറുന്നു:"
+  },
   "downloadOnlyDetailsPrivate": {
     "message": "സ്വകാര്യ ബ്രൌസിങ്ങ് ജാലകത്തില്‍."
   },
+  "downloadOnlyDetailsThirdParty": {
+    "message": "മൂന്നാം കക്ഷി കുക്കികൾ അപ്രാപ്തമാക്കി."
+  },
+  "downloadOnlyDetailsNeverRemember": {
+    "message": "“ചരിത്രം ഒരിക്കലും ഓർക്കേണ്ട” എന്ന സജ്ജീകരണം പ്രാപ്തമാക്കിയിരിക്കുന്നു."
+  },
+  "downloadOnlyDetailsESR": {
+    "message": "നിങ്ങൾ ഫയർഫോക്സ് ESR ഉപയോഗിക്കുന്നു."
+  },
+  "downloadOnlyDetailsNoUploadPref": {
+    "message": "അപ്ലോഡുകൾ അപ്രാപ്തമാക്കി."
+  },
   "notificationLinkCopiedTitle": {
     "message": "ലിങ്ക് പകര്‍ത്തി"
   },
   "notificationLinkCopiedDetails": {
     "message": "നിങ്ങളുടെ ഷോട്ടിലേക്കുള്ള ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി. ഒട്ടിക്കാൻ $META_KEY$-V അമർത്തുക.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "പകര്‍ത്തുക"
   },
+  "copyScreenshotTitle": {
+    "message": "ക്ലിപ്പ്ബോർഡിലേക്ക് സ്ക്രീൻഷോട്ട് പകർത്തുക"
+  },
   "notificationImageCopiedTitle": {
     "message": "ഷോട്ട് പകര്‍ത്തി"
   },
+  "notificationImageCopiedDetails": {
+    "message": "നിങ്ങളുടെ ഷോട്ടിലേക്കുള്ള ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി. ഒട്ടിക്കാൻ $META_KEY$-V അമർത്തുക.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
+  "imageCropPopupWarning": {
+    "message": "സംരക്ഷിത ചിത്രം $PIXELS$px ഉയരത്തിലേക്ക് മാറ്റപ്പെടും.",
+    "placeholders": {
+      "pixels": {
+        "content": "$1"
+      }
+    }
+  },
   "requestErrorTitle": {
     "message": "പണി പാളി."
   },
   "requestErrorDetails": {
     "message": "ക്ഷമിക്കണം! താങ്കളുടെ സ്ക്രീൻഷോട്ട് സൂക്ഷിക്കാന്‍ കഴിഞ്ഞില്ല. ദയവായി പിന്നീടു ശ്രമിക്കുക."
   },
   "connectionErrorTitle": {
     "message": "നിങ്ങളുടെ സ്ക്രീൻഷോട്ടുകളിലേക്കു ബന്ധിപ്പിക്കാന്‍ കഴിയുന്നില്ല."
@@ -71,43 +114,72 @@
     "message": "ഇതൊരു സാധാരണ താള്‍ അല്ല, അതിനാൽ അതിന്റെ ഒരു സ്ക്രീൻഷോട്ട് എടുക്കാനാകില്ല."
   },
   "selfScreenshotErrorTitle": {
     "message": "സ്ക്രീൻഷോട്ട് താളിന്റെ സ്ക്രീൻഷോട്ട് എടുക്കാന്‍ പറ്റില്ല‌!"
   },
   "emptySelectionErrorTitle": {
     "message": "നിങ്ങളുടെ തെരഞ്ഞെടുപ്പ് തീരെ ചെറുതാണ്"
   },
+  "privateWindowErrorTitle": {
+    "message": "സ്വകാര്യ ബ്രൗസിംഗ് മോഡിൽ സ്ക്രീൻഷോട്ട്സ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു"
+  },
   "privateWindowErrorDetails": {
     "message": "അസൗകര്യം നേരിടേണ്ടി വന്നതിൽ ഖേദിക്കുന്നു. ഈ സവിശേഷതയുള്ള ഭാവിയിലെ റിലീസുകൾക്കായി ഞങ്ങൾ പ്രവർത്തിക്കുന്നു."
   },
   "genericErrorTitle": {
     "message": "അയ്യോ! ഫയര്‍ഫോക്സ് സ്ക്രീൻഷോട്ടിനു എന്തോ പറ്റി."
   },
   "genericErrorDetails": {
     "message": "എന്താ സംഭവിച്ചതെന്ന് വല്യ പിടി ഇല്ല. ഒന്നുകൂടി നോക്കുകയോ അല്ലെങ്കില്‍ വേറെ താളിന്റെ സ്ക്രീൻഷോട്ട് എടുക്കുകയോ ചെയ്യുന്നോ?"
   },
+  "tourBodyIntroServerless": {
+    "message": "ഫയര്‍ഫോക്സ് വിട്ടുപോകാതെ തന്നെ സ്ക്രീൻഷോട്ടുകൾ എടുക്കുക, പകര്‍ത്തുക, ഡൌണ്‍ലോഡു് ചെയ്യുക."
+  },
   "tourHeaderPageAction": {
     "message": "സൂക്ഷിക്കാന്‍ പുതിയൊരു മാർഗ്ഗം"
   },
-  "tourHeaderDownloadUpload": {
-    "message": "നിങ്ങളുടെ ഇഷ്ടാനുസൃതം"
+  "tourBodyPageAction": {
+    "message": "നിങ്ങൾ സ്ക്രീൻഷോട്ട് എടുക്കാൻ ആഗ്രഹിക്കുമ്പോള്‍ വിലാസ ബാറിലെ പേജ് പ്രവർത്തനങ്ങളുടെ മെനു വിപുലീകരിക്കുക."
+  },
+  "tourHeaderClickAndDrag": {
+    "message": "നിങ്ങൾക്ക് വേണ്ടതു് പിടിച്ചെടുക്കുക"
+  },
+  "tourBodyClickAndDrag": {
+    "message": "ഒരു പേജിന്റെ ഒരു ഭാഗം പകർത്താൻ ക്ലിക്കുചെയ്ത് ഇഴയ്ക്കുക. നിങ്ങളുടെ തിരഞ്ഞെടുക്കൽ ഹൈലൈറ്റുചെയ്ത് നിങ്ങൾക്ക് ഹോവർ ചെയ്യാനാകും."
+  },
+  "tourHeaderFullPage": {
+    "message": "പണിയിടം അല്ലെങ്കിൽ പേജു് മുഴുവൻ ക്യാപ്ചർ ചെയ്യുക"
+  },
+  "tourBodyFullPage": {
+    "message": "പണിയിടത്തില്‍ ദൃശ്യമായ പ്രദേശം പിടിച്ചെടുക്കാനോ അല്ലെങ്കിൽ ഒരു മുഴുവൻ പേജ് എടുക്കാനോ മുകളിലോ വലതുഭാഗത്തുള്ള ബട്ടണുകൾ തിരഞ്ഞെടുക്കുക."
   },
   "tourSkip": {
     "message": "ഒഴിവാക്കുക"
   },
   "tourNext": {
     "message": "അടുത്ത സ്ലൈഡ്"
   },
   "tourPrevious": {
     "message": "മുമ്പത്തെ സ്ലൈഡ്"
   },
   "tourDone": {
     "message": "തീര്‍ന്നു"
   },
+  "termsAndPrivacyNotice2": {
+    "message": "ഫയർഫോക്സ് സ്ക്രീൻഷോട്ട്സ് ഉപയോഗിക്കുമ്പോൾ, നിങ്ങൾ ഞങ്ങളുടെ $TERMSANDPRIVACYNOTICETERMSLINK$, $TERMSANDPRIVACYNOTICEPRIVACYLINK$ എന്നിവ അംഗീകരിക്കുന്നു.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
   "termsAndPrivacyNoticeTermsLink": {
     "message": "നിബന്ധനകൾ"
   },
   "termsAndPrivacyNoticyPrivacyLink": {
     "message": "സ്വകാര്യതാ അറിയിപ്പ്"
   },
   "libraryLabel": {
     "message": "സ്ക്രീൻഷോട്ടുകൾ"
--- a/browser/extensions/screenshots/_locales/my/messages.json
+++ b/browser/extensions/screenshots/_locales/my/messages.json
@@ -27,16 +27,19 @@
     "message": "စာမျက်နှာတစ်ခုလုံးကို သိမ်းပါ"
   },
   "cancelScreenshot": {
     "message": "မဆောင်ရွက်တော့ပါ"
   },
   "downloadScreenshot": {
     "message": "ဆွဲယူရန်"
   },
+  "downloadScreenshotTitle": {
+    "message": "မျက်နှာပြင်ပုံဖမ်းချက်ကို ကူးယူဆွဲချပါ"
+  },
   "downloadOnlyNotice": {
     "message": "ဆွဲချမှု တစ်မျိုးသာသုံးသော ပုံစံဖြင့် သင်အခုသုံးနေသည်"
   },
   "downloadOnlyDetails": {
     "message": "ဒီအခြေအနေတွင် Firefoxမှ ရိုက်ချက်များသည် အလိုအလျောက် ဆွဲချမှုတစ်မျိုးတည်းသာသုံးသောပုံစံသို့ ပြောင်းလဲသည်"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "သီးသန့်ကြည့်ရှုခြင်းပုံစံ"
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "ကူးပါ"
   },
+  "copyScreenshotTitle": {
+    "message": "မျက်နှာပြင်ပုံဖမ်းချက်ကို ကလစ်ဘုတ်သို့ ကူးယူပါ"
+  },
   "notificationImageCopiedTitle": {
     "message": "ရိုက်ချက်ကူးပါ"
   },
   "notificationImageCopiedDetails": {
     "message": "သင်ဖမ်းယူခဲ့သော ပုံကို ကလစ်ဘုတ်သို့ ကူးယူပြီးပြီ။ ပွားယူရန် $META_KEY$-V ကို နှိပ်ပါ။",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "အဆင်မပြေမှုများအတွက် တောင်းပန်ပါတယ်။ ယခုလုပ်ဆောင်ချက်ကို နောင်ထုတ်ကုန်တွင် ပါဝင်စေရန် ဆောင်ရွက်နေပါသည်။"
   },
   "genericErrorTitle": {
     "message": "ဝိုး။ Firefox Screenshots မှာ အမှားဖြစ်ပေါ်ခဲ့သည်။"
   },
   "genericErrorDetails": {
     "message": "ဘာဖြစ်သွားခဲ့မှန်း သေချာမသိခဲ့ပါ။ ထပ်စမ်းကြည့်လိုပါသလား သို့မဟုတ် အခြားဝဘ်စာမျက်နှာကို ပုံရိပ်ဖမ်းလိုပါသလား။"
   },
-  "tourBodyIntro": {
-    "message": "Firefox ကနေ ထွက်ခွာရန် မလိုဘဲ မျက်နှာပြင်ပုံရိပ်များကို ရိုက်ကူး၊ သိမ်းဆည်း၊ မျှဝေပါ။"
+  "tourBodyIntroServerless": {
+    "message": "Firefox ကို ပိတ်စရာမလိုပဲ မျက်နှာပြင်ပုံဖမ်းချက်များကို ရိုက်ကူး၊ ပွားယူ၊ ဆွဲချပါ။"
   },
   "tourHeaderPageAction": {
     "message": "သိမ်းဆဲရန် နည်းလမ်းအသစ်"
   },
   "tourBodyPageAction": {
     "message": "သင် မျက်နှာပြင်ပုံရိပ်ဖမ်းလိုသည့် အခါတိုင်း လိပ်စာဘားတန်းရှိ စာမျက်နှာလုပ်ဆောင်ချက်များ မီနူးကို ဖြန့်ချပါ။"
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "စာမျက်နှာ၏ အစိတ်အပိုင်းကို ဖမ်းယူရန် ကလစ်နှိပ်ပြီး ဖိဆွဲပါ။ သင့်ရွေးချယ်မှုကို ထင်ရှားစေရန် ညွှန်တံမြားကို ဆိုင်ရာအစိတ်အပိုင်းပေါ် ရွှေ့နိုင်သည်။"
   },
   "tourHeaderFullPage": {
     "message": "ဝင်ဒိုးများ သို့မဟုတ် စာမျက်နှာတစ်ခုလုံးကို ဖမ်းယူပါ"
   },
   "tourBodyFullPage": {
     "message": "ဝင်းဒိုးထဲရှိ မြင်ရသော အကျယ်အဝန်းကို ဖမ်းယူရန် သို့မဟုတ် စာမျက်နှာတစ်ခုလုံးကို ဖမ်းယူရန် ညာဘက်အပေါ်ဘက်ရှိ ခလုတ်များကို ရွေးပါ။"
   },
-  "tourHeaderDownloadUpload": {
-    "message": "နှစ်သက်သလို"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "ဝဘ်တွင် အလွယ်တကူ မျှဝေရန် သို့မဟုတ် ကွန်ပျူတာထဲသို့ ဆွဲယူကူးရန် ဖြတ်တောက်ထားသော ပုံဖမ်းချက်များကို သိမ်းပါ။ ရိုက်ထားသမျှပုံများအားလုံးကို ရှာဖွေကြည့်ရှုရန် ရိုက်ထားသောပုံများတွင်လည်း ကလစ်နှိပ်ကြည့်နိုင်သည်။"
-  },
-  "tourHeaderAccounts": {
-    "message": "အသုံးပြုတိုင်း ရှိနေမည့် Screenshots"
-  },
-  "tourBodyAccounts": {
-    "message": "ကိရိယာအားလုံးရှိ မျက်နှာပြင်ရိုက်ချက်များကို အသုံးပြုရန် နှင့် သင်နှစ်သက်သည်များကို သိမ်းဆည်းရန် သင့် Firefox Account နှင့် ဝင်ရောက်ပါ။"
-  },
   "tourSkip": {
     "message": "SKIP"
   },
   "tourNext": {
     "message": "နောက်ဆလိုက်"
   },
   "tourPrevious": {
     "message": "အရင်ကဆလိုက်"
--- a/browser/extensions/screenshots/_locales/nn_NO/messages.json
+++ b/browser/extensions/screenshots/_locales/nn_NO/messages.json
@@ -27,16 +27,19 @@
     "message": "Lagre heile sida"
   },
   "cancelScreenshot": {
     "message": "Avbryt"
   },
   "downloadScreenshot": {
     "message": "Last ned"
   },
+  "downloadScreenshotTitle": {
+    "message": "Last ned skjermbildet"
+  },
   "downloadOnlyNotice": {
     "message": "Du er for tida i nedlastingsmodus."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots endrar automatisk til nedlastingsmodus i følgjande situasjonar:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "I eit privat nettlesingsvindauge."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Kopier"
   },
+  "copyScreenshotTitle": {
+    "message": "Kopier skjermbildet til utklippstavla"
+  },
   "notificationImageCopiedTitle": {
     "message": "Bilde kopiert"
   },
   "notificationImageCopiedDetails": {
     "message": "Bildet ditt er kopiert til utklippstavla. Trykk på $META_KEY$-V for å lime det inn.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Ein er lei for ulempa. Vi jobbar med denne funksjonen for framtidige versjonar."
   },
   "genericErrorTitle": {
     "message": "Oj! Det ser ut til at Firefox Screenshots ikkje fungerer korrekt."
   },
   "genericErrorDetails": {
     "message": "Vi er ikkje sikre på kva som hende. Kan du prøve igjen eller ta eit bilde på ei anna side?"
   },
-  "tourBodyIntro": {
-    "message": "Ta, lagre og del skjermbilde utan å forlate Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Ta, kopier og last ned skjermbilde utan å forlate Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Ein ny måte å lagre på"
   },
   "tourBodyPageAction": {
     "message": "Utvid sidehandlingsmenyen i adresselinja når du vil ta eit skjermbilde."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Klikk for å drage og knipse berre ein del av sida. Du kan også føre musa over for å framheve merkt område."
   },
   "tourHeaderFullPage": {
     "message": "Knips vindauge eller heile sider"
   },
   "tourBodyFullPage": {
     "message": "Vel knappane i det øvre høgre hjørnet for å knipse det synlege området i vindauget eller for å knipse ei heil side."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Som du vil ha det"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Lagre dei tilskorne bilda dine på nettet for enklare deling, eller last dei ned til datamaskina di. Du kan også klikke på knappen Mine skjermbilde for å finne alle bilda du har tatt."
-  },
-  "tourHeaderAccounts": {
-    "message": "Skjermbilde til å ta med"
-  },
-  "tourBodyAccounts": {
-    "message": "Logg på med Firefox-kontoen din for å få tilgang til bilda dine på alle einingane dine og lagre favorittbilda dine for alltid."
-  },
   "tourSkip": {
     "message": "Hopp over"
   },
   "tourNext": {
     "message": "Neste slide"
   },
   "tourPrevious": {
     "message": "Føregåande slide"
@@ -177,17 +171,17 @@
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
   },
   "termsAndPrivacyNoticeTermsLink": {
-    "message": "Vilkår"
+    "message": "vilkåra"
   },
   "termsAndPrivacyNoticyPrivacyLink": {
-    "message": "Personvernmerknad"
+    "message": "personvernmerknaden"
   },
   "libraryLabel": {
     "message": "Skjermbilde"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/_locales/pt_BR/messages.json
+++ b/browser/extensions/screenshots/_locales/pt_BR/messages.json
@@ -7,17 +7,17 @@
   },
   "contextMenuLabel": {
     "message": "Capturar tela"
   },
   "myShotsLink": {
     "message": "Minhas capturas"
   },
   "screenshotInstructions": {
-    "message": "Arraste ou clique na página para selecionar uma região. Pressione ESC para cancelar."
+    "message": "Clique e arraste ou aponte e clique para selecionar uma região. Tecle ESC para cancelar."
   },
   "saveScreenshotSelectedArea": {
     "message": "Salvar"
   },
   "uploadScreenshotSelectedArea": {
     "message": "Enviar"
   },
   "saveScreenshotVisibleArea": {
@@ -127,29 +127,29 @@
   },
   "genericErrorTitle": {
     "message": "Uoou! O Firefox Screenshot enlouqueceu."
   },
   "genericErrorDetails": {
     "message": "Não temos certeza do que acabou de acontecer. Tentar novamente ou fazer uma captura de uma página diferente?"
   },
   "tourBodyIntroServerless": {
-    "message": "Faça, copie e baixe capturas de tela sem deixar o Firefox."
+    "message": "Capture telas, copie e baixe sem sair do Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Um novo jeito de salvar"
   },
   "tourBodyPageAction": {
-    "message": "Abra o menu de ações da página na barra de endereços sempre que quiser fazer uma captura de tela."
+    "message": "Abra o menu de ações da página na barra de endereços sempre que quiser capturar uma tela."
   },
   "tourHeaderClickAndDrag": {
     "message": "Capture apenas o que você quer"
   },
   "tourBodyClickAndDrag": {
-    "message": "Clique e arraste para capturar apenas uma parte de uma página. Você também pode passar o mouse para realçar sua seleção."
+    "message": "Clique e arraste para capturar apenas uma parte da página. Você também pode mover o mouse sobre a tela para realçar uma seleção."
   },
   "tourHeaderFullPage": {
     "message": "Capture janelas ou páginas inteiras"
   },
   "tourBodyFullPage": {
     "message": "Selecione os botões no canto superior direito para capturar a área visível na janela ou capturar uma página inteira."
   },
   "tourSkip": {
--- a/browser/extensions/screenshots/_locales/pt_PT/messages.json
+++ b/browser/extensions/screenshots/_locales/pt_PT/messages.json
@@ -19,17 +19,17 @@
   },
   "uploadScreenshotSelectedArea": {
     "message": "Carregar"
   },
   "saveScreenshotVisibleArea": {
     "message": "Guardar visível"
   },
   "saveScreenshotFullPage": {
-    "message": "Guardar página inteira"
+    "message": "Guardar página completa"
   },
   "cancelScreenshot": {
     "message": "Cancelar"
   },
   "downloadScreenshot": {
     "message": "Transferir"
   },
   "downloadScreenshotTitle": {
--- a/browser/extensions/screenshots/_locales/sk/messages.json
+++ b/browser/extensions/screenshots/_locales/sk/messages.json
@@ -27,16 +27,19 @@
     "message": "Uložiť celú stránku"
   },
   "cancelScreenshot": {
     "message": "Zrušiť"
   },
   "downloadScreenshot": {
     "message": "Prevziať"
   },
+  "downloadScreenshotTitle": {
+    "message": "Prevziať snímku obrazovky"
+  },
   "downloadOnlyNotice": {
     "message": "Ste v režime len na prevzatie."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots automaticky prejde do režimu len na prevzatie v nasledujúcich situáciách:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Ste v režime Súkromné prehliadanie."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Kopírovať"
   },
+  "copyScreenshotTitle": {
+    "message": "Skopírovať snímok do schránky"
+  },
   "notificationImageCopiedTitle": {
     "message": "Snímka bola skopírovaná"
   },
   "notificationImageCopiedDetails": {
     "message": "Vaša snímka bola skopírovaná do schránky. Stlačením $META_KEY$-V ju prilepíte.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Ospravedlňujeme sa za spôsobené nepríjemnosti. Pracujeme na vylepšení tejto funkcie v budúcich verziách."
   },
   "genericErrorTitle": {
     "message": "Ups! Služba Firefox Screenshots prestala pracovať."
   },
   "genericErrorDetails": {
     "message": "Nie sme si istí, čo sa práve stalo. Chcete tú skúsiť znova alebo chcete vytvoriť snímku inej stránky?"
   },
-  "tourBodyIntro": {
-    "message": "Tvorte, ukladajte a zdieľajte snímky obrazovky bez toho, aby ste museli opustiť Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Tvorte, kopírujte a preberajte snímky obrazovky bez toho, aby ste museli opustiť Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Nový spôsob ukladania"
   },
   "tourBodyPageAction": {
     "message": "Kedykoľvek chcete urobiť snímku, otvorte ponuku akcii stránky v paneli s adresou."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Ak chcete zachytiť časť stránky, urobíte to kliknutím a potiahnutím. Váš výber zvýrazníte tak, že sa naň presuniete myšou."
   },
   "tourHeaderFullPage": {
     "message": "Zachyťte okná alebo celé webové stránky"
   },
   "tourBodyFullPage": {
     "message": "Kliknutím na tlačidlo v pravom hornom rohu môžete zachytiť viditeľnú časť stránky. Pomocou ďalšieho tlačidla zachytíte celú stránku."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Urobte to, čo chcete"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Uložte si orezanú snímku na web, aby ste ju mohli ľahšie zdieľať alebo si ju prevziať do počítača. Môžete si taktiež pozrieť všetky vaše snímky - stačí, ak kliknete na tlačidlo Moje snímky."
-  },
-  "tourHeaderAccounts": {
-    "message": "Snímky stránok vždy so sebou"
-  },
-  "tourBodyAccounts": {
-    "message": "Prihláste sa so svojím účtom Firefox a majte prístup ku všetkým svojich snímkam zo všetkých vašich zariadení."
-  },
   "tourSkip": {
     "message": "Preskočiť"
   },
   "tourNext": {
     "message": "Ďalšia snímka"
   },
   "tourPrevious": {
     "message": "Predchádzajúca snímka"
--- a/browser/extensions/screenshots/_locales/sl/messages.json
+++ b/browser/extensions/screenshots/_locales/sl/messages.json
@@ -27,16 +27,19 @@
     "message": "Shrani celotno stran"
   },
   "cancelScreenshot": {
     "message": "Prekliči"
   },
   "downloadScreenshot": {
     "message": "Prenesi"
   },
+  "downloadScreenshotTitle": {
+    "message": "Prenesi posnetek zaslona"
+  },
   "downloadOnlyNotice": {
     "message": "Trenutno ste v načinu samo za prenos."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots se samodejno preklopi v način samo za prenos v naslednjih primerih:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "V oknu zasebnega brskanja."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Kopiraj"
   },
+  "copyScreenshotTitle": {
+    "message": "Kopiraj posnetek zaslona v odložišče"
+  },
   "notificationImageCopiedTitle": {
     "message": "Posnetek kopiran"
   },
   "notificationImageCopiedDetails": {
     "message": "Posnetek zaslona je bil kopiran na odložišče. Pritisnite $META_KEY$-V, da ga prilepite.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Oprostite za nevšečnost. To možnost izboljšujemo za prihodnje izdaje."
   },
   "genericErrorTitle": {
     "message": "Uf! Firefox Screenshots se je pokvaril."
   },
   "genericErrorDetails": {
     "message": "Ne vemo točno, kaj se je pravkar zgodilo. Bi radi poskusili znova ali pa zajeli posnetek kakšne druge strani?"
   },
-  "tourBodyIntro": {
-    "message": "Zajemite, shranite in delite zaslonske posnetke, ne da bi zapustili Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Zajemite, kopirajte in delite zaslonske posnetke, ne da bi zapustili Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Nov način shranjevanja"
   },
   "tourBodyPageAction": {
     "message": "Kadarkoli želite zajeti posnetek zaslona, v naslovni vrstici razširite meni dejanj strani."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Kliknite in povlecite, če želite zajeti samo del strani. Svojo izbiro lahko tudi poudarite, tako da preko nje povlečete miškin kazalec."
   },
   "tourHeaderFullPage": {
     "message": "Zajemite okna ali celotne strani"
   },
   "tourBodyFullPage": {
     "message": "V zgornjem desnem kotu izberite gumb za zajem vidnega območja v oknu ali celotne strani."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Kot vi želite"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Shranite obrezane posnetke na splet za lažje deljenje ali jih prenesite na svoj računalnik. Vse zajete posnetke lahko najdete s klikom na gumb Moji posnetki."
-  },
-  "tourHeaderAccounts": {
-    "message": "Posnetki za na pot"
-  },
-  "tourBodyAccounts": {
-    "message": "Prijavite se s Firefox Računom za dostop do posnetkov na vseh svojih napravah in trajno shranjevanje priljubljenih posnetkov."
-  },
   "tourSkip": {
     "message": "Preskoči"
   },
   "tourNext": {
     "message": "Naslednji diapozitiv"
   },
   "tourPrevious": {
     "message": "Prejšnji diapozitiv"
--- a/browser/extensions/screenshots/_locales/sq/messages.json
+++ b/browser/extensions/screenshots/_locales/sq/messages.json
@@ -27,16 +27,19 @@
     "message": "Ruaj krejt faqen"
   },
   "cancelScreenshot": {
     "message": "Anuloje"
   },
   "downloadScreenshot": {
     "message": "Shkarkoje"
   },
+  "downloadScreenshotTitle": {
+    "message": "Shkarkojeni foton e ekranit"
+  },
   "downloadOnlyNotice": {
     "message": "Gjendeni nën mënyrën Vetëm Shkarkim."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots kalon vetvetiu nën mënyrën Vetëm Shkarkim në këto situata:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Në një dritare Shfletimi Privat."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Kopjoje"
   },
+  "copyScreenshotTitle": {
+    "message": "Kopjojeni foton e ekranit te e papastra"
+  },
   "notificationImageCopiedTitle": {
     "message": "Fotoja u Kopjua"
   },
   "notificationImageCopiedDetails": {
     "message": "Fotoja juaj u kopjua në të papastër. Për ta ngjitur diku, shtypni $META_KEY$-V.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Na ndjeni për mungesën. Po punojmë mbi këtë veçori për hedhjet e ardhshme në qarkullim."
   },
   "genericErrorTitle": {
     "message": "Yhaaa! Firefox Screenshots shkalloi."
   },
   "genericErrorDetails": {
     "message": "S’jemi të sigurt se ç’ndodhi. Ju prish punë të bëni një foto të një faqeje tjetër?"
   },
-  "tourBodyIntro": {
-    "message": "Bëni, ruani dhe ndani foto ekrani me të tjerët pa dalë nga Firefox-i."
+  "tourBodyIntroServerless": {
+    "message": "Bëni, kopjoni, dhe shkarkoni foto ekrani pa dalë nga Firefox-i."
   },
   "tourHeaderPageAction": {
     "message": "Një mënyrë e re për të ruajtur"
   },
   "tourBodyPageAction": {
     "message": "Kurdo që doni të bëni një foto ekrani, zgjeroni menunë e veprimeve mbi faqen, te shtylla e adresave."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Klikoni dhe tërhiqeni që të fotografoni thjesht një copë të faqes. Mund edhe të kaloni kursorin përsipër që të theksoni përzgjedhjen tuaj."
   },
   "tourHeaderFullPage": {
     "message": "Fiksoni Dritare ose Krejt Faqet"
   },
   "tourBodyFullPage": {
     "message": "Përzgjidhni butonat në cepin e sipërm djathtas që të fotografoni zonën e dukshme te dritarja ose një faqe të tërë."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Si T’ju Pëlqejë"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Ruajini fotot tuaja në web, për ndarje më të lehtë me të tjerët, ose shkarkojini në kompjuterin tuaj. Mund edhe të klikoni te butoni Shkrepjet e Mia që të gjeni krejt shkrepjet që keni bërë."
-  },
-  "tourHeaderAccounts": {
-    "message": "Foto ekrani Kudo Me Vete"
-  },
-  "tourBodyAccounts": {
-    "message": "Hyni në Llogarinë tuaj Firefox që të përdorni shkrepjet tuaja në krejt pajisjet tuaja dhe t’i ruani shkrepjet e parapëlqyera përgjithmonë."
-  },
   "tourSkip": {
     "message": "ANASHKALOJE"
   },
   "tourNext": {
     "message": "Diapozitivi Pasues"
   },
   "tourPrevious": {
     "message": "Diapozitivi i Mëparshëm"
--- a/browser/extensions/screenshots/_locales/th/messages.json
+++ b/browser/extensions/screenshots/_locales/th/messages.json
@@ -27,16 +27,19 @@
     "message": "บันทึกเต็มหน้า"
   },
   "cancelScreenshot": {
     "message": "ยกเลิก"
   },
   "downloadScreenshot": {
     "message": "ดาวน์โหลด"
   },
+  "downloadScreenshotTitle": {
+    "message": "ดาวน์โหลดภาพหน้าจอ"
+  },
   "downloadOnlyNotice": {
     "message": "คุณกำลังอยู่ในโหมดดาวน์โหลดเท่านั้น"
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots จะเปลี่ยนเป็นโหมดดาวน์โหลดอย่างเดียวโดยอัตโนมัติในสถานการณ์เหล่านี้:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "ในหน้าต่างการท่องเว็บแบบส่วนตัว"
@@ -62,27 +65,38 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "คัดลอก"
   },
+  "copyScreenshotTitle": {
+    "message": "คัดลอกภาพหน้าจอไปยังคลิปบอร์ด"
+  },
   "notificationImageCopiedTitle": {
     "message": "คัดลอกภาพหน้าจอแล้ว"
   },
   "notificationImageCopiedDetails": {
     "message": "คัดลอกภาพของคุณไปยังคลิปบอร์ดแล้ว กด $META_KEY$-V เพื่อวาง",
     "placeholders": {
       "meta_key": {
         "content": "$1"
       }
     }
   },
+  "imageCropPopupWarning": {
+    "message": "ภาพที่บันทึกจะถูกครอบตัดให้มีความสูง $PIXELS$ พิกเซล",
+    "placeholders": {
+      "pixels": {
+        "content": "$1"
+      }
+    }
+  },
   "requestErrorTitle": {
     "message": "ใช้งานไม่ได้"
   },
   "requestErrorDetails": {
     "message": "ขออภัย! เราไม่สามารถบันทึกภาพหน้าจอของคุณ โปรดลองอีกครั้งในภายหลัง"
   },
   "connectionErrorTitle": {
     "message": "เราไม่สามารถเชื่อมต่อกับภาพหน้าจอของคุณ"
@@ -112,18 +126,18 @@
     "message": "ขออภัยในความไม่สะดวก เรากำลังพัฒนาคุณลักษณะนี้สำหรับรุ่นในอนาคต"
   },
   "genericErrorTitle": {
     "message": "โอ๊ย! Firefox Screenshots รวน"
   },
   "genericErrorDetails": {
     "message": "เราไม่แน่ใจว่าเกิดอะไรขึ้น ต้องการลองอีกครั้งหรือจับภาพหน้าจอของหน้าอื่น?"
   },
-  "tourBodyIntro": {
-    "message": "จับ บันทึก และแบ่งปันภาพหน้าจอโดยไม่ต้องออกจาก Firefox"
+  "tourBodyIntroServerless": {
+    "message": "จับ คัดลอก และดาวน์โหลดภาพหน้าจอโดยไม่ต้องออกจาก Firefox"
   },
   "tourHeaderPageAction": {
     "message": "หนทางใหม่ในการบันทึก"
   },
   "tourBodyPageAction": {
     "message": "ขยายเมนูการกระทำหน้าในแถบที่อยู่ทุกครั้งที่คุณต้องการจับภาพหน้าจอ"
   },
   "tourHeaderClickAndDrag": {
@@ -133,22 +147,16 @@
     "message": "คลิกแล้วลากเพื่อจับภาพแค่บางส่วนของหน้า คุณยังสามารถวางเมาส์เพื่อเน้นการเลือกของคุณ"
   },
   "tourHeaderFullPage": {
     "message": "จับภาพหน้าต่างหรือทั้งหน้า"
   },
   "tourBodyFullPage": {
     "message": "คลิกที่ปุ่มด้านบนขวาเพื่อจับภาพพื้นที่ที่มองเห็นในหน้าต่างหรือเพื่อจับภาพทั้งหน้า"
   },
-  "tourHeaderDownloadUpload": {
-    "message": "ตามใจชอบ"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "บันทึกภาพหน้าจอที่ครอบตัดของคุณไปยังเว็บเพื่อการแบ่งปันที่ง่ายขึ้น หรือดาวน์โหลดไปยังคอมพิวเตอร์ของคุณ คุณยังสามารถคลิกที่ปุ่ม ภาพหน้าจอของฉัน เพื่อค้นหาภาพหน้าจอทั้งหมดที่คุณได้จับไว้"
-  },
   "tourSkip": {
     "message": "ข้าม"
   },
   "tourNext": {
     "message": "ภาพนิ่งถัดไป"
   },
   "tourPrevious": {
     "message": "ภาพนิ่งก่อนหน้า"
--- a/browser/extensions/screenshots/_locales/uk/messages.json
+++ b/browser/extensions/screenshots/_locales/uk/messages.json
@@ -27,16 +27,19 @@
     "message": "Зберегти всю сторінку"
   },
   "cancelScreenshot": {
     "message": "Скасувати"
   },
   "downloadScreenshot": {
     "message": "Завантажити"
   },
+  "downloadScreenshotTitle": {
+    "message": "Завантажити знімки екрану"
+  },
   "downloadOnlyNotice": {
     "message": "Ви зараз в режимі лише завантаження."
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots автоматично переходить в режим лише завантаження в таких випадках:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "У вікні приватного перегляду."
@@ -62,16 +65,19 @@
       "meta_key": {
         "content": "$1"
       }
     }
   },
   "copyScreenshot": {
     "message": "Копіювати"
   },
+  "copyScreenshotTitle": {
+    "message": "Копіювати знімки в буфер обміну"
+  },
   "notificationImageCopiedTitle": {
     "message": "Знімок скопійовано"
   },
   "notificationImageCopiedDetails": {
     "message": "Ваш знімок був скопійований в буфер обміну. Натисніть $META_KEY$-V, щоб вставити.",
     "placeholders": {
       "meta_key": {
         "content": "$1"
@@ -120,18 +126,18 @@
     "message": "Вибачте за незручності. Ми працюємо над цією функцією для майбутніх випусків."
   },
   "genericErrorTitle": {
     "message": "Оу! З Firefox Screenshots щось негаразд."
   },
   "genericErrorDetails": {
     "message": "Ми не впевнені, в чому проблема. Спробувати ще раз, або ж зробити знімок іншої сторінки?"
   },
-  "tourBodyIntro": {
-    "message": "Робіть знімки екрану, зберігайте та діліться ними прямо в Firefox."
+  "tourBodyIntroServerless": {
+    "message": "Створюйте, копіюйте і завантажуйте знімки екрану, не покидаючи Firefox."
   },
   "tourHeaderPageAction": {
     "message": "Новий спосіб збереження"
   },
   "tourBodyPageAction": {
     "message": "Розгорніть меню дій для сторінки в панелі адреси, коли ви хочете зробити знімок екрану."
   },
   "tourHeaderClickAndDrag": {
@@ -141,28 +147,16 @@
     "message": "Клацніть і потягніть мишею для захоплення частини сторінки. Ви також можете навести курсор миші для підсвічення вибраної області."
   },
   "tourHeaderFullPage": {
     "message": "Захоплюйте вікна або цілі сторінки"
   },
   "tourBodyFullPage": {
     "message": "За допомогою кнопок у верхній правій частині обирайте захоплення видимої області вікна, або сторінки повністю."
   },
-  "tourHeaderDownloadUpload": {
-    "message": "Як вам подобається"
-  },
-  "tourBodyDownloadUpload": {
-    "message": "Зберігайте свої знімки в Інтернеті, щоб легко ними ділитися, або завантажуйте їх на свій комп'ютер. Ви також можете переглянути всі збережені знімки, натиснувши на кнопку Мої знімки."
-  },
-  "tourHeaderAccounts": {
-    "message": "Знімки екрану на ходу"
-  },
-  "tourBodyAccounts": {
-    "message": "Увійдіть в обліковий запис Firefox, щоб отримати доступ до своїх знімків на всіх пристроях і зберігати обрані знімки без обмежень."
-  },
   "tourSkip": {
     "message": "Пропустити"
   },
   "tourNext": {
     "message": "Наступний слайд"
   },
   "tourPrevious": {
     "message": "Попередній слайд"
--- a/browser/extensions/screenshots/_locales/vi/messages.json
+++ b/browser/extensions/screenshots/_locales/vi/messages.json
@@ -40,17 +40,17 @@
   },
   "downloadOnlyDetails": {
     "message": "Firefox Screenshots sẽ tự động chuyển sang chế độ chỉ tải về trong các tình huống:"
   },
   "downloadOnlyDetailsPrivate": {
     "message": "Trong một cửa sổ duyệt web riêng tư."
   },
   "downloadOnlyDetailsThirdParty": {
-    "message": "Cookies của bên thứ ba đã bị vô hiệu hóa."
+    "message": "Cookie của bên thứ ba đã bị vô hiệu hóa."
   },
   "downloadOnlyDetailsNeverRemember": {
     "message": "“Không bao giờ ghi nhớ lược sử” đã được kích hoạt."
   },
   "downloadOnlyDetailsESR": {
     "message": "Bạn đang sử dụng Firefox ESR."
   },
   "downloadOnlyDetailsNoUploadPref": {
@@ -142,23 +142,23 @@
   },
   "tourHeaderClickAndDrag": {
     "message": "Chỉ chụp những gì bạn muốn"
   },
   "tourBodyClickAndDrag": {
     "message": "Nhấp và kéo để chụp một phần của một trang. Bạn cũng có thể di chuột để làm nổi bật lựa chọn của bạn."
   },
   "tourHeaderFullPage": {
-    "message": "Chụp Windows hoặc Toàn bộ trang"
+    "message": "Chụp cửa sổ hoặc toàn bộ trang"
   },
   "tourBodyFullPage": {
     "message": "Chọn các nút ở phía trên bên phải để chụp khu vực nhìn thấy được trong cửa sổ hoặc để chụp toàn bộ trang."
   },
   "tourSkip": {
-    "message": "SKIP"
+    "message": "BỎ QUA"
   },
   "tourNext": {
     "message": "Slide tiếp theo"
   },
   "tourPrevious": {
     "message": "Slide trước đó"
   },
   "tourDone": {
--- a/browser/extensions/screenshots/_locales/zh_CN/messages.json
+++ b/browser/extensions/screenshots/_locales/zh_CN/messages.json
@@ -136,17 +136,17 @@
   },
   "tourHeaderPageAction": {
     "message": "新的保存方法"
   },
   "tourBodyPageAction": {
     "message": "随时可以展开地址栏中的页面操作菜单来截图。"
   },
   "tourHeaderClickAndDrag": {
-    "message": "截取你所需"
+    "message": "任您截取"
   },
   "tourBodyClickAndDrag": {
     "message": "单击并拖动以截取页面某个区域。您也可以把光标移到你要的地方,高亮后单击即可截图。"
   },
   "tourHeaderFullPage": {
     "message": "截取窗口或整个页面"
   },
   "tourBodyFullPage": {
--- a/browser/extensions/screenshots/background/main.js
+++ b/browser/extensions/screenshots/background/main.js
@@ -5,26 +5,19 @@
 this.main = (function() {
   const exports = {};
 
   const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl";
   const { sendEvent, incrementCount } = analytics;
 
   const manifest = browser.runtime.getManifest();
   let backend;
-  let _hasAnyShots = false;
-
-  startBackground.serverStatus.then((status) => {
-    _hasAnyShots = status.hasAny;
-  }).catch((e) => {
-    log.warn("Cannot see server status", e);
-  });
 
   exports.hasAnyShots = function() {
-    return _hasAnyShots;
+    return false;
   };
 
   let hasSeenOnboarding = browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
     const onboarded = !!result.hasSeenOnboarding;
     hasSeenOnboarding = Promise.resolve(onboarded);
     return hasSeenOnboarding;
   }).catch((error) => {
     log.error("Error getting hasSeenOnboarding:", error);
--- a/browser/extensions/screenshots/background/startBackground.js
+++ b/browser/extensions/screenshots/background/startBackground.js
@@ -5,26 +5,16 @@
      browser.runtime.onMessage
    and loads the rest of the background page in response to those events, forwarding
    the events to main.onClicked, main.onClickedContextMenu, or communication.onMessage
 */
 const startTime = Date.now();
 
 this.startBackground = (function() {
   const exports = {startTime};
-  // Wait until this many milliseconds to check the server for shots (for the purpose of migration warning):
-  const CHECK_SERVER_TIME = 5000; // 5 seconds
-  // If we want to pop open the tab showing the server status, wait this many milliseconds to open it:
-  const OPEN_SERVER_TAB_TIME = 5000; // 5 seconds
-  let hasSeenServerStatus = false;
-  let _resolveServerStatus;
-  exports.serverStatus = new Promise((resolve, reject) => {
-    _resolveServerStatus = {resolve, reject};
-  });
-  let backend;
 
   const backgroundScripts = [
     "log.js",
     "makeUuid.js",
     "catcher.js",
     "blobConverters.js",
     "background/selectorLoader.js",
     "background/communication.js",
@@ -109,89 +99,10 @@ this.startBackground = (function() {
           };
           document.head.appendChild(tag);
         });
       });
     });
     return loadedPromise;
   }
 
-  async function checkExpiration() {
-    const manifest = await browser.runtime.getManifest();
-    for (const permission of manifest.permissions) {
-      if (/^https?:\/\//.test(permission)) {
-        backend = permission.replace(/\/*$/, "");
-        break;
-      }
-    }
-    const result = await browser.storage.local.get(["registrationInfo", "hasSeenServerStatus", "hasShotsResponse"]);
-    hasSeenServerStatus = result.hasSeenServerStatus;
-    const { registrationInfo } = result;
-    if (!backend || !registrationInfo || !registrationInfo.registered) {
-      // The add-on hasn't been used, or at least no upload has occurred
-      _resolveServerStatus.resolve({hasIndefinite: false, hasAny: false});
-      return;
-    }
-    if (result.hasShotsResponse) {
-      // We've already retrieved information from the server
-      _resolveServerStatus.resolve(result.hasShotsResponse);
-      return;
-    }
-    const loginUrl = `${backend}/api/login`;
-    const hasShotsUrl = `${backend}/api/has-shots`;
-    try {
-      let resp = await fetch(loginUrl, {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-        },
-        body: JSON.stringify({
-          deviceId: registrationInfo.deviceId,
-          secret: registrationInfo.secret,
-        }),
-      });
-      if (!resp.ok) {
-        throw new Error(`Bad login response: ${resp.status}`);
-      }
-      const { authHeader } = await resp.json();
-      resp = await fetch(hasShotsUrl, {
-        method: "GET",
-        credentials: "include",
-        headers: Object.assign({}, authHeader, {
-          "Content-Type": "application/json",
-        }),
-      });
-      if (!resp.ok) {
-        throw new Error(`Bad response from server: ${resp.status}`);
-      }
-      const body = await resp.json();
-      browser.storage.local.set({hasShotsResponse: body});
-      _resolveServerStatus.resolve(body);
-    } catch (e) {
-      _resolveServerStatus.reject(e);
-    }
-  }
-
-  exports.serverStatus.then((status) => {
-    if (status.hasAny) {
-      browser.experiments.screenshots.initLibraryButton();
-    }
-    if (status.hasIndefinite && !hasSeenServerStatus) {
-      setTimeout(async () => {
-        await browser.tabs.create({
-          url: `${backend}/hosting-shutdown`,
-        });
-        hasSeenServerStatus = true;
-        await browser.storage.local.set({hasSeenServerStatus});
-      }, OPEN_SERVER_TAB_TIME);
-    }
-  }).catch((e) => {
-    console.error("Error finding Screenshots server status:", String(e), e.stack);
-  });
-
-  setTimeout(() => {
-    window.requestIdleCallback(() => {
-      checkExpiration();
-    });
-  }, CHECK_SERVER_TIME);
-
   return exports;
 })();
--- a/browser/extensions/screenshots/manifest.json
+++ b/browser/extensions/screenshots/manifest.json
@@ -1,12 +1,12 @@
 {
   "manifest_version": 2,
   "name": "Firefox Screenshots",
-  "version": "37.1.0",
+  "version": "39.0.0",
   "description": "__MSG_addonDescription__",
   "author": "__MSG_addonAuthorsList__",
   "homepage_url": "https://github.com/mozilla-services/screenshots",
   "incognito": "spanning",
   "applications": {
     "gecko": {
       "id": "screenshots@mozilla.org",
       "strict_min_version": "57.0a1"
--- a/browser/extensions/screenshots/moz.build
+++ b/browser/extensions/screenshots/moz.build
@@ -92,20 +92,16 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["_locales"]["dsb"] += [
   '_locales/dsb/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["_locales"]["el"] += [
   '_locales/el/messages.json'
 ]
 
-FINAL_TARGET_FILES.features['screenshots@mozilla.org']["_locales"]["en_GB"] += [
-  '_locales/en_GB/messages.json'
-]
-
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["_locales"]["en_US"] += [
   '_locales/en_US/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["_locales"]["eo"] += [
   '_locales/eo/messages.json'
 ]
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -114,45 +114,45 @@
 @media (-moz-mac-yosemite-theme: 0) {
   .titlebar-spacer[type="fullscreen-button"] {
     margin-right: 4px;
   }
 }
 
 /** End titlebar **/
 
-#main-window[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
+:root[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
   border-top: 1px solid rgba(0,0,0,0.65);
 }
 
 .browser-toolbar:not(.titlebar-color) {
   -moz-appearance: none;
   background: var(--toolbar-bgcolor);
   color: var(--toolbar-color);
 }
 
 /* Draw the bottom border of the tabs toolbar when it's not using
    -moz-appearance: toolbar. */
-#main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #nav-bar:not([tabs-hidden="true"]),
-#main-window:not([tabsintitlebar]) #nav-bar:not([tabs-hidden="true"]),
+:root:-moz-any([sizemode="fullscreen"],[customize-entered]) #nav-bar:not([tabs-hidden="true"]),
+:root:not([tabsintitlebar]) #nav-bar:not([tabs-hidden="true"]),
 #nav-bar:not([tabs-hidden="true"]):-moz-lwtheme {
   box-shadow: 0 calc(-1 * var(--tabs-navbar-shadow-size)) 0 var(--tabs-border-color);
 }
 
 /* Always draw a border on Yosemite to ensure the border is well-defined there
  * (the default border is too light). */
 @media (-moz-mac-yosemite-theme) {
   #navigator-toolbox:not(:-moz-lwtheme) {
     --tabs-border-color: rgba(0,0,0,.2);
   }
   #navigator-toolbox:not(:-moz-lwtheme):-moz-window-inactive {
     --tabs-border-color: rgba(0,0,0,.05);
   }
 
-  #main-window[tabsintitlebar] #nav-bar:not([tabs-hidden="true"]):not(:-moz-lwtheme) {
+  :root[tabsintitlebar] #nav-bar:not([tabs-hidden="true"]):not(:-moz-lwtheme) {
     box-shadow: 0 calc(-1 * var(--tabs-navbar-shadow-size)) 0 var(--tabs-border-color);
   }
 }
 
 #nav-bar:not([tabs-hidden="true"]) {
   /* The toolbar buttons that animate are only visible when the #TabsToolbar is not collapsed.
      The animations use position:absolute and require a positioned #nav-bar. */
   position: relative;
@@ -223,17 +223,17 @@
    * their fill-opacity of 0.7. calc() doesn't work here - we'd need
    * to multiply two unitless numbers and that's invalid in CSS, so
    * we need to hard code the value for now. */
   fill-opacity: 0.28;
 }
 
 /* Inactive elements are faded out on OSX */
 .toolbarbutton-1:not(:hover):-moz-window-inactive,
-#main-window:not([customizing]) .toolbarbutton-1:-moz-window-inactive[disabled="true"] {
+:root:not([customizing]) .toolbarbutton-1:-moz-window-inactive[disabled="true"] {
   opacity: 0.5;
 }
 
 /* ----- FULLSCREEN WINDOW CONTROLS ----- */
 
 #minimize-button,
 #close-button,
 #fullscreen-button ~ #window-controls > #restore-button {
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -81,40 +81,43 @@ function onRemoteDebuggerClientClosed() 
   window.AboutDebugging.onUSBRuntimesUpdated();
 }
 
 function onMultiE10sUpdated() {
   window.AboutDebugging.store.dispatch(updateMultiE10s());
 }
 
 function connectRuntime(id) {
+  // Create a random connection id to track the connection attempt in telemetry.
+  const connectionId = (Math.random() * 100000) | 0;
+
   return async (dispatch, getState) => {
-    dispatch({ type: CONNECT_RUNTIME_START, id });
+    dispatch({ type: CONNECT_RUNTIME_START, connectionId, id });
 
     // The preferences test-connection-timing-out-delay and test-connection-cancel-delay
     // don't have a default value but will be overridden during our tests.
     const connectionTimingOutDelay = Services.prefs.getIntPref(
       "devtools.aboutdebugging.test-connection-timing-out-delay",
       CONNECTION_TIMING_OUT_DELAY);
     const connectionCancelDelay = Services.prefs.getIntPref(
       "devtools.aboutdebugging.test-connection-cancel-delay", CONNECTION_CANCEL_DELAY);
 
     const connectionNotRespondingTimer = setTimeout(() => {
       // If connecting to the runtime takes time over CONNECTION_TIMING_OUT_DELAY,
       // we assume the connection prompt is showing on the runtime, show a dialog
       // to let user know that.
-      dispatch({ type: CONNECT_RUNTIME_NOT_RESPONDING, id });
+      dispatch({ type: CONNECT_RUNTIME_NOT_RESPONDING, connectionId, id });
     }, connectionTimingOutDelay);
     const connectionCancelTimer = setTimeout(() => {
       // Connect button of the runtime will be disabled during connection, but the status
       // continues till the connection was either succeed or failed. This may have a
       // possibility that the disabling continues unless page reloading, user will not be
       // able to click again. To avoid this, revert the connect button status after
       // CONNECTION_CANCEL_DELAY ms.
-      dispatch({ type: CONNECT_RUNTIME_CANCEL, id });
+      dispatch({ type: CONNECT_RUNTIME_CANCEL, connectionId, id });
     }, connectionCancelDelay);
 
     try {
       const runtime = findRuntimeById(id, getState().runtimes);
       const clientWrapper = await createClientForRuntime(runtime);
 
       const deviceDescription = await clientWrapper.getDeviceDescription();
       const compatibilityReport = await clientWrapper.checkVersionCompatibility();
@@ -178,24 +181,25 @@ function connectRuntime(id) {
       if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
         // `closed` event will be emitted when disabling remote debugging
         // on the connected remote runtime.
         clientWrapper.addOneTimeListener("closed", onRemoteDebuggerClientClosed);
       }
 
       dispatch({
         type: CONNECT_RUNTIME_SUCCESS,
+        connectionId,
         runtime: {
           id,
           runtimeDetails,
           type: runtime.type,
         },
       });
     } catch (e) {
-      dispatch({ type: CONNECT_RUNTIME_FAILURE, id, error: e });
+      dispatch({ type: CONNECT_RUNTIME_FAILURE, connectionId, id, error: e });
     } finally {
       clearTimeout(connectionNotRespondingTimer);
       clearTimeout(connectionCancelTimer);
     }
   };
 }
 
 function createThisFirefoxRuntime() {
--- a/devtools/client/aboutdebugging-new/src/middleware/event-recording.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/event-recording.js
@@ -5,16 +5,20 @@
 "use strict";
 
 const Telemetry = require("devtools/client/shared/telemetry");
 loader.lazyGetter(this, "telemetry", () => new Telemetry());
 // This is a unique id that should be submitted with all about:debugging events.
 loader.lazyGetter(this, "sessionId", () => parseInt(telemetry.msSinceProcessStart(), 10));
 
 const {
+  CONNECT_RUNTIME_CANCEL,
+  CONNECT_RUNTIME_FAILURE,
+  CONNECT_RUNTIME_NOT_RESPONDING,
+  CONNECT_RUNTIME_START,
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_SUCCESS,
   REMOTE_RUNTIMES_UPDATED,
   RUNTIMES,
   SELECT_PAGE_SUCCESS,
   SHOW_PROFILER_DIALOG,
   TELEMETRY_RECORD,
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
@@ -141,23 +145,51 @@ function onRemoteRuntimesUpdated(action,
       recordEvent("device_added", {
         "connection_type": action.runtimeType,
         "device_name": newDeviceName,
       });
     }
   }
 }
 
+function recordConnectionAttempt(connectionId, runtimeId, status, store) {
+  const runtime = findRuntimeById(runtimeId, store.getState().runtimes);
+  if (runtime.type === RUNTIMES.THIS_FIREFOX) {
+    // Only record connection_attempt events for remote runtimes.
+    return;
+  }
+
+  recordEvent("connection_attempt", {
+    "connection_id": connectionId,
+    "connection_type": runtime.type,
+    "runtime_id": getTelemetryRuntimeId(runtimeId),
+    "status": status,
+  });
+}
+
 /**
  * This middleware will record events to telemetry for some specific actions.
  */
 function eventRecordingMiddleware(store) {
   return next => action => {
     switch (action.type) {
+      case CONNECT_RUNTIME_CANCEL:
+        recordConnectionAttempt(action.connectionId, action.id, "cancelled", store);
+        break;
+      case CONNECT_RUNTIME_FAILURE:
+        recordConnectionAttempt(action.connectionId, action.id, "failed", store);
+        break;
+      case CONNECT_RUNTIME_NOT_RESPONDING:
+        recordConnectionAttempt(action.connectionId, action.id, "not responding", store);
+        break;
+      case CONNECT_RUNTIME_START:
+        recordConnectionAttempt(action.connectionId, action.id, "start", store);
+        break;
       case CONNECT_RUNTIME_SUCCESS:
+        recordConnectionAttempt(action.connectionId, action.runtime.id, "success", store);
         onConnectRuntimeSuccess(action, store);
         break;
       case DISCONNECT_RUNTIME_SUCCESS:
         onDisconnectRuntimeSuccess(action, store);
         break;
       case REMOTE_RUNTIMES_UPDATED:
         onRemoteRuntimesUpdated(action, store);
         break;
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -105,16 +105,17 @@ skip-if = debug || asan # Frequent inter
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_runtime_select.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 [browser_aboutdebugging_sidebar_usb_unavailable_runtime.js]
 [browser_aboutdebugging_sidebar_usb_unplugged_device.js]
 [browser_aboutdebugging_hidden_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
 [browser_aboutdebugging_telemetry_basic.js]
+[browser_aboutdebugging_telemetry_connection_attempt.js]
 [browser_aboutdebugging_telemetry_inspect.js]
 [browser_aboutdebugging_telemetry_navigate.js]
 [browser_aboutdebugging_telemetry_runtime_actions.js]
 [browser_aboutdebugging_telemetry_runtime_connected_details.js]
 [browser_aboutdebugging_telemetry_runtime_updates.js]
 [browser_aboutdebugging_telemetry_runtime_updates_multi.js]
 [browser_aboutdebugging_telemetry_runtime_updates_network.js]
 [browser_aboutdebugging_thisfirefox.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_connection_attempt.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-telemetry.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-telemetry.js", this);
+
+const USB_RUNTIME = {
+  id: "runtime-id-1",
+  deviceName: "Device A",
+  name: "Runtime 1",
+  shortName: "R1",
+};
+
+/**
+ * Check that telemetry events for connection attempts are correctly recorded in various
+ * scenarios:
+ * - successful connection
+ * - successful connection after showing the timeout warning
+ * - failed connection
+ * - connection timeout
+ */
+add_task(async function testSuccessfulConnectionAttempt() {
+  const { doc, mocks, runtimeId, sessionId, tab } = await setupConnectionAttemptTest();
+
+  await connectToRuntime(USB_RUNTIME.deviceName, doc);
+
+  const connectionEvents = checkTelemetryEvents([
+    { method: "runtime_connected", extras: { runtime_id: runtimeId } },
+    { method: "connection_attempt", extras: getEventExtras("start", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("success", runtimeId) },
+  ], sessionId).filter(({method}) => method === "connection_attempt");
+
+  checkConnectionId(connectionEvents);
+
+  await removeUsbRuntime(USB_RUNTIME, mocks, doc);
+  await removeTab(tab);
+});
+
+add_task(async function testFailedConnectionAttempt() {
+  const { doc, mocks, runtimeId, sessionId, tab } = await setupConnectionAttemptTest();
+  mocks.runtimeClientFactoryMock.createClientForRuntime = async (runtime) => {
+    throw new Error("failed");
+  };
+
+  info("Try to connect to the runtime and wait for the connection error message");
+  const usbRuntimeSidebarItem = findSidebarItemByText(USB_RUNTIME.deviceName, doc);
+  const connectButton = usbRuntimeSidebarItem.querySelector(".qa-connect-button");
+  connectButton.click();
+  await waitUntil(() => usbRuntimeSidebarItem.querySelector(".qa-connection-error"));
+
+  const connectionEvents = checkTelemetryEvents([
+    { method: "connection_attempt", extras: getEventExtras("start", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("failed", runtimeId) },
+  ], sessionId).filter(({method}) => method === "connection_attempt");
+
+  checkConnectionId(connectionEvents);
+
+  await removeUsbRuntime(USB_RUNTIME, mocks, doc);
+  await removeTab(tab);
+});
+
+add_task(async function testPendingConnectionAttempt() {
+  info("Set timeout preferences to avoid cancelling the connection");
+  await pushPref("devtools.aboutdebugging.test-connection-timing-out-delay", 100);
+  await pushPref("devtools.aboutdebugging.test-connection-cancel-delay", 100000);
+
+  const { doc, mocks, runtimeId, sessionId, tab } = await setupConnectionAttemptTest();
+
+  info("Simulate a pending connection");
+  let resumeConnection;
+  const resumeConnectionPromise = new Promise(r => {
+    resumeConnection = r;
+  });
+  mocks.runtimeClientFactoryMock.createClientForRuntime = async (runtime) => {
+    await resumeConnectionPromise;
+    return mocks._clients[runtime.type][runtime.id];
+  };
+
+  info("Click on the connect button and wait for the warning message");
+  const usbRuntimeSidebarItem = findSidebarItemByText(USB_RUNTIME.deviceName, doc);
+  const connectButton = usbRuntimeSidebarItem.querySelector(".qa-connect-button");
+  connectButton.click();
+  await waitUntil(() => doc.querySelector(".qa-connection-not-responding"));
+
+  info("Resume the connection and wait for the connection to succeed");
+  resumeConnection();
+  await waitUntil(() => !usbRuntimeSidebarItem.querySelector(".qa-connect-button"));
+
+  const connectionEvents = checkTelemetryEvents([
+    { method: "runtime_connected", extras: { runtime_id: runtimeId } },
+    { method: "connection_attempt", extras: getEventExtras("start", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("not responding", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("success", runtimeId) },
+  ], sessionId).filter(({method}) => method === "connection_attempt");
+  checkConnectionId(connectionEvents);
+
+  await removeUsbRuntime(USB_RUNTIME, mocks, doc);
+  await removeTab(tab);
+});
+
+add_task(async function testCancelledConnectionAttempt() {
+  info("Set timeout preferences to quickly cancel the connection");
+  await pushPref("devtools.aboutdebugging.test-connection-timing-out-delay", 100);
+  await pushPref("devtools.aboutdebugging.test-connection-cancel-delay", 1000);
+
+  const { doc, mocks, runtimeId, sessionId, tab } = await setupConnectionAttemptTest();
+
+  info("Simulate a connection timeout");
+  mocks.runtimeClientFactoryMock.createClientForRuntime = async (runtime) => {
+    await new Promise(r => {});
+  };
+
+  info("Click on the connect button and wait for the error message");
+  const usbRuntimeSidebarItem = findSidebarItemByText(USB_RUNTIME.deviceName, doc);
+  const connectButton = usbRuntimeSidebarItem.querySelector(".qa-connect-button");
+  connectButton.click();
+  await waitUntil(() => usbRuntimeSidebarItem.querySelector(".qa-connection-timeout"));
+
+  const connectionEvents = checkTelemetryEvents([
+    { method: "connection_attempt", extras: getEventExtras("start", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("not responding", runtimeId) },
+    { method: "connection_attempt", extras: getEventExtras("cancelled", runtimeId) },
+  ], sessionId).filter(({method}) => method === "connection_attempt");
+  checkConnectionId(connectionEvents);
+
+  await removeUsbRuntime(USB_RUNTIME, mocks, doc);
+  await removeTab(tab);
+});
+
+function checkConnectionId(connectionEvents) {
+  const connectionId = connectionEvents[0].extras.connection_id;
+  ok(!!connectionId, "Found a valid connection id in the first connection_attempt event");
+  for (const evt of connectionEvents) {
+    is(evt.extras.connection_id, connectionId,
+      "All connection_attempt events share the same connection id");
+  }
+}
+
+// Small helper to create the expected event extras object for connection_attempt events
+function getEventExtras(status, runtimeId) {
+  return {
+    connection_type: "usb",
+    runtime_id: runtimeId,
+    status,
+  };
+}
+
+// Open about:debugging, setup telemetry, mocks and create a mocked USB runtime.
+async function setupConnectionAttemptTest() {
+  const mocks = new Mocks();
+  setupTelemetryTest();
+
+  const { tab, document } = await openAboutDebugging();
+
+  const sessionId = getOpenEventSessionId();
+  ok(!isNaN(sessionId), "Open event has a valid session id");
+
+  mocks.createUSBRuntime(USB_RUNTIME.id, {
+    deviceName: USB_RUNTIME.deviceName,
+    name: USB_RUNTIME.name,
+    shortName: USB_RUNTIME.shortName,
+  });
+  mocks.emitUSBUpdate();
+
+  info("Wait for the runtime to appear in the sidebar");
+  await waitUntil(() => findSidebarItemByText(USB_RUNTIME.shortName, document));
+  const evts = checkTelemetryEvents([
+    { method: "device_added", extras: {} },
+    { method: "runtime_added", extras: {} },
+  ], sessionId);
+
+  const runtimeId = evts.filter(e => e.method === "runtime_added")[0].extras.runtime_id;
+  return { doc: document, mocks, runtimeId, sessionId, tab };
+}
+
+async function removeUsbRuntime(runtime, mocks, doc) {
+  mocks.removeRuntime(runtime.id);
+  mocks.emitUSBUpdate();
+  await waitUntil(() => !findSidebarItemByText(runtime.name, doc) &&
+                        !findSidebarItemByText(runtime.shortName, doc));
+}
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates.js
@@ -72,16 +72,18 @@ add_task(async function testUsbRuntimeUp
   const runtime1ConnectedExtras = Object.assign({}, runtime1Extras, {
     "runtime_name": USB_RUNTIME_1.name,
   });
 
   await connectToRuntime(USB_RUNTIME_1.deviceName, document);
 
   checkTelemetryEvents([
     { method: "runtime_connected", extras: runtime1ConnectedExtras },
+    { method: "connection_attempt", extras: { status: "start" } },
+    { method: "connection_attempt", extras: { status: "success" } },
   ], sessionId);
 
   info("Add a second runtime");
   await addUsbRuntime(USB_RUNTIME_2, mocks, document);
   evts = checkTelemetryEvents([
     { method: "runtime_added", extras: RUNTIME_2_EXTRAS },
   ], sessionId);
 
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates_network.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates_network.js
@@ -45,16 +45,18 @@ add_task(async function testNetworkRunti
   // device_added event.
   checkTelemetryEvents([
     { method: "runtime_added", extras: networkRuntimeExtras },
   ], sessionId);
 
   await connectToRuntime(NETWORK_RUNTIME.host, document);
   checkTelemetryEvents([
     { method: "runtime_connected", extras: connectedNetworkRuntimeExtras },
+    { method: "connection_attempt", extras: { status: "start" } },
+    { method: "connection_attempt", extras: { status: "success" } },
   ], sessionId);
 
   info("Remove network runtime");
   mocks.removeRuntime(NETWORK_RUNTIME.host);
   await waitUntil(() => !findSidebarItemByText(NETWORK_RUNTIME.host, document));
   // Similarly we should not have any device removed event.
   checkTelemetryEvents([
     { method: "runtime_disconnected", extras: connectedNetworkRuntimeExtras },
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -171,16 +171,35 @@ body {
   display: flex;
   flex-wrap: nowrap;
   flex-direction: row;
   align-items: center;
   white-space: nowrap;
   margin-inline-end: 5px;
 }
 
+#audit-progress-container {
+  position: fixed;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  z-index: 9999;
+  background: rgba(255,255,255,0.9);
+  padding-block-start: 30vh;
+  font: message-box;
+  font-size: 12px;
+  font-style: italic;
+}
+
+.audit-progress-progressbar {
+  width: 30vw;
+}
+
 /* Description */
 .description {
   color: var(--theme-toolbar-color);
   font: message-box;
   font-size: calc(var(--accessibility-font-size) + 1px);
   margin: auto;
   padding-top: 15vh;
   width: 50vw;
--- a/devtools/client/accessibility/actions/audit.js
+++ b/devtools/client/accessibility/actions/audit.js
@@ -1,23 +1,39 @@
 /* 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 { AUDIT, AUDITING, FILTER_TOGGLE } = require("../constants");
+const { AUDIT, AUDIT_PROGRESS, AUDITING, FILTER_TOGGLE } = require("../constants");
 
 exports.filterToggle = filter =>
   dispatch => dispatch({ filter, type: FILTER_TOGGLE });
 
 exports.auditing = filter =>
   dispatch => dispatch({ auditing: filter, type: AUDITING });
 
 exports.audit = (walker, filter) =>
-  dispatch => {
-    const onAuditEvent = walker.once("audit-event");
+  dispatch => new Promise(resolve => {
+    const auditEventHandler = ({ type, ancestries, progress }) => {
+      switch (type) {
+        case "error":
+          walker.off("audit-event", auditEventHandler);
+          dispatch({ type: AUDIT, error: true });
+          resolve();
+          break;
+        case "completed":
+          walker.off("audit-event", auditEventHandler);
+          dispatch({ type: AUDIT, response: ancestries });
+          resolve();
+          break;
+        case "progress":
+          dispatch({ type: AUDIT_PROGRESS, progress });
+          break;
+        default:
+          break;
+      }
+    };
+
+    walker.on("audit-event", auditEventHandler);
     walker.startAudit();
-    return onAuditEvent
-      .then(({ ancestries: response, error }) =>
-        dispatch({ type: AUDIT, error, response }))
-      .catch(error => dispatch({ type: AUDIT, error }));
-  };
+  });
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/AuditProgressOverlay.js
@@ -0,0 +1,79 @@
+/* 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 React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { PluralForm } = require("devtools/shared/plural-form");
+
+const { L10N } = require("../utils/l10n");
+
+/**
+ * Helper functional component to render an accessible text progressbar.
+ * @param {Object} props
+ *        - id for the progressbar element
+ *        - valuetext for the progressbar element
+ */
+function TextProgressBar({ id, textStringKey }) {
+  const text = L10N.getStr(textStringKey);
+  return ReactDOM.span({
+    id,
+    key: id,
+    role: "progressbar",
+    "aria-valuetext": text,
+  },
+    text);
+}
+
+class AuditProgressOverlay extends React.Component {
+  static get propTypes() {
+    return {
+      auditing: PropTypes.array,
+      total: PropTypes.number,
+      percentage: PropTypes.number,
+    };
+  }
+
+  render() {
+    const { auditing, percentage, total } = this.props;
+    if (!auditing) {
+      return null;
+    }
+
+    const id = "audit-progress-container";
+
+    if (total == null) {
+      return TextProgressBar({id, textStringKey: "accessibility.progress.initializing"});
+    }
+
+    if (percentage === 100) {
+      return TextProgressBar({id, textStringKey: "accessibility.progress.finishing"});
+    }
+
+    const progressbarString = PluralForm.get(total,
+      L10N.getStr("accessibility.progress.progressbar"));
+
+    return ReactDOM.span({
+      id,
+      key: id,
+    },
+      progressbarString.replace("#1", total),
+      ReactDOM.progress({
+        max: 100,
+        value: percentage,
+        className: "audit-progress-progressbar",
+        "aria-labelledby": id,
+      }));
+  }
+}
+
+const mapStateToProps = ({ audit: { auditing, progress }}) => {
+  const { total, percentage } = progress || {};
+  return { auditing, total, percentage };
+};
+
+module.exports = connect(mapStateToProps)(AuditProgressOverlay);
--- a/devtools/client/accessibility/components/MainFrame.js
+++ b/devtools/client/accessibility/components/MainFrame.js
@@ -2,42 +2,44 @@
  * 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";
 
 /* global gToolbox */
 
 // React & Redux
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
-const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+const { span, div } = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { reset } = require("../actions/ui");
 
 // Constants
 const { SIDEBAR_WIDTH, PORTRAIT_MODE_WIDTH } = require("../constants");
 
 // Accessibility Panel
 const AccessibilityTree = createFactory(require("./AccessibilityTree"));
+const AuditProgressOverlay = createFactory(require("./AuditProgressOverlay"));
 const Description = createFactory(require("./Description").Description);
 const RightSidebar = createFactory(require("./RightSidebar"));
 const Toolbar = createFactory(require("./Toolbar"));
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
 
 /**
  * Renders basic layout of the Accessibility panel. The Accessibility panel
  * content consists of two main parts: tree and sidebar.
  */
 class MainFrame extends Component {
   static get propTypes() {
     return {
       accessibility: PropTypes.object.isRequired,
       walker: PropTypes.object.isRequired,
       enabled: PropTypes.bool.isRequired,
       dispatch: PropTypes.func.isRequired,
+      auditing: PropTypes.string,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.resetAccessibility = this.resetAccessibility.bind(this);
     this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
@@ -84,41 +86,48 @@ class MainFrame extends Component {
       this.refs.splitBox.setState({ vert: this.useLandscapeMode });
     }
   }
 
   /**
    * Render Accessibility panel content
    */
   render() {
-    const { accessibility, walker, enabled } = this.props;
+    const { accessibility, walker, enabled, auditing } = this.props;
 
     if (!enabled) {
       return Description({ accessibility });
     }
 
     return (
       div({ className: "mainFrame", role: "presentation" },
         Toolbar({ accessibility, walker }),
-        SplitBox({
-          ref: "splitBox",
-          initialSize: SIDEBAR_WIDTH,
-          minSize: "10px",
-          maxSize: "80%",
-          splitterSize: 1,
-          endPanelControl: true,
-          startPanel: div({
-            className: "main-panel",
-            role: "presentation",
-          }, AccessibilityTree({ walker })),
-          endPanel: RightSidebar({ walker }),
-          vert: this.useLandscapeMode,
-        })
+        auditing && AuditProgressOverlay(),
+        span({
+          "aria-hidden": !!auditing,
+          role: "presentation",
+          style: { display: "contents" },
+        },
+          SplitBox({
+            ref: "splitBox",
+            initialSize: SIDEBAR_WIDTH,
+            minSize: "10px",
+            maxSize: "80%",
+            splitterSize: 1,
+            endPanelControl: true,
+            startPanel: div({
+              className: "main-panel",
+              role: "presentation",
+            }, AccessibilityTree({ walker })),
+            endPanel: RightSidebar({ walker }),
+            vert: this.useLandscapeMode,
+          })),
       ));
   }
 }
 
-const mapStateToProps = ({ ui }) => ({
+const mapStateToProps = ({ ui, audit: { auditing } }) => ({
   enabled: ui.enabled,
+  auditing,
 });
 
 // Exports from this module
 module.exports = connect(mapStateToProps)(MainFrame);
--- a/devtools/client/accessibility/components/moz.build
+++ b/devtools/client/accessibility/components/moz.build
@@ -5,16 +5,17 @@
 DevToolsModules(
     'AccessibilityRow.js',
     'AccessibilityRowValue.js',
     'AccessibilityTree.js',
     'AccessibilityTreeFilter.js',
     'Accessible.js',
     'AuditController.js',
     'AuditFilter.js',
+    'AuditProgressOverlay.js',
     'Badge.js',
     'Badges.js',
     'Button.js',
     'Checks.js',
     'ColorContrastAccessibility.js',
     'ContrastBadge.js',
     'Description.js',
     'LearnMoreLink.js',
--- a/devtools/client/accessibility/constants.js
+++ b/devtools/client/accessibility/constants.js
@@ -29,16 +29,17 @@ exports.HIGHLIGHT = "HIGHLIGHT";
 exports.UNHIGHLIGHT = "UNHIGHLIGHT";
 exports.ENABLE = "ENABLE";
 exports.DISABLE = "DISABLE";
 exports.UPDATE_CAN_BE_DISABLED = "UPDATE_CAN_BE_DISABLED";
 exports.UPDATE_CAN_BE_ENABLED = "UPDATE_CAN_BE_ENABLED";
 exports.FILTER_TOGGLE = "FILTER_TOGGLE";
 exports.AUDIT = "AUDIT";
 exports.AUDITING = "AUDITING";
+exports.AUDIT_PROGRESS = "AUDIT_PROGRESS";
 
 // List of filters for accessibility checks.
 exports.FILTERS = {
   [AUDIT_TYPE.CONTRAST]: "CONTRAST",
 };
 
 // Ordered accessible properties to be displayed by the accessible component.
 exports.ORDERED_PROPS = [
--- a/devtools/client/accessibility/reducers/audit.js
+++ b/devtools/client/accessibility/reducers/audit.js
@@ -1,31 +1,33 @@
 /* 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 {
   AUDIT,
   AUDITING,
+  AUDIT_PROGRESS,
   FILTER_TOGGLE,
   FILTERS,
   RESET,
   SELECT,
 } = require("../constants");
 
 /**
  * Initial state definition
  */
 function getInitialState() {
   return {
     filters: {
       [FILTERS.CONTRAST]: false,
     },
     auditing: null,
+    progress: null,
   };
 }
 
 function audit(state = getInitialState(), action) {
   switch (action.type) {
     case FILTER_TOGGLE:
       const { filter } = action;
       let { filters } = state;
@@ -45,16 +47,24 @@ function audit(state = getInitialState()
       return {
         ...state,
         auditing,
       };
     case AUDIT:
       return {
         ...state,
         auditing: null,
+        progress: null,
+      };
+    case AUDIT_PROGRESS:
+      const { progress } = action;
+
+      return {
+        ...state,
+        progress,
       };
     case SELECT:
     case RESET:
       return getInitialState();
     default:
       return state;
   }
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/jest/components/__snapshots__/audit-progress-overlay.test.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AuditProgressOverlay component: render auditing finishing up 1`] = `"<span id=\\"audit-progress-container\\" role=\\"progressbar\\" aria-valuetext=\\"accessibility.progress.finishing\\">accessibility.progress.finishing</span>"`;
+
+exports[`AuditProgressOverlay component: render auditing initializing 1`] = `"<span id=\\"audit-progress-container\\" role=\\"progressbar\\" aria-valuetext=\\"accessibility.progress.initializing\\">accessibility.progress.initializing</span>"`;
+
+exports[`AuditProgressOverlay component: render auditing progress 1`] = `"<span id=\\"audit-progress-container\\">accessibility.progress.progressbar<progress max=\\"100\\" value=\\"0\\" class=\\"audit-progress-progressbar\\" aria-labelledby=\\"audit-progress-container\\"></progress></span>"`;
+
+exports[`AuditProgressOverlay component: render auditing progress 2`] = `"<span id=\\"audit-progress-container\\">accessibility.progress.progressbar<progress max=\\"100\\" value=\\"50\\" class=\\"audit-progress-progressbar\\" aria-labelledby=\\"audit-progress-container\\"></progress></span>"`;
+
+exports[`AuditProgressOverlay component: render auditing progress 3`] = `"<span id=\\"audit-progress-container\\">accessibility.progress.progressbar<progress max=\\"100\\" value=\\"75\\" class=\\"audit-progress-progressbar\\" aria-labelledby=\\"audit-progress-container\\"></progress></span>"`;
+
+exports[`AuditProgressOverlay component: render not auditing 1`] = `null`;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/jest/components/audit-progress-overlay.test.js
@@ -0,0 +1,116 @@
+/* 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 { mount } = require("enzyme");
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const { setupStore } = require("devtools/client/accessibility/test/jest/helpers");
+
+const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
+const { AUDIT_PROGRESS } = require("devtools/client/accessibility/constants");
+
+const ConnectedAuditProgressOverlayClass =
+  require("devtools/client/accessibility/components/AuditProgressOverlay");
+const AuditProgressOverlayClass = ConnectedAuditProgressOverlayClass.WrappedComponent;
+const AuditProgressOverlay = createFactory(ConnectedAuditProgressOverlayClass);
+
+function testTextProgressBar(store, expectedText) {
+  const wrapper = mount(Provider({ store }, AuditProgressOverlay()));
+  expect(wrapper.html()).toMatchSnapshot();
+
+  const overlay = wrapper.find(AuditProgressOverlayClass);
+  expect(overlay.children().length).toBe(1);
+
+  const overlayText = overlay.childAt(0);
+  expect(overlayText.type()).toBe("span");
+  expect(overlayText.prop("id")).toBe("audit-progress-container");
+  expect(overlayText.prop("role")).toBe("progressbar");
+  expect(overlayText.prop("aria-valuetext")).toBe(expectedText);
+  expect(overlayText.text()).toBe(expectedText);
+}
+
+function testProgress(wrapper, percentage) {
+  const progress = wrapper.find("progress");
+  expect(progress.prop("max")).toBe(100);
+  expect(progress.prop("value")).toBe(percentage);
+  expect(progress.hasClass("audit-progress-progressbar")).toBe(true);
+  expect(progress.prop("aria-labelledby")).toBe("audit-progress-container");
+}
+
+describe("AuditProgressOverlay component:", () => {
+  it("render not auditing", () => {
+    const store = setupStore();
+    const wrapper = mount(Provider({ store }, AuditProgressOverlay()));
+    expect(wrapper.html()).toMatchSnapshot();
+    expect(wrapper.isEmptyRender()).toBe(true);
+  });
+
+  it("render auditing initializing", () => {
+    const store = setupStore({
+      preloadedState: { audit: { auditing: AUDIT_TYPE.CONTRAST } },
+    });
+
+    testTextProgressBar(store, "accessibility.progress.initializing");
+  });
+
+  it("render auditing progress", () => {
+    const store = setupStore({
+      preloadedState: {
+        audit: {
+          auditing: AUDIT_TYPE.CONTRAST,
+          progress: { total: 5, percentage: 0 },
+        },
+      },
+    });
+
+    const wrapper = mount(Provider({ store }, AuditProgressOverlay()));
+    expect(wrapper.html()).toMatchSnapshot();
+
+    const overlay = wrapper.find(AuditProgressOverlayClass);
+    expect(overlay.children().length).toBe(1);
+
+    const overlayContainer = overlay.childAt(0);
+    expect(overlayContainer.type()).toBe("span");
+    expect(overlayContainer.prop("id")).toBe("audit-progress-container");
+    expect(overlayContainer.children().length).toBe(1);
+
+    expect(overlayContainer.text()).toBe("accessibility.progress.progressbar");
+    expect(overlayContainer.childAt(0).type()).toBe("progress");
+
+    testProgress(wrapper, 0);
+
+    store.dispatch({
+      type: AUDIT_PROGRESS,
+      progress: { total: 5, percentage: 50 },
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    testProgress(wrapper, 50);
+
+    store.dispatch({
+      type: AUDIT_PROGRESS,
+      progress: { total: 5, percentage: 75 },
+    });
+    wrapper.update();
+
+    expect(wrapper.html()).toMatchSnapshot();
+    testProgress(wrapper, 75);
+  });
+
+  it("render auditing finishing up", () => {
+    const store = setupStore({
+      preloadedState: {
+        audit: {
+          auditing: AUDIT_TYPE.CONTRAST,
+          progress: { total: 5, percentage: 100 },
+        },
+      },
+    });
+
+    testTextProgressBar(store, "accessibility.progress.finishing");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/jest/fixtures/plural-form.js
@@ -0,0 +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";
+
+module.exports.PluralForm = {
+  get(num, str) {
+    return str;
+  },
+};
--- a/devtools/client/accessibility/test/jest/jest.config.js
+++ b/devtools/client/accessibility/test/jest/jest.config.js
@@ -7,16 +7,17 @@
 /* global __dirname */
 
 module.exports = {
   verbose: true,
   moduleNameMapper: {
     // Custom name mappers for modules that require m-c specific API.
     "^../utils/l10n": `${__dirname}/fixtures/l10n`,
     "^devtools/client/shared/link": `${__dirname}/fixtures/stub`,
+    "^devtools/shared/plural-form": `${__dirname}/fixtures/plural-form`,
     "^devtools/client/shared/components/tree/TreeView": `${__dirname}/fixtures/stub`,
     "^Services": `${__dirname}/fixtures/Services`,
     // Map all require("devtools/...") to the real devtools root.
     "^devtools\\/(.*)": `${__dirname}/../../../../$1`,
   },
   setupFiles: [
     "<rootDir>setup.js",
   ],
--- a/devtools/client/debugger/packages/devtools-reps/src/reps/rep-utils.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/reps/rep-utils.js
@@ -7,17 +7,17 @@ const validProtocols = /(http|https|ftp|
 
 // URL Regex, common idioms:
 //
 // Lead-in (URL):
 // (                     Capture because we need to know if there was a lead-in
 //                       character so we can include it as part of the text
 //                       preceding the match. We lack look-behind matching.
 //  ^|                   The URL can start at the beginning of the string.
-//  [\s(,;'"`]           Or whitespace or some punctuation that does not imply
+//  [\s(,;'"`“]          Or whitespace or some punctuation that does not imply
 //                       a context which would preclude a URL.
 // )
 //
 // We do not need a trailing look-ahead because our regex's will terminate
 // because they run out of characters they can eat.
 
 // What we do not attempt to have the regexp do:
 // - Avoid trailing '.' and ')' characters.  We let our greedy match absorb
@@ -48,17 +48,17 @@ const validProtocols = /(http|https|ftp|
 //                       found here: http://www.iana.org/domains/root/db
 //  )
 //  [-\w.!~*'();,/?:@&=+$#%]*
 //                       path onwards. We allow the set of characters that
 //                       encodeURI does not escape plus the result of escaping
 //                       (so also '%')
 // )
 // eslint-disable-next-line max-len
-const urlRegex = /(^|[\s(,;'"`])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
+const urlRegex = /(^|[\s(,;'"`“])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
 
 // Set of terminators that are likely to have been part of the context rather
 // than part of the URL and so should be uneaten. This is '(', ',', ';', plus
 // quotes and question end-ing punctuation and the potential permutations with
 // parentheses (english-specific).
 const uneatLastUrlCharsRegex = /(?:[),;.!?`'"]|[.!?]\)|\)[.!?])$/;
 
 const ELLIPSIS = "\u2026";
--- a/devtools/client/debugger/packages/devtools-reps/src/reps/string.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/reps/string.js
@@ -24,29 +24,31 @@ const { a, span } = dom;
 /**
  * Renders a string. String value is enclosed within quotes.
  */
 StringRep.propTypes = {
   useQuotes: PropTypes.bool,
   escapeWhitespace: PropTypes.bool,
   style: PropTypes.object,
   cropLimit: PropTypes.number.isRequired,
+  urlCropLimit: PropTypes.number,
   member: PropTypes.object,
   object: PropTypes.object.isRequired,
   openLink: PropTypes.func,
   className: PropTypes.string,
   title: PropTypes.string,
   isInContentPage: PropTypes.bool
 };
 
 function StringRep(props) {
   const {
     className,
     style,
     cropLimit,
+    urlCropLimit,
     object,
     useQuotes = true,
     escapeWhitespace = true,
     member,
     openLink,
     title,
     isInContentPage
   } = props;
@@ -86,22 +88,23 @@ function StringRep(props) {
     actor: object.actor,
     title
   });
 
   if (!isLong) {
     if (containsURL(text)) {
       return span(
         config,
-        ...getLinkifiedElements(
+        getLinkifiedElements({
           text,
-          shouldCrop && cropLimit,
+          cropLimit: shouldCrop ? cropLimit : null,
+          urlCropLimit,
           openLink,
           isInContentPage
-        )
+        })
       );
     }
 
     // Cropping of longString has been handled before formatting.
     text = maybeCropString(
       {
         isLong,
         shouldCrop,
@@ -167,24 +170,34 @@ function maybeCropString(opts, text) {
 
   return shouldCrop ? rawCropString(text, cropLimit) : text;
 }
 
 /**
  * Get an array of the elements representing the string, cropped if needed,
  * with actual links.
  *
- * @param {String} text: The actual string to linkify.
- * @param {Integer | null} cropLimit
- * @param {Function} openLink: Function handling the link opening.
- * @param {Boolean} isInContentPage: pass true if the reps is rendered in
- *                                   the content page (e.g. in JSONViewer).
+ * @param {Object} An options object of the following shape:
+ *                 - text {String}: The actual string to linkify.
+ *                 - cropLimit {Integer}: The limit to apply on the whole text.
+ *                 - urlCropLimit {Integer}: The limit to apply on each URL.
+ *                 - openLink {Function} openLink: Function handling the link
+ *                                                 opening.
+ *                 - isInContentPage {Boolean}: pass true if the reps is
+ *                                              rendered in the content page
+ *                                              (e.g. in JSONViewer).
  * @returns {Array<String|ReactElement>}
  */
-function getLinkifiedElements(text, cropLimit, openLink, isInContentPage) {
+function getLinkifiedElements({
+  text,
+  cropLimit,
+  urlCropLimit,
+  openLink,
+  isInContentPage
+}) {
   const halfLimit = Math.ceil((cropLimit - ELLIPSIS.length) / 2);
   const startCropIndex = cropLimit ? halfLimit : null;
   const endCropIndex = cropLimit ? text.length - halfLimit : null;
 
   const items = [];
   let currentIndex = 0;
   let contentStart;
   while (true) {
@@ -206,27 +219,38 @@ function getLinkifiedElements(text, crop
     // URL.
     let useUrl = url[2];
     const uneat = uneatLastUrlCharsRegex.exec(useUrl);
     if (uneat) {
       useUrl = useUrl.substring(0, uneat.index);
     }
 
     currentIndex = currentIndex + contentStart;
-    const linkText = getCroppedString(
+    let linkText = getCroppedString(
       useUrl,
       currentIndex,
       startCropIndex,
       endCropIndex
     );
 
     if (linkText) {
+      if (urlCropLimit && useUrl.length > urlCropLimit) {
+        const urlCropHalf = Math.ceil((urlCropLimit - ELLIPSIS.length) / 2);
+        linkText = getCroppedString(
+          useUrl,
+          0,
+          urlCropHalf,
+          useUrl.length - urlCropHalf
+        );
+      }
+
       items.push(
         a(
           {
+            key: `${useUrl}-${currentIndex}`,
             className: "url",
             title: useUrl,
             draggable: false,
             // Because we don't want the link to be open in the current
             // panel's frame, we only render the href attribute if `openLink`
             // exists (so we can preventDefault) or if the reps will be
             // displayed in content page (e.g. in the JSONViewer).
             href: openLink || isInContentPage ? useUrl : null,
--- a/devtools/client/debugger/packages/devtools-reps/src/reps/tests/string-with-url.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/reps/tests/string-with-url.js
@@ -381,37 +381,111 @@ describe("test String with URL", () => {
     const linkFr = element.find("a").at(0);
     expect(linkFr.prop("href")).toBe("http://example.fr");
     expect(linkFr.prop("title")).toBe("http://example.fr");
   });
 
   it("renders URLs without unrelated characters", () => {
     const text =
       "global(http://example.com) and local(http://example.us)" +
-      " and maybe https://example.fr, https://example.es?";
+      " and maybe https://example.fr, “https://example.cz“, https://example.es?";
     const openLink = jest.fn();
     const element = renderRep(text, {
       openLink,
       useQuotes: false
     });
 
     expect(element.text()).toEqual(text);
     const linkCom = element.find("a").at(0);
     expect(linkCom.prop("href")).toBe("http://example.com");
 
     const linkUs = element.find("a").at(1);
     expect(linkUs.prop("href")).toBe("http://example.us");
 
     const linkFr = element.find("a").at(2);
     expect(linkFr.prop("href")).toBe("https://example.fr");
 
-    const linkEs = element.find("a").at(3);
+    const linkCz = element.find("a").at(3);
+    expect(linkCz.prop("href")).toBe("https://example.cz");
+
+    const linkEs = element.find("a").at(4);
     expect(linkEs.prop("href")).toBe("https://example.es");
   });
 
+  it("renders a cropped URL with urlCropLimit", () => {
+    const xyzUrl = "http://xyz.com/abcdefghijklmnopqrst";
+    const text = `${xyzUrl} is the best`;
+    const openLink = jest.fn();
+    const element = renderRep(text, {
+      openLink,
+      useQuotes: false,
+      urlCropLimit: 20
+    });
+
+    expect(element.text()).toEqual("http://xyz…klmnopqrst is the best");
+    const link = element.find("a").at(0);
+    expect(link.prop("href")).toBe(xyzUrl);
+    expect(link.prop("title")).toBe(xyzUrl);
+  });
+
+  it("renders multiple cropped URL", () => {
+    const xyzUrl = "http://xyz.com/abcdefghijklmnopqrst";
+    const abcUrl = "http://abc.com/abcdefghijklmnopqrst";
+    const text = `${xyzUrl} is lit, not ${abcUrl}`;
+    const openLink = jest.fn();
+    const element = renderRep(text, {
+      openLink,
+      useQuotes: false,
+      urlCropLimit: 20
+    });
+
+    expect(element.text()).toEqual(
+      "http://xyz…klmnopqrst is lit, not http://abc…klmnopqrst"
+    );
+
+    const links = element.find("a");
+    const xyzLink = links.at(0);
+    expect(xyzLink.prop("href")).toBe(xyzUrl);
+    expect(xyzLink.prop("title")).toBe(xyzUrl);
+    const abc = links.at(1);
+    expect(abc.prop("href")).toBe(abcUrl);
+    expect(abc.prop("title")).toBe(abcUrl);
+  });
+
+  it("renders full URL if smaller than cropLimit", () => {
+    const xyzUrl = "http://example.com/";
+
+    const openLink = jest.fn();
+    const element = renderRep(xyzUrl, {
+      openLink,
+      useQuotes: false,
+      urlCropLimit: 20
+    });
+
+    expect(element.text()).toEqual(xyzUrl);
+    const link = element.find("a").at(0);
+    expect(link.prop("href")).toBe(xyzUrl);
+    expect(link.prop("title")).toBe(xyzUrl);
+  });
+
+  it("renders cropped URL followed by cropped string with urlCropLimit", () => {
+    const text = "http://example.fr abcdefghijkl";
+    const openLink = jest.fn();
+    const element = renderRep(text, {
+      openLink,
+      useQuotes: false,
+      cropLimit: 20
+    });
+
+    expect(element.text()).toEqual("http://exa…cdefghijkl");
+    const linkFr = element.find("a").at(0);
+    expect(linkFr.prop("href")).toBe("http://example.fr");
+    expect(linkFr.prop("title")).toBe("http://example.fr");
+  });
+
   it("does not render a link if the URL has no scheme", () => {
     const url = "example.com";
     const element = renderRep(url, { useQuotes: false });
     expect(element.text()).toEqual(url);
     expect(element.find("a").exists()).toBeFalsy();
   });
 
   it("does not render a link if the URL has an invalid scheme", () => {
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -36,17 +36,16 @@ support-files =
   doc_style_editor_link.css
   doc_test_image.png
   doc_urls_clickable.css
   doc_urls_clickable.html
   doc_variables_1.html
   doc_variables_2.html
   doc_variables_3.html
   head.js
-  !/devtools/client/debugger/test/mochitest/helpers/context.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_rules_add-property-and-reselect.js]
--- a/devtools/client/inspector/rules/test/browser_rules_inactive_css_flexbox.js
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_flexbox.js
@@ -142,16 +142,18 @@ const AFTER = [
         },
         ruleIndex: 1,
       },
     ],
   },
 ];
 
 add_task(async function() {
+  await pushPref("devtools.inspector.inactive.css.enabled", true);
+
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
 
   await runInactiveCSSTests(view, inspector, BEFORE);
 
   // Toggle `display:flex` to disabled.
   await toggleDeclaration(inspector, view, 0, {
     display: "flex",
--- a/devtools/client/inspector/rules/test/browser_rules_inactive_css_grid.js
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_grid.js
@@ -144,16 +144,18 @@ const AFTER = [
         },
         ruleIndex: 1,
       },
     ],
   },
 ];
 
 add_task(async function() {
+  await pushPref("devtools.inspector.inactive.css.enabled", true);
+
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const {inspector, view} = await openRuleView();
 
   await runInactiveCSSTests(view, inspector, BEFORE);
 
   // Toggle `display:grid` to disabled.
   await toggleDeclaration(inspector, view, 0, {
     display: "grid",
--- a/devtools/client/inspector/test/browser_inspector_highlighter-07.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-07.js
@@ -1,13 +1,18 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+/* import-globals-from ../../debugger/test/mochitest/helpers/context.js */
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers/context.js",
+  this);
+
 // Test that the highlighter works when the debugger is paused.
 
 function debuggerIsPaused(dbg) {
   return !!dbg.selectors.getIsPaused(dbg.selectors.getCurrentThread());
 }
 
 function waitForPaused(dbg) {
   return new Promise(resolve => {
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -17,22 +17,16 @@ Services.scriptloader.loadSubScript(
 //   Services.prefs.clearUserPref("devtools.debugger.log");
 // });
 
 // Import helpers for the inspector that are also shared with others
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
   this);
 
-// Import helpers for the new debugger
-/* import-globals-from ../../debugger/test/mochitest/helpers/context.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers/context.js",
-  this);
-
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
       new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
 });
 
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -168,8 +168,22 @@ accessibility.badge.contrast=contrast
 # row in the accessibility tree for a given accessible object that does not
 # satisfy the WCAG guideline for colour contrast.
 accessibility.badge.contrast.tooltip=Does not meet WCAG standards for accessible text.
 
 # LOCALIZATION NOTE (accessibility.tree.filters): A title text for the toolbar
 # within the main accessibility panel that contains a list of filters to be for
 # accessibility audit.
 accessibility.tree.filters=Check for issues:
+
+# LOCALIZATION NOTE (accessibility.progress.initializing): A title text for the
+# accessibility panel overlay shown when accessibility audit is starting up.
+accessibility.progress.initializing=Initializing…
+
+# LOCALIZATION NOTE (accessibility.progress.initializing): A title text for the
+# accessibility panel overlay shown when accessibility audit is running showing
+# the number of nodes being audited. Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+accessibility.progress.progressbar=Checking #1 node;Checking #1 nodes
+
+# LOCALIZATION NOTE (accessibility.progress.finishing): A title text for the
+# accessibility panel overlay shown when accessibility audit is finishing up.
+accessibility.progress.finishing=Finishing up…
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -3742,17 +3742,17 @@ const validProtocols = /(http|https|ftp|
 
 // URL Regex, common idioms:
 //
 // Lead-in (URL):
 // (                     Capture because we need to know if there was a lead-in
 //                       character so we can include it as part of the text
 //                       preceding the match. We lack look-behind matching.
 //  ^|                   The URL can start at the beginning of the string.
-//  [\s(,;'"`]           Or whitespace or some punctuation that does not imply
+//  [\s(,;'"`“]          Or whitespace or some punctuation that does not imply
 //                       a context which would preclude a URL.
 // )
 //
 // We do not need a trailing look-ahead because our regex's will terminate
 // because they run out of characters they can eat.
 
 // What we do not attempt to have the regexp do:
 // - Avoid trailing '.' and ')' characters.  We let our greedy match absorb
@@ -3783,17 +3783,17 @@ const validProtocols = /(http|https|ftp|
 //                       found here: http://www.iana.org/domains/root/db
 //  )
 //  [-\w.!~*'();,/?:@&=+$#%]*
 //                       path onwards. We allow the set of characters that
 //                       encodeURI does not escape plus the result of escaping
 //                       (so also '%')
 // )
 // eslint-disable-next-line max-len
-const urlRegex = /(^|[\s(,;'"`])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
+const urlRegex = /(^|[\s(,;'"`“])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
 
 // Set of terminators that are likely to have been part of the context rather
 // than part of the URL and so should be uneaten. This is '(', ',', ';', plus
 // quotes and question end-ing punctuation and the potential permutations with
 // parentheses (english-specific).
 const uneatLastUrlCharsRegex = /(?:[),;.!?`'"]|[.!?]\)|\)[.!?])$/;
 
 const ELLIPSIS = "\u2026";
@@ -4391,29 +4391,31 @@ const { a, span } = dom;
 /**
  * Renders a string. String value is enclosed within quotes.
  */
 StringRep.propTypes = {
   useQuotes: PropTypes.bool,
   escapeWhitespace: PropTypes.bool,
   style: PropTypes.object,
   cropLimit: PropTypes.number.isRequired,
+  urlCropLimit: PropTypes.number,
   member: PropTypes.object,
   object: PropTypes.object.isRequired,
   openLink: PropTypes.func,
   className: PropTypes.string,
   title: PropTypes.string,
   isInContentPage: PropTypes.bool
 };
 
 function StringRep(props) {
   const {
     className,
     style,
     cropLimit,
+    urlCropLimit,
     object,
     useQuotes = true,
     escapeWhitespace = true,
     member,
     openLink,
     title,
     isInContentPage
   } = props;
@@ -4445,17 +4447,23 @@ function StringRep(props) {
     className,
     style,
     actor: object.actor,
     title
   });
 
   if (!isLong) {
     if (containsURL(text)) {
-      return span(config, ...getLinkifiedElements(text, shouldCrop && cropLimit, openLink, isInContentPage));
+      return span(config, getLinkifiedElements({
+        text,
+        cropLimit: shouldCrop ? cropLimit : null,
+        urlCropLimit,
+        openLink,
+        isInContentPage
+      }));
     }
 
     // Cropping of longString has been handled before formatting.
     text = maybeCropString({
       isLong,
       shouldCrop,
       cropLimit
     }, text);
@@ -4515,24 +4523,34 @@ function maybeCropString(opts, text) {
 
   return shouldCrop ? rawCropString(text, cropLimit) : text;
 }
 
 /**
  * Get an array of the elements representing the string, cropped if needed,
  * with actual links.
  *
- * @param {String} text: The actual string to linkify.
- * @param {Integer | null} cropLimit
- * @param {Function} openLink: Function handling the link opening.
- * @param {Boolean} isInContentPage: pass true if the reps is rendered in
- *                                   the content page (e.g. in JSONViewer).
+ * @param {Object} An options object of the following shape:
+ *                 - text {String}: The actual string to linkify.
+ *                 - cropLimit {Integer}: The limit to apply on the whole text.
+ *                 - urlCropLimit {Integer}: The limit to apply on each URL.
+ *                 - openLink {Function} openLink: Function handling the link
+ *                                                 opening.
+ *                 - isInContentPage {Boolean}: pass true if the reps is
+ *                                              rendered in the content page
+ *                                              (e.g. in JSONViewer).
  * @returns {Array<String|ReactElement>}
  */
-function getLinkifiedElements(text, cropLimit, openLink, isInContentPage) {
+function getLinkifiedElements({
+  text,
+  cropLimit,
+  urlCropLimit,
+  openLink,
+  isInContentPage
+}) {
   const halfLimit = Math.ceil((cropLimit - ELLIPSIS.length) / 2);
   const startCropIndex = cropLimit ? halfLimit : null;
   const endCropIndex = cropLimit ? text.length - halfLimit : null;
 
   const items = [];
   let currentIndex = 0;
   let contentStart;
   while (true) {
@@ -4552,20 +4570,26 @@ function getLinkifiedElements(text, crop
     // URL.
     let useUrl = url[2];
     const uneat = uneatLastUrlCharsRegex.exec(useUrl);
     if (uneat) {
       useUrl = useUrl.substring(0, uneat.index);
     }
 
     currentIndex = currentIndex + contentStart;
-    const linkText = getCroppedString(useUrl, currentIndex, startCropIndex, endCropIndex);
+    let linkText = getCroppedString(useUrl, currentIndex, startCropIndex, endCropIndex);
 
     if (linkText) {
+      if (urlCropLimit && useUrl.length > urlCropLimit) {
+        const urlCropHalf = Math.ceil((urlCropLimit - ELLIPSIS.length) / 2);
+        linkText = getCroppedString(useUrl, 0, urlCropHalf, useUrl.length - urlCropHalf);
+      }
+
       items.push(a({
+        key: `${useUrl}-${currentIndex}`,
         className: "url",
         title: useUrl,
         draggable: false,
         // Because we don't want the link to be open in the current
         // panel's frame, we only render the href attribute if `openLink`
         // exists (so we can preventDefault) or if the reps will be
         // displayed in content page (e.g. in the JSONViewer).
         href: openLink || isInContentPage ? useUrl : null,
--- a/devtools/client/webconsole/components/message-types/PageError.js
+++ b/devtools/client/webconsole/components/message-types/PageError.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // React & Redux
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const Message = createFactory(require("devtools/client/webconsole/components/Message"));
+const { MODE, REPS } = require("devtools/client/shared/components/reps/reps");
 
 PageError.displayName = "PageError";
 
 PageError.propTypes = {
   message: PropTypes.object.isRequired,
   open: PropTypes.bool,
   timestampsVisible: PropTypes.bool.isRequired,
   serviceContainer: PropTypes.object,
@@ -47,22 +48,24 @@ function PageError(props) {
     messageText,
     stacktrace,
     frame,
     exceptionDocURL,
     timeStamp,
     notes,
   } = message;
 
-  let messageBody;
-  if (typeof messageText === "string") {
-    messageBody = messageText;
-  } else if (typeof messageText === "object" && messageText.type === "longString") {
-    messageBody = `${message.messageText.initial}…`;
-  }
+  const messageBody = REPS.StringRep.rep({
+    object: messageText,
+    mode: MODE.LONG,
+    useQuotes: false,
+    escapeWhitespace: false,
+    urlCropLimit: 120,
+    openLink: serviceContainer.openLink,
+  });
 
   return Message({
     dispatch,
     messageId,
     executionPoint,
     isPaused,
     open,
     collapsible: Array.isArray(stacktrace),
--- a/devtools/client/webconsole/test/components/evaluation-result.test.js
+++ b/devtools/client/webconsole/test/components/evaluation-result.test.js
@@ -1,19 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Test utils.
 const expect = require("expect");
-const { render } = require("enzyme");
-
-// Commented out for "displays a [Learn more] link"
-// const { render, mount } = require("enzyme");
-// const sinon = require("sinon");
+const { render, mount } = require("enzyme");
+const sinon = require("sinon");
 
 // React
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const Provider = createFactory(require("react-redux").Provider);
 const { setupStore } = require("devtools/client/webconsole/test/helpers");
 
 // Components under test.
 const EvaluationResult = createFactory(
@@ -95,37 +92,40 @@ describe("EvaluationResult component:", 
     const message = stubPreparedMessages.get("cd(document)");
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
 
     expect(wrapper.find(".message-body").text()).toBe(
       "Cannot cd() to the given window. Invalid argument."
     );
   });
 
-  // TODO: Regressed by Bug 1230194, disabled in Bug 1535484. Filed Bug 1550791 to fix it.
-  // it("displays a [Learn more] link", () => {
-  //   const store = setupStore();
+  it("displays a [Learn more] link", () => {
+    const store = setupStore();
 
-  //   const message = stubPreparedMessages.get("asdf()");
-
-  //   serviceContainer.openLink = sinon.spy();
-  //   const wrapper = mount(
-  //     Provider({ store }, EvaluationResult({ message, serviceContainer }))
-  //   );
+    const message = stubPreparedMessages.get("asdf()");
 
-  //   const url =
-  //     "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined";
-  //   const learnMore = wrapper.find(".learn-more-link");
-  //   expect(learnMore.length).toBe(1);
-  //   expect(learnMore.prop("title")).toBe(url);
+    serviceContainer.openLink = sinon.spy();
+    const wrapper = mount(
+      Provider({ store }, EvaluationResult({
+        message,
+        serviceContainer,
+        dispatch: () => {},
+      }))
+    );
 
-  //   learnMore.simulate("click");
-  //   const call = serviceContainer.openLink.getCall(0);
-  //   expect(call.args[0]).toEqual(message.exceptionDocURL);
-  // });
+    const url =
+      "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined";
+    const learnMore = wrapper.find(".learn-more-link");
+    expect(learnMore.length).toBe(1);
+    expect(learnMore.prop("title")).toBe(url);
+
+    learnMore.simulate("click");
+    const call = serviceContainer.openLink.getCall(0);
+    expect(call.args[0]).toEqual(message.exceptionDocURL);
+  });
 
   it("has the expected indent", () => {
     const message = stubPreparedMessages.get("new Date(0)");
 
     const indent = 10;
     // We need to wrap the ConsoleApiElement in a Provider in order for the
     // ObjectInspector to work.
     let wrapper = render(
--- a/devtools/client/webconsole/test/components/page-error.test.js
+++ b/devtools/client/webconsole/test/components/page-error.test.js
@@ -6,27 +6,29 @@
 const expect = require("expect");
 const { render, mount } = require("enzyme");
 const sinon = require("sinon");
 
 // React
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const Provider = createFactory(require("react-redux").Provider);
 const { setupStore } = require("devtools/client/webconsole/test/helpers");
+const { prepareMessage } = require("devtools/client/webconsole/utils/messages");
 
 // Components under test.
 const PageError = require("devtools/client/webconsole/components/message-types/PageError");
 const {
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
 } = require("devtools/client/webconsole/constants");
 const { INDENT_WIDTH } = require("devtools/client/webconsole/components/MessageIndent");
 
 // Test fakes.
-const { stubPreparedMessages } = require("devtools/client/webconsole/test/fixtures/stubs/index");
+const { stubPackets, stubPreparedMessages } =
+  require("devtools/client/webconsole/test/fixtures/stubs/index");
 const serviceContainer = require("devtools/client/webconsole/test/fixtures/serviceContainer");
 
 describe("PageError component:", () => {
   it("renders", () => {
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
     const wrapper = render(PageError({
       message,
       serviceContainer,
@@ -78,16 +80,61 @@ describe("PageError component:", () => {
 
   it("renders thrown string", () => {
     const message = stubPreparedMessages.get(`throw "tomato"`);
     const wrapper = render(PageError({ message, serviceContainer }));
     const text = wrapper.find(".message-body").text();
     expect(text).toBe(`uncaught exception: tomato`);
   });
 
+  it("renders URLs in message as actual, cropped, links", () => {
+    // Let's replace the packet data in order to mimick a pageError.
+    const packet = stubPackets.get("ReferenceError: asdf is not defined");
+
+    const evilDomain = `https://evil.com/?`;
+    const badDomain = `https://not-so-evil.com/?`;
+    const paramLength = 200;
+    const longParam = "a".repeat(paramLength);
+
+    const evilURL = `${evilDomain}${longParam}`;
+    const badURL = `${badDomain}${longParam}`;
+
+    packet.pageError.errorMessage =
+      `“${evilURL}“ is evil and “${badURL}“ is not good either`;
+
+    // We remove the exceptionDocURL to not have the "learn more" link.
+    packet.pageError.exceptionDocURL = null;
+
+    const message = prepareMessage(packet, {getNextId: () => "1"});
+    const wrapper = render(PageError({ message, serviceContainer }));
+
+    // Keep in sync with `urlCropLimit` in PageError.js.
+    const cropLimit = 120;
+    const partLength = cropLimit / 2;
+    const getCroppedUrl = url =>
+      `${url}${"a".repeat((partLength - url.length))}…${"a".repeat(partLength)}`;
+
+    const croppedEvil = getCroppedUrl(evilDomain);
+    const croppedbad = getCroppedUrl(badDomain);
+
+    const text = wrapper.find(".message-body").text();
+    expect(text).toBe(
+      `“${croppedEvil}“ is evil and “${croppedbad}“ is not good either`);
+
+    // There should be 2 links.
+    const links = wrapper.find(".message-body a");
+    expect(links.length).toBe(2);
+
+    expect(links.eq(0).attr("href")).toBe(evilURL);
+    expect(links.eq(0).attr("title")).toBe(evilURL);
+
+    expect(links.eq(1).attr("href")).toBe(badURL);
+    expect(links.eq(1).attr("title")).toBe(badURL);
+  });
+
   it("displays a [Learn more] link", () => {
     const store = setupStore();
 
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     serviceContainer.openLink = sinon.spy();
     const wrapper = mount(Provider({store},
       PageError({
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -291,16 +291,17 @@ tags = clipboard
 [browser_webconsole_csp_ignore_reflected_xss_message.js]
 [browser_webconsole_csp_violation.js]
 [browser_webconsole_cspro.js]
 [browser_webconsole_document_focus.js]
 [browser_webconsole_duplicate_errors.js]
 [browser_webconsole_error_with_grouped_stack.js]
 [browser_webconsole_error_with_longstring_stack.js]
 [browser_webconsole_error_with_unicode.js]
+[browser_webconsole_error_with_url.js]
 [browser_webconsole_errors_after_page_reload.js]
 [browser_webconsole_eval_error.js]
 [browser_webconsole_eval_in_debugger_stackframe.js]
 [browser_webconsole_eval_in_debugger_stackframe2.js]
 [browser_webconsole_eval_sources.js]
 [browser_webconsole_execution_scope.js]
 [browser_webconsole_external_script_errors.js]
 [browser_webconsole_file_uri.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_error_with_url.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check if an error with Unicode characters is reported correctly.
+
+"use strict";
+
+const longParam = "0".repeat(200);
+const url1 = `https://example.com?v=${longParam}`;
+const url2 = `https://example.org?v=${longParam}`;
+
+const TEST_URI = `data:text/html;charset=utf8,<script>
+  throw "Visit \u201c${url1}\u201d or \u201c${url2}\u201d to get more " +
+        "information on this error.";
+</script>`;
+const {ELLIPSIS} = require("devtools/shared/l10n");
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  // On e10s, the exception is triggered in child process
+  // and is ignored by test harness
+  if (!Services.appinfo.browserTabsRemoteAutostart) {
+    expectUncaughtException();
+  }
+
+  const getCroppedUrl = origin => {
+    const cropLimit = 120;
+    const half = cropLimit / 2;
+    const params =
+      `?v=${"0".repeat(half - origin.length - 3)}${ELLIPSIS}${"0".repeat(half)}`;
+    return `${origin}${params}`;
+  };
+
+  const EXPECTED_MESSAGE = `get more information on this error`;
+
+  const msg = await waitFor(() => findMessage(hud, EXPECTED_MESSAGE));
+  ok(msg, `Link in error message are cropped as expected`);
+
+  const [comLink, orgLink] = Array.from(msg.querySelectorAll("a"));
+  is(comLink.getAttribute("href"), url1, "First link has expected url");
+  is(comLink.getAttribute("title"), url1, "First link has expected tooltip");
+  is(comLink.textContent, getCroppedUrl("https://example.com"),
+    "First link has expected text");
+
+  is(orgLink.getAttribute("href"), url2, "Second link has expected url");
+  is(orgLink.getAttribute("title"), url2, "Second link has expected tooltip");
+  is(orgLink.textContent, getCroppedUrl("https://example.org"),
+    "Second link has expected text");
+});
--- a/devtools/docs/contributing/performance.md
+++ b/devtools/docs/contributing/performance.md
@@ -131,17 +131,17 @@ It highlights that:
 ### Run performance tests
 
 See if any subtest reports a improvement. Ensure that the improvement makes any sense.
 For example, if the test is 50% faster, maybe you broke the performance test.
 This might happen if the test no longer waits for all the operations to finish executing before completing.
 
 To push your current patch to try, execute:
 ```
-./mach try -b o -p linux64 -u none -t damp-e10s --rebuild-talos 5 --artifact
+./mach try -b o -p linux64 -u none -t damp --rebuild-talos 5 --artifact
 ```
 It will print in your Terminal a link to perfherder like this one:
 [https://treeherder.mozilla.org/perf.html#/comparechooser?newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db](https://treeherder.mozilla.org/perf.html#/comparechooser?newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db)
 Running performance tests takes time, so you should open it 30 minutes up to 2 hours later to see your results.
 See [Performance tests (DAMP)](../tests/performance-tests.md) for more information about PerfHerder/try.
 
 Let's look at how to interpret an actual real-life [set of perfherder results](https://treeherder.mozilla.org/perf.html#/comparesubtest?originalProject=mozilla-central&newProject=try&newRevision=9bef6cb13c43bbce21d40ffaea595e082a4c28db&originalSignature=edaec66500db21d37602c99daa61ac983f21a6ac&newSignature=edaec66500db21d37602c99daa61ac983f21a6ac&showOnlyImportant=1&framework=1&selectedTimeRange=172800):
 
--- a/devtools/docs/tests/performance-tests.md
+++ b/devtools/docs/tests/performance-tests.md
@@ -12,21 +12,21 @@ This will run all DAMP tests, you can fi
 ```bash
 ./mach talos-test --activeTests damp --subtests console
 ```
 This command will run all tests which contains "console" in their name.
 
 ## How to run it on try?
 
 ```bash
-./mach try -b o -p linux64 -u none -t damp-e10s --rebuild-talos 6
+./mach try -b o -p linux64 -u none -t damp --rebuild-talos 6
 ```
 * Linux appears to build and run quickly, and offers quite stable results over the other OSes.
 The vast majority of performance issues for DevTools are OS agnostic, so it doesn't really matter which one you run them on.
-* "damp-e10s" is the talos bucket in which we run DAMP.
+* "damp" is the talos bucket in which we run DAMP.
 * And 6 is the number of times we run DAMP tests. That's to do averages between all the 6 runs and helps filtering out the noise.
 
 ## What does it do?
 
 DAMP measures three important operations:
 * Open a toolbox
 * Reload the web page
 * Close the toolbox
--- a/devtools/docs/tests/tips.md
+++ b/devtools/docs/tests/tips.md
@@ -13,10 +13,10 @@ export MOZ_QUIET=1
 
 You can also send `MOZ_QUIET` when you push to try&hellip; it makes the logs easier to read and makes the tests run faster because there is so much less logging.
 
 Example try syntax containing `MOZ_QUIET`:
 
 ```
 ./mach try -b do -p linux,linux64,macosx64,win32,win64 \
   -u xpcshell,mochitest-bc,mochitest-e10s-bc,mochitest-dt,mochitest-chrome \
-  -t damp-e10s --setenv MOZ_QUIET=1
+  -t damp --setenv MOZ_QUIET=1
 ```
--- a/devtools/server/actors/accessibility/walker.js
+++ b/devtools/server/actors/accessibility/walker.js
@@ -125,27 +125,77 @@ function isStale(accessible) {
 
 /**
  * Get accessibility audit starting with the passed accessible object as a root.
  *
  * @param {Object} acc
  *        AccessibileActor to be used as the root for the audit.
  * @param {Map} report
  *        An accumulator map to be used to store audit information.
+ * @param {Object} progress
+ *        An audit project object that is used to track the progress of the
+ *        audit and send progress "audit-event" events to the client.
  */
-function getAudit(acc, report) {
+function getAudit(acc, report, progress) {
   if (acc.isDefunct) {
     return;
   }
 
   // Audit returns a promise, save the actual value in the report.
-  report.set(acc, acc.audit().then(result => report.set(acc, result)));
+  report.set(acc, acc.audit().then(result => {
+    report.set(acc, result);
+    progress.increment();
+  }));
 
   for (const child of acc.children()) {
-    getAudit(child, report);
+    getAudit(child, report, progress);
+  }
+}
+
+/**
+ * A helper class that is used to track audit progress and send progress events
+ * to the client.
+ */
+class AuditProgress {
+  constructor(walker) {
+    this.completed = 0;
+    this.percentage = 0;
+    this.walker = walker;
+  }
+
+  setTotal(size) {
+    this.size = size;
+  }
+
+  notify() {
+    this.walker.emit("audit-event", {
+      type: "progress",
+      progress: {
+        total: this.size,
+        percentage: this.percentage,
+      },
+    });
+  }
+
+  increment() {
+    this.completed++;
+    const { completed, size } = this;
+    if (!size) {
+      return;
+    }
+
+    const percentage = Math.round(completed / size * 100);
+    if (percentage > this.percentage) {
+      this.percentage = percentage;
+      this.notify();
+    }
+  }
+
+  destroy() {
+    this.walker = null;
   }
 }
 
 /**
  * The AccessibleWalkerActor stores a cache of AccessibleActors that represent
  * accessible objects in a given document.
  *
  * It is also responsible for implicitely initializing and shutting down
@@ -396,17 +446,19 @@ const AccessibleWalkerActor = ActorClass
    *
    * @return {Promise}
    *         A promise that resolves when the audit is complete and all relevant
    *         ancestries are calculated.
    */
   async audit() {
     const doc = await this.getDocument();
     const report = new Map();
-    getAudit(doc, report);
+    this._auditProgress = new AuditProgress(this);
+    getAudit(doc, report, this._auditProgress);
+    this._auditProgress.setTotal(report.size);
     await Promise.all(report.values());
 
     const ancestries = [];
     for (const [acc, audit] of report.entries()) {
       // Filter out audits that have no failing checks.
       if (audit &&
           Object.values(audit).some(check => check != null && !check.error &&
             check.score === accessibility.SCORES.FAIL)) {
@@ -426,20 +478,25 @@ const AccessibleWalkerActor = ActorClass
     // Audit is already running, wait for the "audit-event" event.
     if (this._auditing) {
       return;
     }
 
     this._auditing = this.audit()
       // We do not want to block on audit request, instead fire "audit-event"
       // event when internal audit is finished or failed.
-      .then(ancestries => this.emit("audit-event", { ancestries }))
-      .catch(() => this.emit("audit-event", { error: true }))
+      .then(ancestries => this.emit("audit-event", {
+        type: "completed",
+        ancestries,
+      }))
+      .catch(() => this.emit("audit-event", { type: "error" }))
       .finally(() => {
         this._auditing = null;
+        this._auditProgress.destroy();
+        this._auditProgress = null;
       });
   },
 
   onHighlighterEvent: function(data) {
     this.emit("highlighter-event", data);
   },
 
   /**
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -8,17 +8,17 @@
 
 const Services = require("Services");
 const { Cr, Ci } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object/utils");
 const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn } = DevToolsUtils;
-const { threadSpec } = require("devtools/shared/specs/script");
+const { threadSpec } = require("devtools/shared/specs/thread");
 const {
   getAvailableEventBreakpoints,
 } = require("devtools/server/actors/utils/event-breakpoints");
 
 loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true);
 loader.lazyRequireGetter(this, "BreakpointActorMap", "devtools/server/actors/utils/breakpoint-actor-map", true);
 loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors/pause-scoped", true);
 loader.lazyRequireGetter(this, "EventLoopStack", "devtools/server/actors/utils/event-loop", true);
--- a/devtools/server/tests/browser/browser_accessibility_walker_audit.js
+++ b/devtools/server/tests/browser/browser_accessibility_walker_audit.js
@@ -54,30 +54,59 @@ add_task(async function() {
         "value": 4.00,
         "color": [255, 0, 0, 1],
         "backgroundColor": [255, 255, 255, 1],
         "isLargeText": false,
         "score": "fail",
       },
     },
   }];
+  const total = accessibles.length;
+  const expectedProgress = [
+    { total, percentage: 20 },
+    { total, percentage: 40 },
+    { total, percentage: 60 },
+    { total, percentage: 80 },
+    { total, percentage: 100},
+  ];
 
   function findAccessible(name, role) {
     return accessibles.find(accessible =>
       accessible.name === name && accessible.role === role);
   }
 
   const a11yWalker = await accessibility.getWalker();
   ok(a11yWalker, "The AccessibleWalkerFront was returned");
   await accessibility.enable();
 
   info("Checking AccessibleWalker audit functionality");
-  const auditEvent = a11yWalker.once("audit-event");
-  a11yWalker.startAudit();
-  const { ancestries } = await auditEvent;
+  const ancestries = await new Promise((resolve, reject) => {
+    const auditEventHandler = ({ type, ancestries: response, progress }) => {
+      switch (type) {
+        case "error":
+          a11yWalker.off("audit-event", auditEventHandler);
+          reject();
+          break;
+        case "completed":
+          a11yWalker.off("audit-event", auditEventHandler);
+          resolve(response);
+          is(expectedProgress.length, 0, "All progress events fired");
+          break;
+        case "progress":
+          SimpleTest.isDeeply(progress, expectedProgress.shift(),
+                              "Progress data is correct");
+          break;
+        default:
+          break;
+      }
+    };
+
+    a11yWalker.on("audit-event", auditEventHandler);
+    a11yWalker.startAudit();
+  });
 
   is(ancestries.length, 2, "The size of ancestries is correct");
   for (const ancestry of ancestries) {
     for (const { accessible, children } of ancestry) {
       checkA11yFront(accessible,
                      findAccessible(accessibles.name, accessibles.role));
       for (const child of children) {
         checkA11yFront(child,
--- a/devtools/shared/specs/accessibility.js
+++ b/devtools/shared/specs/accessibility.js
@@ -16,25 +16,26 @@ types.addActorType("accessible");
 types.addDictType("accessibleWithChildren", {
   // Accessible
   accessible: "accessible",
   // Accessible's children
   children: "array:accessible",
 });
 
 /**
- * Data passed via "audit-event" to the client. It may include a list of
+ * Data passed via "audit-event" to the client. It may include type, a list of
  * ancestries for accessible actors that have failing accessibility checks or
- * an error flag.
+ * a progress information.
  */
 types.addDictType("auditEventData", {
+  type: "string",
   // List of ancestries (array:accessibleWithChildren)
-  ancestries: "array:array:accessibleWithChildren",
-  // True if the audit failed.
-  error: "boolean",
+  ancestries: "nullable:array:array:accessibleWithChildren",
+  // Audit progress information
+  progress: "nullable:json",
 });
 
 /**
  * Accessible relation object described by its type that also includes relation targets.
  */
 types.addDictType("accessibleRelation", {
   // Accessible relation type
   type: "string",
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -166,22 +166,16 @@ const Types = exports.__TypesForTests = 
     spec: "devtools/shared/specs/reflow",
     front: "devtools/shared/fronts/reflow",
   },
   {
     types: ["screenshot"],
     spec: "devtools/shared/specs/screenshot",
     front: "devtools/shared/fronts/screenshot",
   },
-  /* Script and source have old fashion client and no front */
-  {
-    types: ["context"],
-    spec: "devtools/shared/specs/script",
-    front: null,
-  },
   {
     types: ["source"],
     spec: "devtools/shared/specs/source",
     front: "devtools/shared/fronts/source",
   },
   {
     types: ["cookies", "localStorage", "sessionStorage", "Cache", "indexedDB", "storage"],
     spec: "devtools/shared/specs/storage",
@@ -253,16 +247,22 @@ const Types = exports.__TypesForTests = 
     spec: "devtools/shared/specs/targets/webextension",
     front: null,
   },
   {
     types: ["workerTarget"],
     spec: "devtools/shared/specs/targets/worker",
     front: "devtools/shared/fronts/targets/worker",
   },
+  /* Thread has an old fashion client and no front */
+  {
+    types: ["context"],
+    spec: "devtools/shared/specs/thread",
+    front: null,
+  },
   {
     types: ["console"],
     spec: "devtools/shared/specs/webconsole",
     front: "devtools/shared/fronts/webconsole",
   },
   {
     types: ["pushSubscription"],
     spec: "devtools/shared/specs/worker/push-subscription",
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -36,19 +36,19 @@ DevToolsModules(
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'property-iterator.js',
     'reflow.js',
     'root.js',
     'screenshot.js',
-    'script.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'symbol-iterator.js',
     'symbol.js',
+    'thread.js',
     'timeline.js',
     'webconsole.js',
 )
rename from devtools/shared/specs/script.js
rename to devtools/shared/specs/thread.js
--- a/devtools/shared/specs/script.js
+++ b/devtools/shared/specs/thread.js
@@ -1,28 +1,94 @@
 /* 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 {Arg, RetVal, generateActorSpec, types} = require("devtools/shared/protocol");
+const {Arg, Option, RetVal, generateActorSpec, types} = require("devtools/shared/protocol");
 
 types.addDictType("available-breakpoint-group", {
   name: "string",
   events: "array:available-breakpoint-event",
 });
 types.addDictType("available-breakpoint-event", {
   id: "string",
   name: "string",
 });
 
 const threadSpec = generateActorSpec({
   typeName: "context",
 
+  events: {
+    newSource: {
+      source: Option(0, "source"),
+    },
+    progress: {
+      recording: Option(0, "json"),
+      executionPoint: Option(0, "json"),
+    },
+  },
+
   methods: {
+    attach: {
+      request: {
+        options: Arg(0, "json"),
+      },
+      response: RetVal("nullable:json"),
+    },
+    detach: {
+      response: {},
+    },
+    reconfigure: {
+      request: {
+        options: Arg(0, "json"),
+      },
+      response: {},
+    },
+    resume: {
+      request: {
+        resumeLimit: Arg(0, "nullable:json"),
+        rewind: Arg(1, "boolean"),
+      },
+      response: RetVal("nullable:json"),
+    },
+    frames: {
+      request: {
+        start: Arg(0, "number"),
+        count: Arg(1, "number"),
+      },
+      response: RetVal("json"),
+    },
+    interrupt: {
+      request: {
+        when: Arg(0, "json"),
+      },
+      response: RetVal("array:json"),
+    },
+    sources: {
+      response: RetVal("array:json"),
+    },
+    skipBreakpoints: {
+      request: {
+        skip: Arg(0, "json"),
+      },
+      response: {
+        skip: Arg(0, "json"),
+      },
+    },
+    threadGrips: {
+      request: {
+        actors: Arg(0, "array:string"),
+      },
+      response: RetVal("json"),
+    },
+    dumpThread: {
+      request: {},
+      response: RetVal("json"),
+    },
     setBreakpoint: {
       request: {
         location: Arg(0, "json"),
         options: Arg(1, "json"),
       },
     },
     removeBreakpoint: {
       request: {
--- a/dom/base/ProcessSelector.jsm
+++ b/dom/base/ProcessSelector.jsm
@@ -6,18 +6,18 @@
 // ones.
 function RandomSelector() {
 }
 
 RandomSelector.prototype = {
   classID:          Components.ID("{c616fcfd-9737-41f1-aa74-cee72a38f91b}"),
   QueryInterface:   ChromeUtils.generateQI([Ci.nsIContentProcessProvider]),
 
-  provideProcess(aType, aOpener, aProcesses, aCount, aMaxCount) {
-    if (aCount < aMaxCount) {
+  provideProcess(aType, aOpener, aProcesses, aMaxCount) {
+    if (aProcesses.length < aMaxCount) {
       return Ci.nsIContentProcessProvider.NEW_PROCESS;
     }
 
     let startIdx = Math.floor(Math.random() * aMaxCount);
     let curIdx = startIdx;
 
     do {
       if (aProcesses[curIdx].opener === aOpener) {
@@ -35,26 +35,26 @@ RandomSelector.prototype = {
 // ones that host the least number of tabs.
 function MinTabSelector() {
 }
 
 MinTabSelector.prototype = {
   classID:          Components.ID("{2dc08eaf-6eef-4394-b1df-a3a927c1290b}"),
   QueryInterface:   ChromeUtils.generateQI([Ci.nsIContentProcessProvider]),
 
-  provideProcess(aType, aOpener, aProcesses, aCount, aMaxCount) {
-    if (aCount < aMaxCount) {
+  provideProcess(aType, aOpener, aProcesses, aMaxCount) {
+    if (aProcesses.length < aMaxCount) {
       return Ci.nsIContentProcessProvider.NEW_PROCESS;
     }
 
     let min = Number.MAX_VALUE;
     let candidate = Ci.nsIContentProcessProvider.NEW_PROCESS;
 
     // Note, that at this point aMaxCount is in the valid range and
-    // the reason for not using aCount here is because if we keep
+    // the reason for not using aProcesses.length here is because if we keep
     // processes alive for testing but want a test to use only single
     // content process we can just keep relying on dom.ipc.processCount = 1
     // this way.
     for (let i = 0; i < aMaxCount; i++) {
       let process = aProcesses[i];
       let tabCount = process.tabCount;
       if (process.opener === aOpener && tabCount < min) {
         min = tabCount;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1138,16 +1138,27 @@ static int CompareIdsAtIndices(const voi
   const uint16_t index2 = *static_cast<const uint16_t*>(aElement2);
   const PropertyInfo* infos = static_cast<PropertyInfo*>(aClosure);
 
   MOZ_ASSERT(JSID_BITS(infos[index1].Id()) != JSID_BITS(infos[index2].Id()));
 
   return JSID_BITS(infos[index1].Id()) < JSID_BITS(infos[index2].Id()) ? -1 : 1;
 }
 
+// {JSPropertySpec,JSFunctionSpec} use {JSPropertySpec,JSFunctionSpec}::Name
+// and ConstantSpec uses `const char*` for name field.
+static inline JSPropertySpec::Name ToPropertySpecName(
+    JSPropertySpec::Name name) {
+  return name;
+}
+
+static inline JSPropertySpec::Name ToPropertySpecName(const char* name) {
+  return JSPropertySpec::Name(name);
+}
+
 template <typename SpecT>
 static bool InitIdsInternal(JSContext* cx, const Prefable<SpecT>* pref,
                             PropertyInfo* infos, PropertyType type) {
   MOZ_ASSERT(pref);
   MOZ_ASSERT(pref->specs);
 
   // Index of the Prefable that contains the id for the current PropertyInfo.
   uint32_t prefIndex = 0;
@@ -1156,17 +1167,18 @@ static bool InitIdsInternal(JSContext* c
     // We ignore whether the set of ids is enabled and just intern all the IDs,
     // because this is only done once per application runtime.
     const SpecT* spec = pref->specs;
     // Index of the property/function/constant spec for our current PropertyInfo
     // in the "specs" array of the relevant Prefable.
     uint32_t specIndex = 0;
     do {
       jsid id;
-      if (!JS::PropertySpecNameToPermanentId(cx, spec->name, &id)) {
+      if (!JS::PropertySpecNameToPermanentId(cx, ToPropertySpecName(spec->name),
+                                             &id)) {
         return false;
       }
       infos->SetId(id);
       infos->type = type;
       infos->prefIndex = prefIndex;
       infos->specIndex = specIndex++;
       ++infos;
     } while ((++spec)->name);
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -1278,36 +1278,31 @@ class EventStateManager : public nsSuppo
                                                 nsIContent* aElement);
   static void sClickHoldCallback(nsITimer* aTimer, void* aESM);
 };
 
 /**
  * This class is used while processing real user input. During this time, popups
  * are allowed. For mousedown events, mouse capturing is also permitted.
  */
-class AutoHandlingUserInputStatePusher {
+class MOZ_RAII AutoHandlingUserInputStatePusher final {
  public:
   AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput,
                                    WidgetEvent* aEvent,
                                    dom::Document* aDocument);
   ~AutoHandlingUserInputStatePusher();
 
  protected:
   RefPtr<dom::Document> mMouseButtonEventHandlingDocument;
   EventMessage mMessage;
   bool mIsHandlingUserInput;
 
   bool NeedsToResetFocusManagerMouseButtonHandlingState() const {
     return mMessage == eMouseDown || mMessage == eMouseUp;
   }
-
- private:
-  // Hide so that this class can only be stack-allocated
-  static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; }
-  static void operator delete(void* /*memory*/) {}
 };
 
 }  // namespace mozilla
 
 // Click and double-click events need to be handled even for content that
 // has no frame. This is required for Web compatibility.
 #define NS_EVENT_NEEDS_FRAME(event)               \
   (!(event)->HasPluginActivationEventMessage() && \
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -162,16 +162,21 @@ already_AddRefed<Response> Response::Red
 
 /*static*/
 already_AddRefed<Response> Response::Constructor(
     const GlobalObject& aGlobal,
     const Optional<Nullable<fetch::ResponseBodyInit>>& aBody,
     const ResponseInit& aInit, ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   if (aInit.mStatus < 200 || aInit.mStatus > 599) {
     aRv.ThrowRangeError<MSG_INVALID_RESPONSE_STATUSCODE_ERROR>();
     return nullptr;
   }
 
   // Check if the status text contains illegal characters
   nsACString::const_iterator start, end;
   aInit.mStatusText.BeginReading(start);
@@ -204,20 +209,32 @@ already_AddRefed<Response> Response::Con
 
       principalInfo.reset(new mozilla::ipc::PrincipalInfo());
       nsresult rv =
           PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
         return nullptr;
       }
-    } else {
+
+      internalResponse->InitChannelInfo(info);
+    } else if (nsContentUtils::IsSystemPrincipal(global->PrincipalOrNull())) {
       info.InitFromChromeGlobal(global);
+
+      internalResponse->InitChannelInfo(info);
     }
-    internalResponse->InitChannelInfo(info);
+
+    /**
+     * The channel info is left uninitialized if neither the above `if` nor
+     * `else if` statements are executed; this could be because we're in a
+     * WebExtensions content script, where the global (i.e. `global`) is a
+     * wrapper, and the principal is an expanded principal. In this case,
+     * as far as I can tell, there's no way to get the security info, but we'd
+     * like the `Response` to be successfully constructed.
+     */
   } else {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     internalResponse->InitChannelInfo(worker->GetChannelInfo());
     principalInfo =
         MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
   }
 
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -55,12 +55,13 @@ LOCAL_INCLUDES += [
     '/netwerk/base',
     # For nsDataHandler.h
     '/netwerk/protocol/data',
     # For HttpBaseChannel.h
     '/netwerk/protocol/http',
 ]
 
 BROWSER_CHROME_MANIFESTS += [ 'tests/browser.ini' ]
+MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
 
 FINAL_LIBRARY = 'xul'
 
 include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644
--- /dev/null
+++ b/dom/fetch/tests/mochitest.ini
@@ -0,0 +1,1 @@
+[test_ext_response_constructor.html]
new file mode 100644
--- /dev/null
+++ b/dom/fetch/tests/test_ext_response_constructor.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test `Response` constructor in a WebExtension</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+  <script>
+    add_task(async function testResponseConstructor() {
+      function contentScript() {
+        new Response();
+        browser.test.notifyPass("done");
+      }
+
+      const extension = ExtensionTestUtils.loadExtension({
+        manifest: {
+          content_scripts: [
+            {
+              matches: ["<all_urls>"],
+              js: ["content_script.js"],
+            },
+          ],
+        },
+
+        files: {
+          "content_script.js": contentScript,
+        },
+      });
+
+      await extension.startup();
+
+      const win = window.open("https://example.com");
+      await extension.awaitFinish("done");
+      win.close();
+
+      await extension.unload();
+    });
+  </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -49,16 +49,17 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsNetUtil.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIWebProgress.h"
 #include "nsIDocShell.h"
 #include "nsIPrompt.h"
 #include "nsISecurityUITelemetry.h"
 #include "nsIStringBundle.h"
+#include "nsIProtocolHandler.h"
 
 // radio buttons
 #include "mozilla/dom/HTMLInputElement.h"
 #include "nsIRadioVisitor.h"
 #include "RadioNodeList.h"
 
 #include "nsLayoutUtils.h"
 
@@ -762,35 +763,29 @@ nsresult HTMLFormElement::DoSecureToInse
   if (!principalURI) {
     principalURI = OwnerDoc()->GetDocumentURI();
   }
   bool formIsHTTPS;
   rv = principalURI->SchemeIs("https", &formIsHTTPS);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  bool actionIsHTTPS;
-  rv = aActionURL->SchemeIs("https", &actionIsHTTPS);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  bool actionIsJS;
-  rv = aActionURL->SchemeIs("javascript", &actionIsJS);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
 
-  if (!formIsHTTPS || actionIsHTTPS || actionIsJS) {
+  if (!formIsHTTPS) {
     return NS_OK;
   }
 
   if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) {
     return NS_OK;
   }
 
+  if (nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(aActionURL)) {
+    return NS_OK;
+  }
+
   if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aActionURL)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
   if (!window) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/interfaces/base/nsIContentProcess.idl
+++ b/dom/interfaces/base/nsIContentProcess.idl
@@ -46,11 +46,11 @@ interface nsIContentProcessProvider : ns
   const int32_t NEW_PROCESS = -1;
 
   /**
    * Given aAliveProcesses (with an opener aOpener), choose which process of
    * aType to use. Return nsIContentProcessProvider.NEW_PROCESS to ask the
    * caller to create a new content process.
    */
   int32_t provideProcess(in AString aType, in nsIContentProcessInfo aOpener,
-                         [array, size_is(aCount)] in nsIContentProcessInfo aAliveProcesses,
-                         in uint32_t aCount, in uint32_t aMaxCount);
+                         in Array<nsIContentProcessInfo> aAliveProcesses,
+                         in uint32_t aMaxCount);
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -619,21 +619,25 @@ bool ContentChild::Init(MessageLoop* aIO
   // on its own when deciding which backend to use, and when starting under
   // XWayland, it may choose to start with the wayland backend
   // instead of the x11 backend.
   // The DISPLAY environment variable is normally set by the parent process.
   // The MOZ_GDK_DISPLAY environment variable is set from nsAppRunner.cpp
   // when --display is set by the command line.
   if (!gfxPlatform::IsHeadless()) {
     const char* display_name = PR_GetEnv("MOZ_GDK_DISPLAY");
-#  ifndef MOZ_WAYLAND
     if (!display_name) {
-      display_name = PR_GetEnv("DISPLAY");
+      bool waylandDisabled = true;
+#  ifdef MOZ_WAYLAND
+      waylandDisabled = IsWaylandDisabled();
+#  endif
+      if (waylandDisabled) {
+        display_name = PR_GetEnv("DISPLAY");
+      }
     }
-#  endif
     if (display_name) {
       int argc = 3;
       char option_name[] = "--display";
       char* argv[] = {
           // argv0 is unused because g_set_prgname() was called in
           // XRE_InitChildProcess().
           nullptr, option_name, const_cast<char*>(display_name), nullptr};
       char** argvp = argv;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -837,35 +837,34 @@ already_AddRefed<ContentParent> ContentP
     // We never want to re-use Large-Allocation processes.
     if (contentParents.Length() >= maxContentParents) {
       return GetNewOrUsedBrowserProcess(aFrameElement,
                                         NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
                                         aPriority, aOpener);
     }
   } else {
     uint32_t numberOfParents = contentParents.Length();
-    nsTArray<nsIContentProcessInfo*> infos(numberOfParents);
+    nsTArray<RefPtr<nsIContentProcessInfo>> infos(numberOfParents);
     for (auto* cp : contentParents) {
       infos.AppendElement(cp->mScriptableHelper);
     }
 
     if (aPreferUsed && numberOfParents) {
       // For the preloaded browser we don't want to create a new process but
       // reuse an existing one.
       maxContentParents = numberOfParents;
     }
 
     nsCOMPtr<nsIContentProcessProvider> cpp =
         do_GetService("@mozilla.org/ipc/processselector;1");
     nsIContentProcessInfo* openerInfo =
         aOpener ? aOpener->mScriptableHelper.get() : nullptr;
     int32_t index;
-    if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(
-                   aRemoteType, openerInfo, infos.Elements(), infos.Length(),
-                   maxContentParents, &index))) {
+    if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, openerInfo, infos,
+                                                maxContentParents, &index))) {
       // If the provider returned an existing ContentParent, use that one.
       if (0 <= index && static_cast<uint32_t>(index) <= maxContentParents) {
         RefPtr<ContentParent> retval = contentParents[index];
         return retval.forget();
       }
     } else {
       // If there was a problem with the JS chooser, fall back to a random
       // selection.
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -29,16 +29,23 @@ var EXPORTED_SYMBOLS = ["WebVTT"];
 
 const {Services} = ChromeUtils.import('resource://gre/modules/Services.jsm');
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "supportPseudo",
                                       "media.webvtt.pseudo.enabled", false);
 
 (function(global) {
+  var DEBUG_LOG = false;
+
+  function LOG(message) {
+    if (DEBUG_LOG) {
+      dump("[vtt] " + message + "\n");
+    }
+  }
 
   var _objCreate = Object.create || (function() {
     function F() {}
     return function(o) {
       if (arguments.length !== 1) {
         throw new Error('Object.create shim only accepts one parameter.');
       }
       F.prototype = o;
@@ -794,31 +801,41 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     get bottom() {
       return this.top + this.height;
     }
 
     get right() {
       return this.left + this.width;
     }
 
+    // This function is used for debugging, it will return the box's information.
+    getBoxInfoInChars() {
+      return `top=${this.top}, bottom=${this.bottom}, left=${this.left}, ` +
+             `right=${this.right}, width=${this.width}, height=${this.height}`;
+    }
+
     // Move the box along a particular axis. Optionally pass in an amount to move
     // the box. If no amount is passed then the default is the line height of the
     // box.
     move(axis, toMove) {
       switch (axis) {
       case "+x":
+        LOG(`box's left moved from ${this.left} to ${this.left + toMove}`);
         this.left += toMove;
         break;
       case "-x":
+        LOG(`box's left moved from ${this.left} to ${this.left - toMove}`);
         this.left -= toMove;
         break;
       case "+y":
+        LOG(`box's top moved from ${this.top} to ${this.top + toMove}`);
         this.top += toMove;
         break;
       case "-y":
+        LOG(`box's top moved from ${this.top} to ${this.top - toMove}`);
         this.top -= toMove;
         break;
       }
     }
 
     // Check if this box overlaps another box, b2.
     overlaps(b2) {
       return this.left < b2.right &&
@@ -1132,16 +1149,18 @@ XPCOMUtils.defineLazyPreferenceGetter(th
       boxPositions.push(controlBarBox);
     }
 
     // https://w3c.github.io/webvtt/#processing-model 6.1.12.1
     // Create regionNode
     let regionNodeBoxes = {};
     let regionNodeBox;
 
+    LOG(`=== processCues ===`);
+
     for (let i = 0; i < cues.length; i++) {
       cue = cues[i];
       if (cue.region != null) {
        // 6.1.14.1
         styleBox = new RegionCueStyleBox(window, cue);
 
         if (!regionNodeBoxes[cue.region.id]) {
           // create regionNode
@@ -1178,16 +1197,17 @@ XPCOMUtils.defineLazyPreferenceGetter(th
         // result of algorithm doesn't want us to show the cue when we don't
         // have any room for this cue.
         let cueBox = adjustBoxPosition(styleBox, containerBox, controlBarBox, boxPositions);
         if (cueBox) {
           // Remember the computed div so that we don't have to recompute it later
           // if we don't have too.
           cue.displayState = styleBox.div;
           boxPositions.push(cueBox);
+          LOG(`cue ${i}, ` + cueBox.getBoxInfoInChars());
         }
       }
     }
   };
 
   WebVTT.Parser = function(window, decoder) {
     this.window = window;
     this.state = "INITIAL";
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -559,59 +559,22 @@ nsresult nsMixedContentBlocker::ShouldLo
   // by the innerMost URL.
   nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation);
   if (!innerContentLocation) {
     NS_ERROR("Can't get innerURI from aContentLocation");
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
-  /* Get the scheme of the sub-document resource to be requested. If it is
-   * a safe to load in an https context then mixed content doesn't apply.
-   *
-   * Check Protocol Flags to determine if scheme is safe to load:
-   * URI_DOES_NOT_RETURN_DATA - e.g.
-   *   "mailto"
-   * URI_IS_LOCAL_RESOURCE - e.g.
-   *   "data",
-   *   "resource",
-   *   "moz-icon"
-   * URI_INHERITS_SECURITY_CONTEXT - e.g.
-   *   "javascript"
-   * URI_IS_POTENTIALLY_TRUSTWORTHY - e.g.
-   *   "https",
-   *   "moz-safe-about"
-   *
-   */
-  bool schemeLocal = false;
-  bool schemeNoReturnData = false;
-  bool schemeInherits = false;
-  bool schemeSecure = false;
-  if (NS_FAILED(NS_URIChainHasFlags(innerContentLocation,
-                                    nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
-                                    &schemeLocal)) ||
-      NS_FAILED(NS_URIChainHasFlags(
-          innerContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
-          &schemeNoReturnData)) ||
-      NS_FAILED(
-          NS_URIChainHasFlags(innerContentLocation,
-                              nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
-                              &schemeInherits)) ||
-      NS_FAILED(NS_URIChainHasFlags(
-          innerContentLocation,
-          nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY, &schemeSecure))) {
-    *aDecision = REJECT_REQUEST;
-    return NS_ERROR_FAILURE;
-  }
   // TYPE_IMAGE redirects are cached based on the original URI, not the final
   // destination and hence cache hits for images may not have the correct
   // innerContentLocation.  Check if the cached hit went through an http
   // redirect, and if it did, we can't treat this as a secure subresource.
   if (!aHadInsecureImageRedirect &&
-      (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure)) {
+      URISafeToBeLoadedInSecureContext(innerContentLocation)) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   // Since there are cases where aRequestingLocation and aRequestPrincipal are
   // definitely not the owning document, we try to ignore them by extracting the
   // requestingLocation in the following order:
   // 1) from the aRequestingContext, either extracting
@@ -1077,16 +1040,53 @@ nsresult nsMixedContentBlocker::ShouldLo
     // from within ShouldLoad
     nsContentUtils::AddScriptRunner(new nsMixedContentEvent(
         aRequestingContext, classification, rootHasSecureConnection));
     *aDecision = ACCEPT;
     return NS_OK;
   }
 }
 
+bool nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(nsIURI* aURI) {
+  /* Returns a bool if the URI can be loaded as a sub resource safely.
+   *
+   * Check Protocol Flags to determine if scheme is safe to load:
+   * URI_DOES_NOT_RETURN_DATA - e.g.
+   *   "mailto"
+   * URI_IS_LOCAL_RESOURCE - e.g.
+   *   "data",
+   *   "resource",
+   *   "moz-icon"
+   * URI_INHERITS_SECURITY_CONTEXT - e.g.
+   *   "javascript"
+   * URI_IS_POTENTIALLY_TRUSTWORTHY - e.g.
+   *   "https",
+   *   "moz-safe-about"
+   *
+   */
+  bool schemeLocal = false;
+  bool schemeNoReturnData = false;
+  bool schemeInherits = false;
+  bool schemeSecure = false;
+  if (NS_FAILED(NS_URIChainHasFlags(
+          aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
+      NS_FAILED(NS_URIChainHasFlags(
+          aURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
+          &schemeNoReturnData)) ||
+      NS_FAILED(NS_URIChainHasFlags(
+          aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
+          &schemeInherits)) ||
+      NS_FAILED(NS_URIChainHasFlags(
+          aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
+          &schemeSecure))) {
+    return false;
+  }
+  return (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure);
+}
+
 NS_IMETHODIMP
 nsMixedContentBlocker::ShouldProcess(nsIURI* aContentLocation,
                                      nsILoadInfo* aLoadInfo,
                                      const nsACString& aMimeGuess,
                                      int16_t* aDecision) {
   if (!aContentLocation) {
     // aContentLocation may be null when a plugin is loading without an
     // associated URI resource
--- a/dom/security/nsMixedContentBlocker.h
+++ b/dom/security/nsMixedContentBlocker.h
@@ -66,16 +66,18 @@ class nsMixedContentBlocker : public nsI
                              nsIURI* aRequestingLocation,
                              nsISupports* aRequestingContext,
                              const nsACString& aMimeGuess,
                              nsIPrincipal* aRequestPrincipal,
                              int16_t* aDecision);
   static void AccumulateMixedContentHSTS(
       nsIURI* aURI, bool aActive, const OriginAttributes& aOriginAttributes);
 
+  static bool URISafeToBeLoadedInSecureContext(nsIURI* aURI);
+
   static bool ShouldUpgradeMixedDisplayContent();
 
   static bool sBlockMixedScript;
   static bool sBlockMixedObjectSubrequest;
   static bool sBlockMixedDisplay;
   static bool sUpgradeMixedDisplay;
 };
 
--- a/dom/svg/SVGMPathElement.cpp
+++ b/dom/svg/SVGMPathElement.cpp
@@ -72,17 +72,17 @@ already_AddRefed<DOMSVGAnimatedString> S
 nsresult SVGMPathElement::BindToTree(Document* aDocument, nsIContent* aParent,
                                      nsIContent* aBindingParent) {
   MOZ_ASSERT(!mPathTracker.get(),
              "Shouldn't have href-target yet (or it should've been cleared)");
   nsresult rv =
       SVGMPathElementBase::BindToTree(aDocument, aParent, aBindingParent);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (aDocument) {
+  if (IsInComposedDoc()) {
     const nsAttrValue* hrefAttrValue =
         HasAttr(kNameSpaceID_None, nsGkAtoms::href)
             ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None)
             : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
     if (hrefAttrValue) {
       UpdateHrefTarget(aParent, hrefAttrValue->GetStringValue());
     }
   }
@@ -98,17 +98,17 @@ void SVGMPathElement::UnbindFromTree(boo
 bool SVGMPathElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
                                      const nsAString& aValue,
                                      nsIPrincipal* aMaybeScriptedPrincipal,
                                      nsAttrValue& aResult) {
   bool returnVal = SVGMPathElementBase::ParseAttribute(
       aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
   if ((aNamespaceID == kNameSpaceID_XLink ||
        aNamespaceID == kNameSpaceID_None) &&
-      aAttribute == nsGkAtoms::href && IsInUncomposedDoc()) {
+      aAttribute == nsGkAtoms::href && IsInComposedDoc()) {
     // Note: If we fail the IsInDoc call, it's ok -- we'll update the target
     // on next BindToTree call.
 
     // Note: "href" takes priority over xlink:href. So if "xlink:href" is being
     // set here, we only let that update our target if "href" is *unset*.
     if (aNamespaceID != kNameSpaceID_XLink ||
         !mStringAttributes[HREF].IsExplicitlySet()) {
       UpdateHrefTarget(GetParent(), aValue);
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -59,16 +59,17 @@
 #endif
 #ifdef MOZ_WIDGET_GTK
 #  include <gtk/gtk.h>
 #  include "skia/include/ports/SkTypeface_cairo.h"
 #endif
 #ifdef MOZ_GECKO_PROFILER
 #  include "ChildProfilerController.h"
 #endif
+#include "nsAppRunner.h"
 
 namespace mozilla {
 namespace gfx {
 
 using namespace ipc;
 using namespace layers;
 
 static GPUParent* sGPUParent;
@@ -219,17 +220,26 @@ mozilla::ipc::IPCResult GPUParent::RecvI
     nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
     Unused << gfxInfo;
 
     Factory::EnsureDWriteFactory();
   }
 #endif
 
 #if defined(MOZ_WIDGET_GTK)
-  char* display_name = PR_GetEnv("DISPLAY");
+  char* display_name = PR_GetEnv("MOZ_GDK_DISPLAY");
+  if (!display_name) {
+    bool waylandDisabled = true;
+#  ifdef MOZ_WAYLAND
+    waylandDisabled = IsWaylandDisabled();
+#  endif
+    if (waylandDisabled) {
+      display_name = PR_GetEnv("DISPLAY");
+    }
+  }
   if (display_name) {
     int argc = 3;
     char option_name[] = "--display";
     char* argv[] = {// argv0 is unused because g_set_prgname() was called in
                     // XRE_InitChildProcess().
                     nullptr, option_name, display_name, nullptr};
     char** argvp = argv;
     gtk_init(&argc, &argvp);
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2658,32 +2658,53 @@ static FeatureState& WebRenderHardwareQu
               0x3ea0,
               0x3ea9,
               0x3ea2,
               0x3ea6,
               0x3ea7,
               0x3ea8,
               0x3ea5,
 
+              // broadwell gt2+
+              0x1612,
+              0x1616,
+              0x161a,
+              0x161b,
+              0x161d,
+              0x161e,
+              0x1622,
+              0x1626,
+              0x162a,
+              0x162b,
+              0x162d,
+              0x162e,
+              0x1632,
+              0x1636,
+              0x163a,
+              0x163b,
+              0x163d,
+              0x163e,
+
               // HD Graphics 4600
               0x0412,
               0x0416,
               0x041a,
               0x041b,
               0x041e,
               0x0a12,
               0x0a16,
               0x0a1a,
               0x0a1b,
               0x0a1e,
           };
           bool supported = false;
           for (uint16_t id : supportedDevices) {
             if (deviceID == id) {
               supported = true;
+              break;
             }
           }
           if (!supported) {
             featureWebRenderQualified.Disable(
                 FeatureStatus::BlockedDeviceTooOld, "Device too old",
                 NS_LITERAL_CSTRING("FEATURE_FAILURE_DEVICE_TOO_OLD"));
           }
 #  ifdef MOZ_WIDGET_GTK
--- a/intl/hyphenation/glue/nsHyphenationManager.cpp
+++ b/intl/hyphenation/glue/nsHyphenationManager.cpp
@@ -75,16 +75,19 @@ nsHyphenationManager::~nsHyphenationMana
 
 already_AddRefed<nsHyphenator> nsHyphenationManager::GetHyphenator(
     nsAtom* aLocale) {
   RefPtr<nsHyphenator> hyph;
   mHyphenators.Get(aLocale, getter_AddRefs(hyph));
   if (hyph) {
     return hyph.forget();
   }
+  nsAutoCString hyphCapPref("intl.hyphenate-capitalized.");
+  hyphCapPref.Append(nsAtomCString(aLocale));
+  bool hyphenateCapitalized = Preferences::GetBool(hyphCapPref.get());
   nsCOMPtr<nsIURI> uri = mPatternFiles.Get(aLocale);
   if (!uri) {
     RefPtr<nsAtom> alias = mHyphAliases.Get(aLocale);
     if (alias) {
       mHyphenators.Get(alias, getter_AddRefs(hyph));
       if (hyph) {
         return hyph.forget();
       }
@@ -106,17 +109,17 @@ already_AddRefed<nsHyphenator> nsHyphena
         localeStr.ReplaceLiteral(i, localeStr.Length() - i, "-*");
         RefPtr<nsAtom> fuzzyLocale = NS_Atomize(localeStr);
         return GetHyphenator(fuzzyLocale);
       } else {
         return nullptr;
       }
     }
   }
-  hyph = new nsHyphenator(uri);
+  hyph = new nsHyphenator(uri, hyphenateCapitalized);
   if (hyph->IsValid()) {
     mHyphenators.Put(aLocale, hyph);
     return hyph.forget();
   }
 #ifdef DEBUG
   nsCString msg("failed to load patterns from ");
   msg += uri->GetSpecOrDefault();
   NS_WARNING(msg.get());
--- a/intl/hyphenation/glue/nsHyphenator.cpp
+++ b/intl/hyphenation/glue/nsHyphenator.cpp
@@ -6,17 +6,18 @@
 #include "nsHyphenator.h"
 #include "nsIFile.h"
 #include "nsUTF8Utils.h"
 #include "nsUnicodeProperties.h"
 #include "nsIURI.h"
 
 #include "hyphen.h"
 
-nsHyphenator::nsHyphenator(nsIURI* aURI) : mDict(nullptr) {
+nsHyphenator::nsHyphenator(nsIURI* aURI, bool aHyphenateCapitalized)
+    : mDict(nullptr), mHyphenateCapitalized(aHyphenateCapitalized) {
   nsCString uriSpec;
   nsresult rv = aURI->GetSpec(uriSpec);
   if (NS_FAILED(rv)) {
     return;
   }
   mDict = hnj_hyphen_load(uriSpec.get());
 #ifdef DEBUG
   if (mDict) {
@@ -65,84 +66,100 @@ nsresult nsHyphenator::Hyphenate(const n
       }
       wordLimit = i + chLen;
       if (i + chLen < aString.Length()) {
         continue;
       }
     }
 
     if (inWord) {
-      // Convert the word to utf-8 for libhyphen, lowercasing it as we go
-      // so that it will match the (lowercased) patterns (bug 1105644).
-      nsAutoCString utf8;
-      const char16_t* const begin = aString.BeginReading();
-      const char16_t* cur = begin + wordStart;
-      const char16_t* end = begin + wordLimit;
-      while (cur < end) {
-        uint32_t ch = *cur++;
-
-        if (NS_IS_HIGH_SURROGATE(ch)) {
-          if (cur < end && NS_IS_LOW_SURROGATE(*cur)) {
-            ch = SURROGATE_TO_UCS4(ch, *cur++);
-          } else {
-            ch = 0xfffd;  // unpaired surrogate, treat as REPLACEMENT CHAR
-          }
-        } else if (NS_IS_LOW_SURROGATE(ch)) {
-          ch = 0xfffd;  // unpaired surrogate
-        }
-
-        // XXX What about language-specific casing? Consider Turkish I/i...
-        // In practice, it looks like the current patterns will not be
-        // affected by this, as they treat dotted and undotted i similarly.
-        ch = ToLowerCase(ch);
-
-        if (ch < 0x80) {  // U+0000 - U+007F
-          utf8.Append(ch);
-        } else if (ch < 0x0800) {  // U+0100 - U+07FF
-          utf8.Append(0xC0 | (ch >> 6));
-          utf8.Append(0x80 | (0x003F & ch));
-        } else if (ch < 0x10000) {  // U+0800 - U+D7FF,U+E000 - U+FFFF
-          utf8.Append(0xE0 | (ch >> 12));
-          utf8.Append(0x80 | (0x003F & (ch >> 6)));
-          utf8.Append(0x80 | (0x003F & ch));
-        } else {
-          utf8.Append(0xF0 | (ch >> 18));
-          utf8.Append(0x80 | (0x003F & (ch >> 12)));
-          utf8.Append(0x80 | (0x003F & (ch >> 6)));
-          utf8.Append(0x80 | (0x003F & ch));
-        }
-      }
-
-      AutoTArray<char, 200> utf8hyphens;
-      utf8hyphens.SetLength(utf8.Length() + 5);
-      char** rep = nullptr;
-      int* pos = nullptr;
-      int* cut = nullptr;
-      int err = hnj_hyphen_hyphenate2((HyphenDict*)mDict, utf8.BeginReading(),
-                                      utf8.Length(), utf8hyphens.Elements(),
-                                      nullptr, &rep, &pos, &cut);
-      if (!err) {
-        // Surprisingly, hnj_hyphen_hyphenate2 converts the 'hyphens' buffer
-        // from utf8 code unit indexing (which would match the utf8 input
-        // string directly) to Unicode character indexing.
-        // We then need to convert this to utf16 code unit offsets for Gecko.
-        const char* hyphPtr = utf8hyphens.Elements();
-        const char16_t* cur = begin + wordStart;
-        const char16_t* end = begin + wordLimit;
-        while (cur < end) {
-          if (*hyphPtr & 0x01) {
-            aHyphens[cur - begin] = true;
-          }
-          cur++;
-          if (cur < end && NS_IS_LOW_SURROGATE(*cur) &&
-              NS_IS_HIGH_SURROGATE(*(cur - 1))) {
-            cur++;
-          }
-          hyphPtr++;
-        }
-      }
+      HyphenateWord(aString, wordStart, wordLimit, aHyphens);
+      inWord = false;
     }
-
-    inWord = false;
   }
 
   return NS_OK;
 }
+
+void nsHyphenator::HyphenateWord(const nsAString& aString, uint32_t aStart,
+                                 uint32_t aLimit, nsTArray<bool>& aHyphens) {
+  // Convert word from aStart and aLimit in aString to utf-8 for libhyphen,
+  // lowercasing it as we go so that it will match the (lowercased) patterns
+  // (bug 1105644).
+  nsAutoCString utf8;
+  const char16_t* const begin = aString.BeginReading();
+  const char16_t* cur = begin + aStart;
+  const char16_t* end = begin + aLimit;
+  bool firstLetter = true;
+  while (cur < end) {
+    uint32_t ch = *cur++;
+
+    if (NS_IS_HIGH_SURROGATE(ch)) {
+      if (cur < end && NS_IS_LOW_SURROGATE(*cur)) {
+        ch = SURROGATE_TO_UCS4(ch, *cur++);
+      } else {
+        ch = 0xfffd;  // unpaired surrogate, treat as REPLACEMENT CHAR
+      }
+    } else if (NS_IS_LOW_SURROGATE(ch)) {
+      ch = 0xfffd;  // unpaired surrogate
+    }
+
+    // XXX What about language-specific casing? Consider Turkish I/i...
+    // In practice, it looks like the current patterns will not be
+    // affected by this, as they treat dotted and undotted i similarly.
+    uint32_t origCh = ch;
+    ch = ToLowerCase(ch);
+
+    // Avoid hyphenating capitalized words (bug 1550532) unless explicitly
+    // allowed by prefs for the language in use.
+    if (firstLetter) {
+      if (!mHyphenateCapitalized && ch != origCh) {
+        return;
+      }
+      firstLetter = false;
+    }
+
+    if (ch < 0x80) {  // U+0000 - U+007F
+      utf8.Append(ch);
+    } else if (ch < 0x0800) {  // U+0100 - U+07FF
+      utf8.Append(0xC0 | (ch >> 6));
+      utf8.Append(0x80 | (0x003F & ch));
+    } else if (ch < 0x10000) {  // U+0800 - U+D7FF,U+E000 - U+FFFF
+      utf8.Append(0xE0 | (ch >> 12));
+      utf8.Append(0x80 | (0x003F & (ch >> 6)));
+      utf8.Append(0x80 | (0x003F & ch));
+    } else {
+      utf8.Append(0xF0 | (ch >> 18));
+      utf8.Append(0x80 | (0x003F & (ch >> 12)));
+      utf8.Append(0x80 | (0x003F & (ch >> 6)));
+      utf8.Append(0x80 | (0x003F & ch));
+    }
+  }
+
+  AutoTArray<char, 200> utf8hyphens;
+  utf8hyphens.SetLength(utf8.Length() + 5);
+  char** rep = nullptr;
+  int* pos = nullptr;
+  int* cut = nullptr;
+  int err = hnj_hyphen_hyphenate2((HyphenDict*)mDict, utf8.BeginReading(),
+                                  utf8.Length(), utf8hyphens.Elements(),
+                                  nullptr, &rep, &pos, &cut);
+  if (!err) {
+    // Surprisingly, hnj_hyphen_hyphenate2 converts the 'hyphens' buffer
+    // from utf8 code unit indexing (which would match the utf8 input
+    // string directly) to Unicode character indexing.
+    // We then need to convert this to utf16 code unit offsets for Gecko.
+    const char* hyphPtr = utf8hyphens.Elements();
+    const char16_t* cur = begin + aStart;
+    const char16_t* end = begin + aLimit;
+    while (cur < end) {
+      if (*hyphPtr & 0x01) {
+        aHyphens[cur - begin] = true;
+      }
+      cur++;
+      if (cur < end && NS_IS_LOW_SURROGATE(*cur) &&
+          NS_IS_HIGH_SURROGATE(*(cur - 1))) {
+        cur++;
+      }
+      hyphPtr++;
+    }
+  }
+}
--- a/intl/hyphenation/glue/nsHyphenator.h
+++ b/intl/hyphenation/glue/nsHyphenator.h
@@ -9,24 +9,27 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 class nsIURI;
 
 class nsHyphenator {
  public:
-  explicit nsHyphenator(nsIURI* aURI);
+  nsHyphenator(nsIURI* aURI, bool aHyphenateCapitalized);
 
   NS_INLINE_DECL_REFCOUNTING(nsHyphenator)
 
   bool IsValid();
 
   nsresult Hyphenate(const nsAString& aText, nsTArray<bool>& aHyphens);
 
  private:
   ~nsHyphenator();
 
- protected:
+  void HyphenateWord(const nsAString& aString, uint32_t aStart,
+                     uint32_t aLimit, nsTArray<bool>& aHyphens);
+
   void* mDict;
+  bool mHyphenateCapitalized;
 };
 
 #endif  // nsHyphenator_h__
--- a/js/public/GCVector.h
+++ b/js/public/GCVector.h
@@ -299,11 +299,22 @@ template <typename T>
 class RootedVector : public Rooted<StackGCVector<T>> {
   using Vec = StackGCVector<T>;
   using Base = Rooted<Vec>;
 
  public:
   explicit RootedVector(JSContext* cx) : Base(cx, Vec(cx)) {}
 };
 
+// For use in rust code, an analog to RootedVector that doesn't require
+// instances to be destroyed in LIFO order.
+template <typename T>
+class PersistentRootedVector : public PersistentRooted<StackGCVector<T>> {
+  using Vec = StackGCVector<T>;
+  using Base = PersistentRooted<Vec>;
+
+ public:
+  explicit PersistentRootedVector(JSContext* cx) : Base(cx, Vec(cx)) {}
+};
+
 }  // namespace JS
 
 #endif  // js_GCVector_h
--- a/js/public/PropertySpec.h
+++ b/js/public/PropertySpec.h
@@ -14,17 +14,17 @@
 #include <stdint.h>     // uint8_t, uint16_t, int32_t, uint32_t, uintptr_t
 #include <type_traits>  // std::enable_if
 
 #include "jstypes.h"  // JS_PUBLIC_API
 
 #include "js/CallArgs.h"            // JSNative
 #include "js/PropertyDescriptor.h"  // JSPROP_*
 #include "js/RootingAPI.h"          // JS::MutableHandle
-#include "js/Symbol.h"              // JS::SymbolCode
+#include "js/Symbol.h"              // JS::SymbolCode, PropertySpecNameIsSymbol
 #include "js/Value.h"               // JS::Value
 
 struct JSContext;
 struct JSJitInfo;
 
 /**
  * Wrapper to relace JSNative for JSPropertySpecs and JSFunctionSpecs. This will
  * allow us to pass one JSJitInfo per function with the property/function spec,
@@ -143,66 +143,133 @@ struct JSPropertySpec {
       return AccessorsOrValue(getter, setter);
     }
 
     static constexpr AccessorsOrValue fromValue(ValueWrapper value) {
       return AccessorsOrValue(value);
     }
   };
 
-  const char* name;
+  union Name {
+   private:
+    const char* string_;
+    uintptr_t symbol_;
+
+   public:
+    Name() = delete;
+
+    explicit constexpr Name(const char* str) : string_(str) {}
+    explicit constexpr Name(JS::SymbolCode symbol)
+        : symbol_(uint32_t(symbol) + 1) {}
+
+    explicit operator bool() const { return !!symbol_; }
+
+    bool isSymbol() const { return JS::PropertySpecNameIsSymbol(symbol_); }
+    JS::SymbolCode symbol() const {
+      MOZ_ASSERT(isSymbol());
+      return JS::SymbolCode(symbol_ - 1);
+    }
+
+    bool isString() const { return !isSymbol(); }
+    const char* string() const {
+      MOZ_ASSERT(isString());
+      return string_;
+    }
+  };
+
+  Name name;
   uint8_t flags;
   AccessorsOrValue u;
 
  private:
   JSPropertySpec() = delete;
 
   constexpr JSPropertySpec(const char* name, uint8_t flags, AccessorsOrValue u)
       : name(name), flags(flags), u(u) {}
+  constexpr JSPropertySpec(JS::SymbolCode name, uint8_t flags,
+                           AccessorsOrValue u)
+      : name(name), flags(flags), u(u) {}
 
  public:
   JSPropertySpec(const JSPropertySpec& other) = default;
 
   static constexpr JSPropertySpec nativeAccessors(
       const char* name, uint8_t flags, JSNative getter,
       const JSJitInfo* getterInfo, JSNative setter = nullptr,
       const JSJitInfo* setterInfo = nullptr) {
     return JSPropertySpec(
         name, flags,
         AccessorsOrValue::fromAccessors(
             JSPropertySpec::Accessor::nativeAccessor(getter, getterInfo),
             JSPropertySpec::Accessor::nativeAccessor(setter, setterInfo)));
   }
 
+  static constexpr JSPropertySpec nativeAccessors(
+      JS::SymbolCode name, uint8_t flags, JSNative getter,
+      const JSJitInfo* getterInfo, JSNative setter = nullptr,
+      const JSJitInfo* setterInfo = nullptr) {
+    return JSPropertySpec(
+        name, flags,
+        AccessorsOrValue::fromAccessors(
+            JSPropertySpec::Accessor::nativeAccessor(getter, getterInfo),
+            JSPropertySpec::Accessor::nativeAccessor(setter, setterInfo)));
+  }
+
   static constexpr JSPropertySpec selfHostedAccessors(
       const char* name, uint8_t flags, const char* getterName,
       const char* setterName = nullptr) {
     return JSPropertySpec(
         name, flags | JSPROP_GETTER | (setterName ? JSPROP_SETTER : 0),
         AccessorsOrValue::fromAccessors(
             JSPropertySpec::Accessor::selfHostedAccessor(getterName),
             setterName
                 ? JSPropertySpec::Accessor::selfHostedAccessor(setterName)
                 : JSPropertySpec::Accessor::noAccessor()));
   }
 
+  static constexpr JSPropertySpec selfHostedAccessors(
+      JS::SymbolCode name, uint8_t flags, const char* getterName,
+      const char* setterName = nullptr) {
+    return JSPropertySpec(
+        name, flags | JSPROP_GETTER | (setterName ? JSPROP_SETTER : 0),
+        AccessorsOrValue::fromAccessors(
+            JSPropertySpec::Accessor::selfHostedAccessor(getterName),
+            setterName
+                ? JSPropertySpec::Accessor::selfHostedAccessor(setterName)
+                : JSPropertySpec::Accessor::noAccessor()));
+  }
+
   static constexpr JSPropertySpec int32Value(const char* name, uint8_t flags,
                                              int32_t n) {
     return JSPropertySpec(name, flags | JSPROP_INTERNAL_USE_BIT,
                           AccessorsOrValue::fromValue(
                               JSPropertySpec::ValueWrapper::int32Value(n)));
   }
 
+  static constexpr JSPropertySpec int32Value(JS::SymbolCode name, uint8_t flags,
+                                             int32_t n) {
+    return JSPropertySpec(name, flags | JSPROP_INTERNAL_USE_BIT,
+                          AccessorsOrValue::fromValue(
+                              JSPropertySpec::ValueWrapper::int32Value(n)));
+  }
+
   static constexpr JSPropertySpec stringValue(const char* name, uint8_t flags,
                                               const char* s) {
     return JSPropertySpec(name, flags | JSPROP_INTERNAL_USE_BIT,
                           AccessorsOrValue::fromValue(
                               JSPropertySpec::ValueWrapper::stringValue(s)));
   }
 
+  static constexpr JSPropertySpec stringValue(JS::SymbolCode name,
+                                              uint8_t flags, const char* s) {
+    return JSPropertySpec(name, flags | JSPROP_INTERNAL_USE_BIT,
+                          AccessorsOrValue::fromValue(
+                              JSPropertySpec::ValueWrapper::stringValue(s)));
+  }
+
   static constexpr JSPropertySpec sentinel() {
     return JSPropertySpec(nullptr, 0,
                           AccessorsOrValue::fromAccessors(
                               JSPropertySpec::Accessor::noAccessor(),
                               JSPropertySpec::Accessor::noAccessor()));
   }
 
   bool isAccessor() const { return !(flags & JSPROP_INTERNAL_USE_BIT); }
@@ -244,71 +311,57 @@ struct JSPropertySpec {
   }
 
   void checkAccessorsAreSelfHosted() const {
     MOZ_ASSERT(!u.accessors.getter.selfHosted.unused);
     MOZ_ASSERT(!u.accessors.setter.selfHosted.unused);
   }
 };
 
-// JSPropertySpec::{nativeAccessors, selfHostedAccessors,int32Value,
-// stringValue} methods require symbol names to be casted to `const char*`,
-// and the cast is `reinterpret_cast`.
-//
-// Provide a macro for the cast because of the following reasons:
-//
-//   * `reinterpret_cast` cannot be used in constexpr
-//   * using non-constexpr static method in parameter disables constexpr of
-//     above methods
-//   * top-level `reinterpret_cast` doesn't disable constexpr of above methods
-//
-#define SYMBOL_TO_PROPERTY_NAME(symbol) \
-  reinterpret_cast<const char*>(uint32_t(symbol) + 1)
-
 #define JS_CHECK_ACCESSOR_FLAGS(flags)                                         \
   (static_cast<std::enable_if<((flags) & ~(JSPROP_ENUMERATE |                  \
                                            JSPROP_PERMANENT)) == 0>::type>(0), \
    (flags))
 
 #define JS_PSG(name, getter, flags)                                     \
   JSPropertySpec::nativeAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                   getter, nullptr)
 #define JS_PSGS(name, getter, setter, flags)                            \
   JSPropertySpec::nativeAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                   getter, nullptr, setter, nullptr)
-#define JS_SYM_GET(symbol, getter, flags)                \
-  JSPropertySpec::nativeAccessors(                       \
-      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol), \
-      JS_CHECK_ACCESSOR_FLAGS(flags), getter, nullptr)
+#define JS_SYM_GET(symbol, getter, flags)                                 \
+  JSPropertySpec::nativeAccessors(::JS::SymbolCode::symbol,               \
+                                  JS_CHECK_ACCESSOR_FLAGS(flags), getter, \
+                                  nullptr)
 #define JS_SELF_HOSTED_GET(name, getterName, flags)                         \
   JSPropertySpec::selfHostedAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                       getterName)
 #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags)          \
   JSPropertySpec::selfHostedAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                       getterName, setterName)
 #define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags) \
   JSPropertySpec::selfHostedAccessors(                    \
-      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol),  \
-      JS_CHECK_ACCESSOR_FLAGS(flags), getterName)
+      ::JS::SymbolCode::symbol, JS_CHECK_ACCESSOR_FLAGS(flags), getterName)
 #define JS_STRING_PS(name, string, flags) \
   JSPropertySpec::stringValue(name, flags, string)
 #define JS_STRING_SYM_PS(symbol, string, flags) \
-  JSPropertySpec::stringValue(                  \
-      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol), flags, string)
+  JSPropertySpec::stringValue(::JS::SymbolCode::symbol, flags, string)
 #define JS_INT32_PS(name, value, flags) \
   JSPropertySpec::int32Value(name, flags, value)
 #define JS_PS_END JSPropertySpec::sentinel()
 
 /**
  * To define a native function, set call to a JSNativeWrapper. To define a
  * self-hosted function, set selfHostedName to the name of a function
  * compiled during JSRuntime::initSelfHosting.
  */
 struct JSFunctionSpec {
-  const char* name;
+  using Name = JSPropertySpec::Name;
+
+  Name name;
   JSNativeWrapper call;
   uint16_t nargs;
   uint16_t flags;
   const char* selfHostedName;
 };
 
 /*
  * Terminating sentinel initializer to put at the end of a JSFunctionSpec array
@@ -334,16 +387,14 @@ struct JSFunctionSpec {
 #define JS_SYM_FN(symbol, call, nargs, flags) \
   JS_SYM_FNSPEC(symbol, call, nullptr, nargs, flags, nullptr)
 #define JS_FNINFO(name, call, info, nargs, flags) \
   JS_FNSPEC(name, call, info, nargs, flags, nullptr)
 #define JS_SELF_HOSTED_FN(name, selfHostedName, nargs, flags) \
   JS_FNSPEC(name, nullptr, nullptr, nargs, flags, selfHostedName)
 #define JS_SELF_HOSTED_SYM_FN(symbol, selfHostedName, nargs, flags) \
   JS_SYM_FNSPEC(symbol, nullptr, nullptr, nargs, flags, selfHostedName)
-#define JS_SYM_FNSPEC(symbol, call, info, nargs, flags, selfHostedName)      \
-  JS_FNSPEC(                                                                 \
-      reinterpret_cast<const char*>(uint32_t(::JS::SymbolCode::symbol) + 1), \
-      call, info, nargs, flags, selfHostedName)
+#define JS_SYM_FNSPEC(symbol, call, info, nargs, flags, selfHostedName) \
+  JS_FNSPEC(::JS::SymbolCode::symbol, call, info, nargs, flags, selfHostedName)
 #define JS_FNSPEC(name, call, info, nargs, flags, selfHostedName) \
-  { name, {call, info}, nargs, flags, selfHostedName }
+  { JSFunctionSpec::Name(name), {call, info}, nargs, flags, selfHostedName }
 
 #endif  // js_PropertySpec_h
--- a/js/public/Symbol.h
+++ b/js/public/Symbol.h
@@ -98,16 +98,15 @@ extern JS_PUBLIC_API SymbolCode GetSymbo
  */
 extern JS_PUBLIC_API Symbol* GetWellKnownSymbol(JSContext* cx,
                                                 SymbolCode which);
 
 /**
  * Return true if the given JSPropertySpec::name or JSFunctionSpec::name value
  * is actually a symbol code and not a string. See JS_SYM_FN.
  */
-inline bool PropertySpecNameIsSymbol(const char* name) {
-  uintptr_t u = reinterpret_cast<uintptr_t>(name);
-  return u != 0 && u - 1 < WellKnownSymbolLimit;
+inline bool PropertySpecNameIsSymbol(uintptr_t name) {
+  return name != 0 && name - 1 < WellKnownSymbolLimit;
 }
 
 }  // namespace JS
 
 #endif /* js_Symbol_h */
--- a/js/public/TypeDecls.h
+++ b/js/public/TypeDecls.h
@@ -57,16 +57,18 @@ class Handle;
 template <typename T>
 class MutableHandle;
 template <typename T>
 class Rooted;
 template <typename T>
 class PersistentRooted;
 template <typename T>
 class RootedVector;
+template <typename T>
+class PersistentRootedVector;
 template <typename T, typename AllocPolicy = js::TempAllocPolicy>
 class StackGCVector;
 
 typedef Handle<JSFunction*> HandleFunction;
 typedef Handle<PropertyKey> HandleId;
 typedef Handle<JSObject*> HandleObject;
 typedef Handle<JSScript*> HandleScript;
 typedef Handle<JSString*> HandleString;
@@ -106,16 +108,19 @@ typedef PersistentRooted<JSFunction*> Pe
 typedef PersistentRooted<PropertyKey> PersistentRootedId;
 typedef PersistentRooted<JSObject*> PersistentRootedObject;
 typedef PersistentRooted<JSScript*> PersistentRootedScript;
 typedef PersistentRooted<JSString*> PersistentRootedString;
 typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol;
 typedef PersistentRooted<JS::BigInt*> PersistentRootedBigInt;
 typedef PersistentRooted<Value> PersistentRootedValue;
 
+typedef PersistentRootedVector<PropertyKey> PersistentRootedIdVector;
+typedef PersistentRootedVector<JSObject*> PersistentRootedObjectVector;
+
 template <typename T>
 using HandleVector = Handle<StackGCVector<T>>;
 template <typename T>
 using MutableHandleVector = MutableHandle<StackGCVector<T>>;
 }  // namespace JS
 
 using jsid = JS::PropertyKey;
 
--- a/js/rust/build.rs
+++ b/js/rust/build.rs
@@ -147,17 +147,16 @@ const EXTRA_CLANG_FLAGS: &'static [&'sta
     "-fno-sized-deallocation",
     "-DRUST_BINDGEN",
 ];
 
 /// Types which we want to generate bindings for (and every other type they
 /// transitively use).
 const WHITELIST_TYPES: &'static [&'static str] = &[
     "JS::AutoCheckCannotGC",
-    "JS::RootedIdVector",
     "JS::CallArgs",
     "js::Class",
     "JS::RealmOptions",
     "JS::ContextOptions",
     "js::DOMCallbacks",
     "js::DOMProxyShadowsResult",
     "js::ESClass",
     "JS::ForOfIterator",
@@ -211,21 +210,22 @@ const WHITELIST_TYPES: &'static [&'stati
     "JS::Latin1Char",
     "JS::detail::MaybeWrapped",
     "JS::MutableHandle",
     "JS::MutableHandleObject",
     "JS::MutableHandleValue",
     "JS::NativeImpl",
     "js::ObjectOps",
     "JS::ObjectOpResult",
+    "JS::PersistentRootedIdVector",
+    "JS::PersistentRootedObjectVector",
     "JS::PromiseState",
     "JS::PropertyDescriptor",
     "JS::Rooted",
     "JS::RootedObject",
-    "JS::RootedObjectVector",
     "JS::RootedValue",
     "JS::RootingContext",
     "JS::RootKind",
     "js::Scalar::Type",
     "JS::ServoSizes",
     "js::shadow::Object",
     "js::shadow::ObjectGroup",
     "JS::SourceText",
@@ -471,20 +471,18 @@ const WHITELIST_FUNCTIONS: &'static [&'s
 ///
 /// These are types which are too tricky for bindgen to handle, and/or use C++
 /// features that don't have an equivalent in rust, such as partial template
 /// specialization.
 const OPAQUE_TYPES: &'static [&'static str] = &[
     "JS::ReadOnlyCompileOptions",
     "mozilla::BufferList",
     "mozilla::UniquePtr.*",
-    "JS::RootedVector",
-    "JS::Rooted<JS::Auto.*Vector.*>",
-    "JS::Auto.*Vector",
-    "JS::StackGCVector"
+    "JS::PersistentRooted",
+    "JS::StackGCVector",
 ];
 
 /// Types for which we should NEVER generate bindings, even if it is used within
 /// a type or function signature that we are generating bindings for.
 const BLACKLIST_TYPES: &'static [&'static str] = &[
     // We provide our own definition because we need to express trait bounds in
     // the definition of the struct to make our Drop implementation correct.
     "JS::Heap",
--- a/js/rust/src/glue.rs
+++ b/js/rust/src/glue.rs
@@ -252,24 +252,24 @@ extern "C" {
                                    -> *const JSErrorFormatString;
     pub fn IsProxyHandlerFamily(obj: *mut JSObject) -> u8;
     pub fn GetProxyHandlerExtra(obj: *mut JSObject) -> *const ::libc::c_void;
     pub fn GetProxyHandler(obj: *mut JSObject) -> *const ::libc::c_void;
     pub fn ReportError(aCx: *mut JSContext, aError: *const i8);
     pub fn IsWrapper(obj: *mut JSObject) -> bool;
     pub fn UnwrapObjectStatic(obj: *mut JSObject) -> *mut JSObject;
     pub fn UncheckedUnwrapObject(obj: *mut JSObject, stopAtOuter: u8) -> *mut JSObject;
-    pub fn CreateRootedIdVector(cx: *mut JSContext) -> *mut JS::RootedIdVector;
-    pub fn AppendToRootedIdVector(v: *mut JS::RootedIdVector, id: jsid) -> bool;
-    pub fn SliceRootedIdVector(v: *const JS::RootedIdVector, length: *mut usize) -> *const jsid;
-    pub fn DestroyRootedIdVector(v: *mut JS::RootedIdVector);
-    pub fn GetMutableHandleIdVector(v: *mut JS::RootedIdVector)-> JS::MutableHandleIdVector;
-    pub fn CreateRootedObjectVector(aCx: *mut JSContext) -> *mut JS::RootedObjectVector;
-    pub fn AppendToRootedObjectVector(v: *mut JS::RootedObjectVector, obj: *mut JSObject) -> bool;
-    pub fn DeleteRootedObjectVector(v: *mut JS::RootedObjectVector);
+    pub fn CreateRootedIdVector(cx: *mut JSContext) -> *mut JS::PersistentRootedIdVector;
+    pub fn AppendToRootedIdVector(v: *mut JS::PersistentRootedIdVector, id: jsid) -> bool;
+    pub fn SliceRootedIdVector(v: *const JS::PersistentRootedIdVector, length: *mut usize) -> *const jsid;
+    pub fn DestroyRootedIdVector(v: *mut JS::PersistentRootedIdVector);
+    pub fn GetMutableHandleIdVector(v: *mut JS::PersistentRootedIdVector)-> JS::MutableHandleIdVector;
+    pub fn CreateRootedObjectVector(aCx: *mut JSContext) -> *mut JS::PersistentRootedObjectVector;
+    pub fn AppendToRootedObjectVector(v: *mut JS::PersistentRootedObjectVector, obj: *mut JSObject) -> bool;
+    pub fn DeleteRootedObjectVector(v: *mut JS::PersistentRootedObjectVector);
     pub fn CollectServoSizes(rt: *mut JSRuntime, sizes: *mut JS::ServoSizes) -> bool;
     pub fn CallIdTracer(trc: *mut JSTracer, idp: *mut Heap<jsid>, name: *const ::libc::c_char);
     pub fn CallValueTracer(trc: *mut JSTracer,
                            valuep: *mut Heap<JS::Value>,
                            name: *const ::libc::c_char);
     pub fn CallObjectTracer(trc: *mut JSTracer,
                             objp: *mut Heap<*mut JSObject>,
                             name: *const ::libc::c_char);
--- a/js/rust/src/jsglue.cpp
+++ b/js/rust/src/jsglue.cpp
@@ -533,45 +533,51 @@ bool IsWrapper(JSObject* obj) { return j
 JSObject* UnwrapObjectStatic(JSObject* obj) {
   return js::CheckedUnwrapStatic(obj);
 }
 
 JSObject* UncheckedUnwrapObject(JSObject* obj, bool stopAtOuter) {
   return js::UncheckedUnwrap(obj, stopAtOuter);
 }
 
-JS::RootedIdVector* CreateRootedIdVector(JSContext* cx) {
-  return new JS::RootedIdVector(cx);
+JS::PersistentRootedIdVector* CreateRootedIdVector(JSContext* cx) {
+  return new JS::PersistentRootedIdVector(cx);
 }
 
-bool AppendToRootedIdVector(JS::RootedIdVector* v, jsid id) {
+bool AppendToRootedIdVector(JS::PersistentRootedIdVector* v, jsid id) {
   return v->append(id);
 }
 
-const jsid* SliceRootedIdVector(const JS::RootedIdVector* v, size_t* length) {
+const jsid* SliceRootedIdVector(const JS::PersistentRootedIdVector* v,
+                                size_t* length) {
   *length = v->length();
   return v->begin();
 }
 
-void DestroyRootedIdVector(JS::RootedIdVector* v) { delete v; }
+void DestroyRootedIdVector(JS::PersistentRootedIdVector* v) { delete v; }
 
-JS::MutableHandleIdVector GetMutableHandleIdVector(JS::RootedIdVector* v) {
+JS::MutableHandleIdVector GetMutableHandleIdVector(JS::PersistentRootedIdVector* v) {
   return JS::MutableHandleIdVector(v);
 }
 
-JS::RootedObjectVector* CreateRootedObjectVector(JSContext* aCx) {
-  JS::RootedObjectVector* vec = new JS::RootedObjectVector(aCx);
+JS::PersistentRootedObjectVector* CreateRootedObjectVector(
+    JSContext* aCx) {
+  JS::PersistentRootedObjectVector* vec =
+      new JS::PersistentRootedObjectVector(aCx);
   return vec;
 }
 
-bool AppendToRootedObjectVector(JS::RootedObjectVector* v, JSObject* obj) {
+bool AppendToRootedObjectVector(JS::PersistentRootedObjectVector* v,
+                                          JSObject* obj) {
   return v->append(obj);
 }
 
-void DeleteRootedObjectVector(JS::RootedObjectVector* v) { delete v; }
+void DeleteRootedObjectVector(JS::PersistentRootedObjectVector* v) {
+  delete v;
+}
 
 #if defined(__linux__)
 #  include <malloc.h>
 #elif defined(__APPLE__)
 #  include <malloc/malloc.h>
 #elif defined(__MINGW32__) || defined(__MINGW64__)
 // nothing needed here
 #elif defined(_MSC_VER)
--- a/js/rust/src/rust.rs
+++ b/js/rust/src/rust.rs
@@ -751,17 +751,17 @@ impl JSJitSetterCallArgs {
         self._base.handle()
     }
 }
 
 // ___________________________________________________________________________
 // Wrappers around things in jsglue.cpp
 
 pub struct RootedObjectVectorWrapper {
-    pub ptr: *mut JS::RootedObjectVector
+    pub ptr: *mut JS::PersistentRootedObjectVector
 }
 
 impl RootedObjectVectorWrapper {
     pub fn new(cx: *mut JSContext) -> RootedObjectVectorWrapper {
         RootedObjectVectorWrapper {
             ptr: unsafe {
                  CreateRootedObjectVector(cx)
             }
@@ -930,17 +930,17 @@ pub unsafe extern fn report_warning(_cx:
 impl JSNativeWrapper {
     fn is_zeroed(&self) -> bool {
         let JSNativeWrapper { op, info } = *self;
         op.is_none() && info.is_null()
     }
 }
 
 pub struct RootedIdVectorWrapper {
-    pub ptr: *mut JS::RootedIdVector
+    pub ptr: *mut JS::PersistentRootedIdVector
 }
 
 impl RootedIdVectorWrapper {
     pub fn new(cx: *mut JSContext) -> RootedIdVectorWrapper {
         RootedIdVectorWrapper {
             ptr: unsafe {
                 CreateRootedIdVector(cx)
             }
@@ -989,17 +989,17 @@ impl Deref for RootedIdVectorWrapper {
 ///
 /// - `cx` must be valid.
 /// - This function calls into unaudited C++ code.
 pub unsafe fn define_methods(cx: *mut JSContext, obj: JS::HandleObject,
                              methods: &'static [JSFunctionSpec])
                              -> Result<(), ()> {
     assert!({
         match methods.last() {
-            Some(&JSFunctionSpec { name, call, nargs, flags, selfHostedName }) => {
+            Some(&JSFunctionSpec { name: JSFunctionSpec_Name { string_: name, }, call, nargs, flags, selfHostedName }) => {
                 name.is_null() && call.is_zeroed() && nargs == 0 && flags == 0 &&
                 selfHostedName.is_null()
             },
             None => false,
         }
     });
 
     JS_DefineFunctions(cx, obj, methods.as_ptr()).to_result()
@@ -1137,62 +1137,70 @@ pub unsafe fn maybe_wrap_value(cx: *mut 
 
 /// Equivalents of the JS_FN* macros.
 impl JSFunctionSpec {
     pub fn js_fs(name: *const ::std::os::raw::c_char,
                  func: JSNative,
                  nargs: u16,
                  flags: u16) -> JSFunctionSpec {
         JSFunctionSpec {
-            name: name,
+            name: JSFunctionSpec_Name {
+                string_: name,
+            },
             call: JSNativeWrapper {
                 op: func,
                 info: ptr::null(),
             },
             nargs: nargs,
             flags: flags,
             selfHostedName: 0 as *const _,
         }
     }
 
     pub fn js_fn(name: *const ::std::os::raw::c_char,
                  func: JSNative,
                  nargs: u16,
                  flags: u16) -> JSFunctionSpec {
         JSFunctionSpec {
-            name: name,
+            name: JSFunctionSpec_Name {
+                string_: name,
+            },
             call: JSNativeWrapper {
                 op: func,
                 info: ptr::null(),
             },
             nargs: nargs,
             flags: flags,
             selfHostedName: 0 as *const _,
         }
     }
 
     pub const NULL: JSFunctionSpec = JSFunctionSpec {
-        name: 0 as *const _,
+        name: JSFunctionSpec_Name {
+            string_: 0 as *const _,
+        },
         call: JSNativeWrapper {
             op: None,
             info: 0 as *const _,
         },
         nargs: 0,
         flags: 0,
         selfHostedName: 0 as *const _,
     };
 }
 
 /// Equivalents of the JS_PS* macros.
 impl JSPropertySpec {
     pub fn getter(name: *const ::std::os::raw::c_char, flags: u8, func: JSNative)
                         -> JSPropertySpec {
         debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0);
         JSPropertySpec {
-            name: name,
+            name: JSPropertySpec_Name {
+                string_: name,
+            },
             flags: flags,
             u: JSPropertySpec_AccessorsOrValue {
                 accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                     getter: JSPropertySpec_Accessor {
                         native: JSNativeWrapper {
                             op: func,
                             info: ptr::null(),
                         },
@@ -1210,17 +1218,19 @@ impl JSPropertySpec {
 
     pub fn getter_setter(name: *const ::std::os::raw::c_char,
                          flags: u8,
                          g_f: JSNative,
                          s_f: JSNative)
                          -> JSPropertySpec {
         debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0);
         JSPropertySpec {
-            name: name,
+            name: JSPropertySpec_Name {
+                string_: name,
+            },
             flags: flags,
             u: JSPropertySpec_AccessorsOrValue {
                 accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                     getter: JSPropertySpec_Accessor {
                         native: JSNativeWrapper {
                             op: g_f,
                             info: ptr::null(),
                         },
@@ -1232,17 +1242,19 @@ impl JSPropertySpec {
                         },
                     }
                 }
             }
         }
     }
 
     pub const NULL: JSPropertySpec = JSPropertySpec {
-        name: 0 as *const _,
+        name: JSPropertySpec_Name {
+            string_: 0 as *const _,
+        },
         flags: 0,
         u: JSPropertySpec_AccessorsOrValue{
             accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                 getter: JSPropertySpec_Accessor {
                     native: JSNativeWrapper {
                         op: None,
                         info: 0 as *const _,
                     },
--- a/js/rust/tests/rooting.rs
+++ b/js/rust/tests/rooting.rs
@@ -40,38 +40,46 @@ fn rooting() {
 
 unsafe extern "C" fn generic_method(_: *mut JSContext, _: u32, _: *mut JS::Value) -> bool {
     true
 }
 
 lazy_static! {
     static ref METHODS: [JSFunctionSpec; 4] = [
         JSFunctionSpec {
-            name: b"addEventListener\0" as *const u8 as *const libc::c_char,
+            name: JSFunctionSpec_Name {
+                string_: b"addEventListener\0" as *const u8 as *const libc::c_char,
+            },
             call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
             nargs: 2,
             flags: JSPROP_ENUMERATE as u16,
             selfHostedName: 0 as *const libc::c_char
         },
         JSFunctionSpec {
-            name: b"removeEventListener\0" as *const u8 as *const libc::c_char,
+            name: JSFunctionSpec_Name {
+                string_: b"removeEventListener\0" as *const u8 as *const libc::c_char,
+            },
             call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
             nargs: 2,
             flags: JSPROP_ENUMERATE as u16,
             selfHostedName: 0 as *const libc::c_char
         },
         JSFunctionSpec {
-            name: b"dispatchEvent\0" as *const u8 as *const libc::c_char,
+            name: JSFunctionSpec_Name {
+                string_: b"dispatchEvent\0" as *const u8 as *const libc::c_char,
+            },
             call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
             nargs: 1,
             flags: JSPROP_ENUMERATE as u16,
             selfHostedName: 0 as *const libc::c_char
         },
         JSFunctionSpec {
-            name: ptr::null(),
+            name: JSFunctionSpec_Name {
+                string_: ptr::null(),
+            },
             call: JSNativeWrapper { op: None, info: ptr::null() },
             nargs: 0,
             flags: 0,
             selfHostedName: ptr::null()
         }
     ];
 }
 
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -333,18 +333,22 @@ static const ClassOps ReferenceTypeDescr
 
 const Class js::ReferenceTypeDescr::class_ = {
     "Reference",
     JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     &ReferenceTypeDescrClassOps};
 
 const JSFunctionSpec js::ReferenceTypeDescr::typeObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
-    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
-    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+    {JSFunctionSpec::Name("array"), {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    {JSFunctionSpec::Name("equivalent"),
+     {nullptr, nullptr},
+     1,
+     0,
+     "TypeDescrEquivalent"},
     JS_FS_END};
 
 static const uint32_t ReferenceSizes[] = {
 #define REFERENCE_SIZE(_kind, _type, _name) sizeof(_type),
     JS_FOR_EACH_REFERENCE_TYPE_REPR(REFERENCE_SIZE) 0
 #undef REFERENCE_SIZE
 };
 
@@ -472,28 +476,36 @@ static const ClassOps ArrayTypeDescrClas
 const Class ArrayTypeDescr::class_ = {
     "ArrayType",
     JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     &ArrayTypeDescrClassOps};
 
 const JSPropertySpec ArrayMetaTypeDescr::typeObjectProperties[] = {JS_PS_END};
 
 const JSFunctionSpec ArrayMetaTypeDescr::typeObjectMethods[] = {
-    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    {JSFunctionSpec::Name("array"), {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
-    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+    {JSFunctionSpec::Name("equivalent"),
+     {nullptr, nullptr},
+     1,
+     0,
+     "TypeDescrEquivalent"},
     JS_SELF_HOSTED_FN("build", "TypedObjectArrayTypeBuild", 3, 0),
     JS_SELF_HOSTED_FN("from", "TypedObjectArrayTypeFrom", 3, 0),
     JS_FS_END};
 
 const JSPropertySpec ArrayMetaTypeDescr::typedObjectProperties[] = {JS_PS_END};
 
 const JSFunctionSpec ArrayMetaTypeDescr::typedObjectMethods[] = {
-    {"forEach", {nullptr, nullptr}, 1, 0, "ArrayForEach"},
-    {"redimension", {nullptr, nullptr}, 1, 0, "TypedObjectArrayRedimension"},
+    {JSFunctionSpec::Name("forEach"), {nullptr, nullptr}, 1, 0, "ArrayForEach"},
+    {JSFunctionSpec::Name("redimension"),
+     {nullptr, nullptr},
+     1,
+     0,
+     "TypedObjectArrayRedimension"},
     JS_SELF_HOSTED_FN("map", "TypedObjectArrayMap", 2, 0),
     JS_SELF_HOSTED_FN("reduce", "TypedObjectArrayReduce", 2, 0),
     JS_SELF_HOSTED_FN("filter", "TypedObjectArrayFilter", 1, 0),
     JS_FS_END};
 
 bool js::CreateUserSizeAndAlignmentProperties(JSContext* cx,
                                               HandleTypeDescr descr) {
   // If data is transparent, also store the public slots.
@@ -713,19 +725,23 @@ static const ClassOps StructTypeDescrCla
 const Class StructTypeDescr::class_ = {
     "StructType",
     JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
     &StructTypeDescrClassOps};
 
 const JSPropertySpec StructMetaTypeDescr::typeObjectProperties[] = {JS_PS_END};
 
 const JSFunctionSpec StructMetaTypeDescr::typeObjectMethods[] = {
-    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    {JSFunctionSpec::Name("array"), {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
-    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeDescrEquivalent"},
+    {JSFunctionSpec::Name("equivalent"),
+     {nullptr, nullptr},
+     1,
+     0,
+     "TypeDescrEquivalent"},
     JS_FS_END};
 
 const JSPropertySpec StructMetaTypeDescr::typedObjectProperties[] = {JS_PS_END};
 
 const JSFunctionSpec StructMetaTypeDescr::typedObjectMethods[] = {JS_FS_END};
 
 CheckedInt32 StructMetaTypeDescr::Layout::addField(int32_t fieldAlignment,
                                                    int32_t fieldSize) {
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -1916,17 +1916,17 @@ static bool DefineABIConstant(JSContext*
 // ctypes.{Pointer,Array,Struct,Function}Type.
 static bool InitTypeConstructor(
     JSContext* cx, HandleObject parent, HandleObject CTypeProto,
     HandleObject CDataProto, const JSFunctionSpec spec,
     const JSFunctionSpec* fns, const JSPropertySpec* props,
     const JSFunctionSpec* instanceFns, const JSPropertySpec* instanceProps,
     MutableHandleObject typeProto, MutableHandleObject dataProto) {
   JSFunction* fun = js::DefineFunctionWithReserved(
-      cx, parent, spec.name, spec.call.op, spec.nargs, spec.flags);
+      cx, parent, spec.name.string(), spec.call.op, spec.nargs, spec.flags);
   if (!fun) {
     return false;
   }
 
   RootedObject obj(cx, JS_GetFunctionObject(fun));
   if (!obj) {
     return false;
   }
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinASTTokenReaderContext.cpp
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#include "frontend/BinASTTokenReaderContext.h"
+
+#include "mozilla/Result.h"  // MOZ_TRY*
+
+#include <string.h>  // memcmp
+
+#include "frontend/BinAST-macros.h"  // BINJS_TRY*, BINJS_MOZ_TRY*
+#include "vm/JSScript.h"             // ScriptSource
+
+namespace js {
+namespace frontend {
+
+// The magic header, at the start of every binjs file.
+const char CX_MAGIC_HEADER[] =
+    "\x89"
+    "BJS\r\n\0\n";
+
+// The latest format version understood by this tokenizer.
+const uint32_t MAGIC_FORMAT_VERSION = 2;
+
+using AutoList = BinASTTokenReaderContext::AutoList;
+using AutoTaggedTuple = BinASTTokenReaderContext::AutoTaggedTuple;
+using CharSlice = BinaryASTSupport::CharSlice;
+using Chars = BinASTTokenReaderContext::Chars;
+
+BinASTTokenReaderContext::BinASTTokenReaderContext(JSContext* cx,
+                                                   ErrorReporter* er,
+                                                   const uint8_t* start,
+                                                   const size_t length)
+    : BinASTTokenReaderBase(cx, er, start, length),
+      metadata_(nullptr),
+      posBeforeTree_(nullptr) {
+  MOZ_ASSERT(er);
+}
+
+BinASTTokenReaderContext::~BinASTTokenReaderContext() {
+  if (metadata_ && metadataOwned_ == MetadataOwnership::Owned) {
+    UniqueBinASTSourceMetadataPtr ptr(metadata_);
+  }
+}
+
+BinASTSourceMetadata* BinASTTokenReaderContext::takeMetadata() {
+  MOZ_ASSERT(metadataOwned_ == MetadataOwnership::Owned);
+  metadataOwned_ = MetadataOwnership::Unowned;
+  return metadata_;
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::initFromScriptSource(
+    ScriptSource* scriptSource) {
+  metadata_ = scriptSource->binASTSourceMetadata();
+  metadataOwned_ = MetadataOwnership::Unowned;
+
+  return Ok();
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::readHeader() {
+  // Check that we don't call this function twice.
+  MOZ_ASSERT(!posBeforeTree_);
+
+  // Read global headers.
+  MOZ_TRY(readConst(CX_MAGIC_HEADER));
+  BINJS_MOZ_TRY_DECL(version, readVarU32());
+
+  if (version != MAGIC_FORMAT_VERSION) {
+    return raiseError("Format version not implemented");
+  }
+
+  // TODO: handle `LinkToSharedDictionary` and remaining things here.
+
+  return raiseError("Not Yet Implemented");
+}
+
+void BinASTTokenReaderContext::traceMetadata(JSTracer* trc) {
+  if (metadata_) {
+    metadata_->trace(trc);
+  }
+}
+
+JS::Result<bool> BinASTTokenReaderContext::readBool() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<double> BinASTTokenReaderContext::readDouble() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<JSAtom*> BinASTTokenReaderContext::readMaybeAtom() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<JSAtom*> BinASTTokenReaderContext::readAtom() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<JSAtom*> BinASTTokenReaderContext::readMaybeIdentifierName() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<JSAtom*> BinASTTokenReaderContext::readIdentifierName() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<JSAtom*> BinASTTokenReaderContext::readPropertyKey() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::readChars(Chars& out) {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<BinASTVariant> BinASTTokenReaderContext::readVariant() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<BinASTTokenReaderBase::SkippableSubTree>
+BinASTTokenReaderContext::readSkippableSubTree() {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::enterTaggedTuple(
+    BinASTKind& tag, BinASTTokenReaderContext::BinASTFields&,
+    AutoTaggedTuple& guard) {
+  return raiseError("Not Yet Implemented");
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::enterList(uint32_t& items,
+                                                   AutoList& guard) {
+  return raiseError("Not Yet Implemented");
+}
+
+void BinASTTokenReaderContext::AutoBase::init() { initialized_ = true; }
+
+BinASTTokenReaderContext::AutoBase::AutoBase(BinASTTokenReaderContext& reader)
+    : initialized_(false), reader_(reader) {}
+
+BinASTTokenReaderContext::AutoBase::~AutoBase() {
+  // By now, the `AutoBase` must have been deinitialized by calling `done()`.
+  // The only case in which we can accept not calling `done()` is if we have
+  // bailed out because of an error.
+  MOZ_ASSERT_IF(initialized_, reader_.hasRaisedError());
+}
+
+JS::Result<Ok> BinASTTokenReaderContext::AutoBase::checkPosition(
+    const uint8_t* expectedEnd) {
+  return reader_.raiseError("Not Yet Implemented");
+}
+
+BinASTTokenReaderContext::AutoList::AutoList(BinASTTokenReaderContext& reader)
+    : AutoBase(reader) {}
+
+void BinASTTokenReaderContext::AutoList::init() { AutoBase::init(); }
+
+JS::Result<Ok> BinASTTokenReaderContext::AutoList::done() {
+  return reader_.raiseError("Not Yet Implemented");
+}
+
+// Internal uint32_t
+//
+// Encoded as variable length number.
+
+MOZ_MUST_USE JS::Result<uint32_t> BinASTTokenReaderContext::readVarU32() {
+  uint32_t result = 0;
+  uint32_t shift = 0;
+  while (true) {
+    MOZ_ASSERT(shift < 32);
+    uint32_t byte;
+    MOZ_TRY_VAR(byte, readByte());
+
+    const uint32_t newResult = result | (byte >> 1) << shift;
+    if (newResult < result) {
+      return raiseError("Overflow during readVarU32");
+    }
+
+    result = newResult;
+    shift += 7;
+
+    if ((byte & 1) == 0) {
+      return result;
+    }
+
+    if (shift >= 32) {
+      return raiseError("Overflow during readVarU32");
+    }
+  }
+}
+
+BinASTTokenReaderContext::AutoTaggedTuple::AutoTaggedTuple(
+    BinASTTokenReaderContext& reader)
+    : AutoBase(reader) {}
+
+JS::Result<Ok> BinASTTokenReaderContext::AutoTaggedTuple::done() {
+  return reader_.raiseError("Not Yet Implemented");
+}
+
+}  // namespace frontend
+
+}  // namespace js
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinASTTokenReaderContext.h
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#ifndef frontend_BinASTTokenReaderContext_h
+#define frontend_BinASTTokenReaderContext_h
+
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
+#include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS
+
+#include "mozilla/Maybe.h"  // mozilla::Maybe
+
+#include <stddef.h>  // size_t
+#include <stdint.h>  // uint8_t, uint32_t
+
+#include "frontend/BinASTRuntimeSupport.h"  // CharSlice, BinASTVariant, BinASTKind, BinASTField, BinASTSourceMetadata
+#include "frontend/BinASTToken.h"
+#include "frontend/BinASTTokenReaderBase.h"  // BinASTTokenReaderBase, SkippableSubTree
+#include "js/AllocPolicy.h"                  // SystemAllocPolicy
+#include "js/HashTable.h"                    // HashMap, DefaultHasher
+#include "js/Result.h"                       // JS::Result, Ok, Error
+#include "js/Vector.h"                       // js::Vector
+
+class JSAtom;
+class JSTracer;
+struct JSContext;
+
+namespace js {
+
+class ScriptSource;
+
+namespace frontend {
+
+class ErrorReporter;
+
+/**
+ * A token reader implementing the "context" serialization format for BinAST.
+ *
+ * This serialization format, which is also supported by the reference
+ * implementation of the BinAST compression suite, is designed to be
+ * space- and time-efficient.
+ *
+ * As other token readers for the BinAST:
+ *
+ * - the reader does not support error recovery;
+ * - the reader does not support lookahead or pushback.
+ */
+class MOZ_STACK_CLASS BinASTTokenReaderContext : public BinASTTokenReaderBase {
+ public:
+  class AutoList;
+  class AutoTaggedTuple;
+
+  using CharSlice = BinaryASTSupport::CharSlice;
+
+  // This implementation of `BinASTFields` is effectively `void`, as the format
+  // does not embed field information.
+  class BinASTFields {
+   public:
+    explicit BinASTFields(JSContext*) {}
+  };
+  using Chars = CharSlice;
+
+ public:
+  /**
+   * Construct a token reader.
+   *
+   * Does NOT copy the buffer.
+   */
+  BinASTTokenReaderContext(JSContext* cx, ErrorReporter* er,
+                           const uint8_t* start, const size_t length);
+
+  /**
+   * Construct a token reader.
+   *
+   * Does NOT copy the buffer.
+   */
+  BinASTTokenReaderContext(JSContext* cx, ErrorReporter* er,
+                           const Vector<uint8_t>& chars);
+
+  ~BinASTTokenReaderContext();
+
+  /**
+   * Read the header of the file.
+   */
+  MOZ_MUST_USE JS::Result<Ok> readHeader();
+
+  // --- Primitive values.
+  //
+  // Note that the underlying format allows for a `null` value for primitive
+  // values.
+  //
+  // Reading will return an error either in case of I/O error or in case of
+  // a format problem. Reading if an exception in pending is an error and
+  // will cause assertion failures. Do NOT attempt to read once an exception
+  // has been cleared: the token reader does NOT support recovery, by design.
+
+  /**
+   * Read a single `true | false` value.
+   */
+  MOZ_MUST_USE JS::Result<bool> readBool();
+
+  /**
+   * Read a single `number` value.
+   */
+  MOZ_MUST_USE JS::Result<double> readDouble();
+
+  /**
+   * Read a single `string | null` value.
+   *
+   * Fails if that string is not valid UTF-8.
+   */
+  MOZ_MUST_USE JS::Result<JSAtom*> readMaybeAtom();
+  MOZ_MUST_USE JS::Result<JSAtom*> readAtom();
+
+  /**
+   * Read a single IdentifierName value.
+   */
+  MOZ_MUST_USE JS::Result<JSAtom*> readMaybeIdentifierName();
+  MOZ_MUST_USE JS::Result<JSAtom*> readIdentifierName();
+
+  /**
+   * Read a single PropertyKey value.
+   */
+  MOZ_MUST_USE JS::Result<JSAtom*> readPropertyKey();
+
+  /**
+   * Read a single `string | null` value.
+   *
+   * MAY check if that string is not valid UTF-8.
+   */
+  MOZ_MUST_USE JS::Result<Ok> readChars(Chars&);
+
+  /**
+   * Read a single `BinASTVariant | null` value.
+   */
+  MOZ_MUST_USE JS::Result<mozilla::Maybe<BinASTVariant>> readMaybeVariant();
+  MOZ_MUST_USE JS::Result<BinASTVariant> readVariant();
+
+  /**
+   * Read over a single `[Skippable]` subtree value.
+   *
+   * This does *not* attempt to parse the subtree itself. Rather, the
+   * returned `SkippableSubTree` contains the necessary information
+   * to parse/tokenize the subtree at a later stage
+   */
+  MOZ_MUST_USE JS::Result<SkippableSubTree> readSkippableSubTree();
+
+  // --- Composite values.
+  //
+  // The underlying format does NOT allows for a `null` composite value.
+  //
+  // Reading will return an error either in case of I/O error or in case of
+  // a format problem. Reading from a poisoned tokenizer is an error and
+  // will cause assertion failures.
+
+  /**
+   * Start reading a list.
+   *
+   * @param length (OUT) The number of elements in the list.
+   * @param guard (OUT) A guard, ensuring that we read the list correctly.
+   *
+   * The `guard` is dedicated to ensuring that reading the list has consumed
+   * exactly all the bytes from that list. The `guard` MUST therefore be
+   * destroyed at the point where the caller has reached the end of the list.
+   * If the caller has consumed too few/too many bytes, this will be reported
+   * in the call go `guard.done()`.
+   */
+  MOZ_MUST_USE JS::Result<Ok> enterList(uint32_t& length, AutoList& guard);
+
+  /**
+   * Start reading a tagged tuple.
+   *
+   * @param tag (OUT) The tag of the tuple.
+   * @param fields Ignored, provided for API compatibility.
+   * @param guard (OUT) A guard, ensuring that we read the tagged tuple
+   * correctly.
+   *
+   * The `guard` is dedicated to ensuring that reading the list has consumed
+   * exactly all the bytes from that tuple. The `guard` MUST therefore be
+   * destroyed at the point where the caller has reached the end of the tuple.
+   * If the caller has consumed too few/too many bytes, this will be reported
+   * in the call go `guard.done()`.
+   *
+   * @return out If the header of the tuple is invalid.
+   */
+  MOZ_MUST_USE JS::Result<Ok> enterTaggedTuple(
+      BinASTKind& tag, BinASTTokenReaderContext::BinASTFields& fields,
+      AutoTaggedTuple& guard);
+
+  /**
+   * Read a single unsigned long.
+   */
+  MOZ_MUST_USE JS::Result<uint32_t> readUnsignedLong() { return readVarU32(); }
+
+ private:
+  /**
+   * Read a single uint32_t.
+   */
+  MOZ_MUST_USE JS::Result<uint32_t> readVarU32();
+
+ private:
+  // A mapping string index => BinASTVariant as extracted from the [STRINGS]
+  // section of the file. Populated lazily.
+  js::HashMap<uint32_t, BinASTVariant, DefaultHasher<uint32_t>,
+              SystemAllocPolicy>
+      variantsTable_;
+
+  enum class MetadataOwnership { Owned, Unowned };
+  MetadataOwnership metadataOwned_ = MetadataOwnership::Owned;
+  BinASTSourceMetadata* metadata_;
+
+  const uint8_t* posBeforeTree_;
+
+ public:
+  BinASTTokenReaderContext(const BinASTTokenReaderContext&) = delete;
+  BinASTTokenReaderContext(BinASTTokenReaderContext&&) = delete;
+  BinASTTokenReaderContext& operator=(BinASTTokenReaderContext&) = delete;
+
+ public:
+  void traceMetadata(JSTracer* trc);
+  BinASTSourceMetadata* takeMetadata();
+  MOZ_MUST_USE JS::Result<Ok> initFromScriptSource(ScriptSource* scriptSource);
+
+ public:
+  // The following classes are used whenever we encounter a tuple/tagged
+  // tuple/list to make sure that:
+  //
+  // - if the construct "knows" its byte length, we have exactly consumed all
+  //   the bytes (otherwise, this means that the file is corrupted, perhaps on
+  //   purpose, so we need to reject the stream);
+  // - if the construct has a footer, once we are done reading it, we have
+  //   reached the footer (this is to aid with debugging).
+  //
+  // In either case, the caller MUST call method `done()` of the guard once
+  // it is done reading the tuple/tagged tuple/list, to report any pending
+  // error.
+
+  // Base class used by other Auto* classes.
+  class MOZ_STACK_CLASS AutoBase {
+   protected:
+    explicit AutoBase(BinASTTokenReaderContext& reader);
+    ~AutoBase();
+
+    // Raise an error if we are not in the expected position.
+    MOZ_MUST_USE JS::Result<Ok> checkPosition(const uint8_t* expectedPosition);
+
+    friend BinASTTokenReaderContext;
+    void init();
+
+    // Set to `true` if `init()` has been called. Reset to `false` once
+    // all conditions have been checked.
+    bool initialized_;
+    BinASTTokenReaderContext& reader_;
+  };
+
+  // Guard class used to ensure that `enterList` is used properly.
+  class MOZ_STACK_CLASS AutoList : public AutoBase {
+   public:
+    explicit AutoList(BinASTTokenReaderContext& reader);
+
+    // Check that we have properly read to the end of the list.
+    MOZ_MUST_USE JS::Result<Ok> done();
+
+   protected:
+    friend BinASTTokenReaderContext;
+    void init();
+  };
+
+  // Guard class used to ensure that `enterTaggedTuple` is used properly.
+  class MOZ_STACK_CLASS AutoTaggedTuple : public AutoBase {
+   public:
+    explicit AutoTaggedTuple(BinASTTokenReaderContext& reader);
+
+    // Check that we have properly read to the end of the tuple.
+    MOZ_MUST_USE JS::Result<Ok> done();
+  };
+
+  // Compare a `Chars` and a string literal (ONLY a string literal).
+  template <size_t N>
+  static bool equals(const Chars& left, const char (&right)[N]) {
+    MOZ_ASSERT(N > 0);
+    MOZ_ASSERT(right[N - 1] == 0);
+    if (left.byteLen_ + 1 /* implicit NUL */ != N) {
+      return false;
+    }
+
+    if (!std::equal(left.start_, left.start_ + left.byteLen_, right)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  template <size_t N>
+  static JS::Result<Ok, JS::Error&> checkFields(
+      const BinASTKind kind, const BinASTFields& actual,
+      const BinASTField (&expected)[N]) {
+    // Not implemented in this tokenizer.
+    return Ok();
+  }
+
+  static JS::Result<Ok, JS::Error&> checkFields0(const BinASTKind kind,
+                                                 const BinASTFields& actual) {
+    // Not implemented in this tokenizer.
+    return Ok();
+  }
+};
+
+}  // namespace frontend
+}  // namespace js
+
+#endif  // frontend_BinASTTokenReaderContext_h
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -71,16 +71,17 @@ if CONFIG['JS_BUILD_BINAST']:
     # These parts of BinAST should eventually move to release.
     SOURCES += [
         'BinASTParser.cpp',
         'BinASTParserBase.cpp',
         'BinASTParserPerTokenizer.cpp',
         'BinASTRuntimeSupport.cpp',
         'BinASTToken.cpp',
         'BinASTTokenReaderBase.cpp',
+        'BinASTTokenReaderContext.cpp',
         'BinASTTokenReaderMultipart.cpp',
     ]
 
     DIRS += [
         'binast'
     ]
 
     # Instrument BinAST files for fuzzing as we have a fuzzing target for BinAST.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/realms/bug1548611.js
@@ -0,0 +1,5 @@
+const otherGlobal = newGlobal();
+for (var i=0; i<60; i++) {
+    new otherGlobal.Array();
+    bailout();
+}
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -5123,16 +5123,18 @@ AttachDecision CallIRGenerator::tryAttac
     trackAttached("Call any scripted func");
   }
 
   return AttachDecision::Attach;
 }
 
 bool CallIRGenerator::getTemplateObjectForNative(HandleFunction calleeFunc,
                                                  MutableHandleObject res) {
+  AutoRealm ar(cx_, calleeFunc);
+
   // Saving the template object is unsound for super(), as a single
   // callsite can have multiple possible prototype objects created
   // (via different newTargets)
   bool isSuper = op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL;
   if (isSuper) {
     return true;
   }
 
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1223,30 +1223,32 @@ bool RNewTypedArray::recover(JSContext* 
   iter.storeInstructionResult(result);
   return true;
 }
 
 bool MNewArray::writeRecoverData(CompactBufferWriter& writer) const {
   MOZ_ASSERT(canRecoverOnBailout());
   writer.writeUnsigned(uint32_t(RInstruction::Recover_NewArray));
   writer.writeUnsigned(length());
+  writer.writeByte(uint8_t(convertDoubleElements()));
   return true;
 }
 
 RNewArray::RNewArray(CompactBufferReader& reader) {
   count_ = reader.readUnsigned();
+  convertDoubleElements_ = reader.readByte();
 }
 
 bool RNewArray::recover(JSContext* cx, SnapshotIterator& iter) const {
   RootedObject templateObject(cx, &iter.read().toObject());
   RootedValue result(cx);
   RootedObjectGroup group(cx, templateObject->group());
 
   ArrayObject* resultObject =
-      NewFullyAllocatedArrayTryUseGroup(cx, group, count_);
+      NewArrayWithGroup(cx, count_, group, convertDoubleElements_);
   if (!resultObject) {
     return false;
   }
 
   result.setObject(*resultObject);
   iter.storeInstructionResult(result);
   return true;
 }
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -603,16 +603,17 @@ class RNewTypedArray final : public RIns
 
   MOZ_MUST_USE bool recover(JSContext* cx,
                             SnapshotIterator& iter) const override;
 };
 
 class RNewArray final : public RInstruction {
  private:
   uint32_t count_;
+  bool convertDoubleElements_;
 
  public:
   RINSTRUCTION_HEADER_NUM_OP_(NewArray, 1)
 
   MOZ_MUST_USE bool recover(JSContext* cx,
                             SnapshotIterator& iter) const override;
 };
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2917,39 +2917,33 @@ JS_PUBLIC_API bool JSPropertySpec::getVa
   } else {
     MOZ_ASSERT(u.value.type == JSVAL_TYPE_INT32);
     vp.setInt32(u.value.int32);
   }
 
   return true;
 }
 
-static JS::SymbolCode PropertySpecNameToSymbolCode(const char* name) {
-  MOZ_ASSERT(JS::PropertySpecNameIsSymbol(name));
-  uintptr_t u = reinterpret_cast<uintptr_t>(name);
-  return JS::SymbolCode(u - 1);
-}
-
-bool PropertySpecNameToId(JSContext* cx, const char* name, MutableHandleId id,
+bool PropertySpecNameToId(JSContext* cx, JSPropertySpec::Name name,
+                          MutableHandleId id,
                           js::PinningBehavior pin = js::DoNotPinAtom) {
-  if (JS::PropertySpecNameIsSymbol(name)) {
-    JS::SymbolCode which = PropertySpecNameToSymbolCode(name);
-    id.set(SYMBOL_TO_JSID(cx->wellKnownSymbols().get(which)));
+  if (name.isSymbol()) {
+    id.set(SYMBOL_TO_JSID(cx->wellKnownSymbols().get(name.symbol())));
   } else {
-    JSAtom* atom = Atomize(cx, name, strlen(name), pin);
+    JSAtom* atom = Atomize(cx, name.string(), strlen(name.string()), pin);
     if (!atom) {
       return false;
     }
     id.set(AtomToId(atom));
   }
   return true;
 }
 
 JS_PUBLIC_API bool JS::PropertySpecNameToPermanentId(JSContext* cx,
-                                                     const char* name,
+                                                     JSPropertySpec::Name name,
                                                      jsid* idp) {
   // We are calling fromMarkedLocation(idp) even though idp points to a
   // location that will never be marked. This is OK because the whole point
   // of this API is to populate *idp with a jsid that does not need to be
   // marked.
   return PropertySpecNameToId(
       cx, name, MutableHandleId::fromMarkedLocation(idp), js::PinAtom);
 }
@@ -4662,44 +4656,46 @@ JS_PUBLIC_API JS::SymbolCode JS::GetSymb
 }
 
 JS_PUBLIC_API JS::Symbol* JS::GetWellKnownSymbol(JSContext* cx,
                                                  JS::SymbolCode which) {
   return cx->wellKnownSymbols().get(which);
 }
 
 #ifdef DEBUG
-static bool PropertySpecNameIsDigits(const char* s) {
-  if (JS::PropertySpecNameIsSymbol(s)) {
+static bool PropertySpecNameIsDigits(JSPropertySpec::Name name) {
+  if (name.isSymbol()) {
     return false;
   }
+  const char* s = name.string();
   if (!*s) {
     return false;
   }
   for (; *s; s++) {
     if (*s < '0' || *s > '9') {
       return false;
     }
   }
   return true;
 }
 #endif  // DEBUG
 
-JS_PUBLIC_API bool JS::PropertySpecNameEqualsId(const char* name, HandleId id) {
-  if (JS::PropertySpecNameIsSymbol(name)) {
+JS_PUBLIC_API bool JS::PropertySpecNameEqualsId(JSPropertySpec::Name name,
+                                                HandleId id) {
+  if (name.isSymbol()) {
     if (!JSID_IS_SYMBOL(id)) {
       return false;
     }
     Symbol* sym = JSID_TO_SYMBOL(id);
-    return sym->isWellKnownSymbol() &&
-           sym->code() == PropertySpecNameToSymbolCode(name);
+    return sym->isWellKnownSymbol() && sym->code() == name.symbol();
   }
 
   MOZ_ASSERT(!PropertySpecNameIsDigits(name));
-  return JSID_IS_ATOM(id) && JS_FlatStringEqualsAscii(JSID_TO_ATOM(id), name);
+  return JSID_IS_ATOM(id) &&
+         JS_FlatStringEqualsAscii(JSID_TO_ATOM(id), name.string());
 }
 
 JS_PUBLIC_API bool JS_Stringify(JSContext* cx, MutableHandleValue vp,
                                 HandleObject replacer, HandleValue space,
                                 JSONWriteCallback callback, void* data) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   cx->check(replacer, space);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -32,16 +32,17 @@
 #include "js/CompileOptions.h"
 #include "js/ErrorReport.h"
 #include "js/GCVector.h"
 #include "js/HashTable.h"
 #include "js/Id.h"
 #include "js/OffThreadScriptCompilation.h"
 #include "js/Principals.h"
 #include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
 #include "js/Realm.h"
 #include "js/RealmOptions.h"
 #include "js/RefCounted.h"
 #include "js/RootingAPI.h"
 #include "js/TracingAPI.h"
 #include "js/Transcoding.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
@@ -2542,28 +2543,30 @@ JS_PUBLIC_API size_t JS_GetStringEncodin
  */
 MOZ_MUST_USE JS_PUBLIC_API bool JS_EncodeStringToBuffer(JSContext* cx,
                                                         JSString* str,
                                                         char* buffer,
                                                         size_t length);
 
 namespace JS {
 
-JS_PUBLIC_API bool PropertySpecNameEqualsId(const char* name, HandleId id);
+JS_PUBLIC_API bool PropertySpecNameEqualsId(JSPropertySpec::Name name,
+                                            HandleId id);
 
 /**
  * Create a jsid that does not need to be marked for GC.
  *
  * 'name' is a JSPropertySpec::name or JSFunctionSpec::name value. The
  * resulting jsid, on success, is either an interned string or a well-known
  * symbol; either way it is immune to GC so there is no need to visit *idp
  * during GC marking.
  */
 JS_PUBLIC_API bool PropertySpecNameToPermanentId(JSContext* cx,
-                                                 const char* name, jsid* idp);
+                                                 JSPropertySpec::Name name,
+                                                 jsid* idp);
 
 } /* namespace JS */
 
 /************************************************************************/
 
 /*
  * Error reporting.
  *
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -75,39 +75,39 @@ using mozilla::MakeScopeExit;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 
 /*** Forward declarations, ClassOps and Classes *****************************/
 
-static void DebuggerFrame_finalize(FreeOp* fop, JSObject* obj);
-static void DebuggerFrame_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
 
 inline js::Debugger* js::DebuggerFrame::owner() const {
   JSObject* dbgobj = &getReservedSlot(OWNER_SLOT).toObject();
   return Debugger::fromJSObject(dbgobj);
 }
 
-const ClassOps DebuggerFrame::classOps_ = {nullptr, /* addProperty */
-                                           nullptr, /* delProperty */
-                                           nullptr, /* enumerate   */
-                                           nullptr, /* newEnumerate */
-                                           nullptr, /* resolve     */
-                                           nullptr, /* mayResolve  */
-                                           DebuggerFrame_finalize,
-                                           nullptr, /* call        */
-                                           nullptr, /* hasInstance */
-                                           nullptr, /* construct   */
-                                           DebuggerFrame_trace};
+const ClassOps DebuggerFrame::classOps_ = {
+    nullptr,  /* addProperty */
+    nullptr,  /* delProperty */
+    nullptr,  /* enumerate   */
+    nullptr,  /* newEnumerate */
+    nullptr,  /* resolve     */
+    nullptr,  /* mayResolve  */
+    finalize, /* finalize */
+    nullptr,  /* call        */
+    nullptr,  /* hasInstance */
+    nullptr,  /* construct   */
+    trace,    /* trace */
+};
 
 const Class DebuggerFrame::class_ = {
     "Frame",
     JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
         JSCLASS_BACKGROUND_FINALIZE,
     &DebuggerFrame::classOps_};
 
 enum { JSSLOT_DEBUGARGUMENTS_FRAME, JSSLOT_DEBUGARGUMENTS_COUNT };
@@ -3361,17 +3361,17 @@ void Debugger::removeAllocationsTracking
   }
 
   allocationsLog.clear();
 }
 
 /*** Debugger JSObjects *****************************************************/
 
 void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
-  generatorFrames.traceCrossCompartmentEdges<DebuggerFrame_trace>(trc);
+  generatorFrames.traceCrossCompartmentEdges<DebuggerFrame::trace>(trc);
   objects.traceCrossCompartmentEdges<DebuggerObject_trace>(trc);
   environments.traceCrossCompartmentEdges<DebuggerEnv_trace>(trc);
   scripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
   lazyScripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
   sources.traceCrossCompartmentEdges<DebuggerSource_trace>(trc);
   wasmInstanceScripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
   wasmInstanceSources.traceCrossCompartmentEdges<DebuggerSource_trace>(trc);
 }
@@ -9597,31 +9597,33 @@ static void DebuggerFrame_maybeDecrement
     wasm::Instance* instance = frame.wasmInstance();
     instance->debug().decrementStepModeCount(
         fop, frame.asWasmDebugFrame()->funcIndex());
   } else {
     frame.script()->decrementStepModeCount(fop);
   }
 }
 
-static void DebuggerFrame_finalize(FreeOp* fop, JSObject* obj) {
+/* static */
+void DebuggerFrame::finalize(FreeOp* fop, JSObject* obj) {
   MOZ_ASSERT(fop->maybeOnHelperThread());
   DebuggerFrame& frameobj = obj->as<DebuggerFrame>();
   frameobj.freeFrameIterData(fop);
   OnStepHandler* onStepHandler = frameobj.onStepHandler();
   if (onStepHandler) {
     onStepHandler->drop();
   }
   OnPopHandler* onPopHandler = frameobj.onPopHandler();
   if (onPopHandler) {
     onPopHandler->drop();
   }
 }
 
-static void DebuggerFrame_trace(JSTracer* trc, JSObject* obj) {
+/* static */
+void DebuggerFrame::trace(JSTracer* trc, JSObject* obj) {
   OnStepHandler* onStepHandler = obj->as<DebuggerFrame>().onStepHandler();
   if (onStepHandler) {
     onStepHandler->trace(trc);
   }
   OnPopHandler* onPopHandler = obj->as<DebuggerFrame>().onPopHandler();
   if (onPopHandler) {
     onPopHandler->trace(trc);
   }
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -1443,16 +1443,18 @@ class DebuggerFrame : public NativeObjec
   enum {
     OWNER_SLOT = 0,
     ARGUMENTS_SLOT,
     ONSTEP_HANDLER_SLOT,
     ONPOP_HANDLER_SLOT,
     RESERVED_SLOTS,
   };
 
+  static void trace(JSTracer* trc, JSObject* obj);
+
   static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor,
                                  Handle<GlobalObject*> global);
   static DebuggerFrame* create(JSContext* cx, HandleObject proto,
                                const FrameIter& iter,
                                HandleNativeObject debugger);
   void freeFrameIterData(FreeOp* fop);
 
   static MOZ_MUST_USE bool getArguments(JSContext* cx,
@@ -1502,16 +1504,18 @@ class DebuggerFrame : public NativeObjec
   bool hasAnyLiveHooks() const;
 
  private:
   static const ClassOps classOps_;
 
   static const JSPropertySpec properties_[];
   static const JSFunctionSpec methods_[];
 
+  static void finalize(FreeOp* fop, JSObject* obj);
+
   static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
   static MOZ_MUST_USE bool getFrameIter(JSContext* cx,
                                         HandleDebuggerFrame frame,
                                         mozilla::Maybe<FrameIter>& result);
   static MOZ_MUST_USE bool requireScriptReferent(JSContext* cx,
                                                  HandleDebuggerFrame frame);
 
   static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -33,17 +33,17 @@
 #include "builtin/Symbol.h"
 #include "builtin/WeakSetObject.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/Policy.h"
 #include "jit/BaselineJIT.h"
 #include "js/CharacterEncoding.h"
 #include "js/MemoryMetrics.h"
 #include "js/PropertyDescriptor.h"  // JS::FromPropertyDescriptor
-#include "js/PropertySpec.h"
+#include "js/PropertySpec.h"        // JSPropertySpec
 #include "js/Proxy.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "js/Wrapper.h"
 #include "util/Text.h"
 #include "util/Windows.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/BytecodeUtil.h"
@@ -3009,17 +3009,17 @@ bool js::GetPropertyDescriptor(JSContext
   }
 
   MOZ_ASSERT(!desc.object());
   return true;
 }
 
 /* * */
 
-extern bool PropertySpecNameToId(JSContext* cx, const char* name,
+extern bool PropertySpecNameToId(JSContext* cx, JSPropertySpec::Name name,
                                  MutableHandleId id,
                                  js::PinningBehavior pin = js::DoNotPinAtom);
 
 // If a property or method is part of an experimental feature that can be
 // disabled at run-time by a preference, we keep it in the JSFunctionSpec /
 // JSPropertySpec list, but omit the definition if the preference is off.
 JS_FRIEND_API bool js::ShouldIgnorePropertyDefinition(JSContext* cx,
                                                       JSProtoKey key, jsid id) {
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1671,16 +1671,17 @@ static nsLineBox* FindLineClampTarget(ns
                "Should have been removed earlier in nsBlockFrame::Reflow");
     MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
                    NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
                "Should have been removed earlier in nsBlockReflow::Reflow");
 
     // Don't count a line that only has collapsible white space (as might exist
     // after calling e.g. getBoxQuads).
     if (line->IsEmpty()) {
+      iter.Next();
       continue;
     }
 
     if (aLineNumber == 0) {
       // We already previously found our target line, and now we have
       // confirmed that there is another line after it.
       foundFollowingLine = true;
       break;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-mpath-shadow.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink" class="reftest-wait">
+  <title>Test that pathLength works inside a shadow tree</title>
+  <defs>
+    <path id="path" pathLength="100" d="M0,0 h400" />
+    <g id="content">
+      <rect width="100%" height="100%" fill="lime"/>
+
+      <!-- calcMode="linear" -->
+      <rect x="10" y="10" width="100" height="100" fill="red"/>
+      <rect x="-190" y="10" width="100" height="100" fill="lime">
+        <animateMotion begin="100s" dur="1s" keyPoints="0;1" keyTimes="0;1" calcMode="linear">
+          <mpath xlink:href="#path" />
+        </animateMotion>
+      </rect>
+
+      <!-- calcMode="paced" -->
+      <rect x="10" y="110" width="100" height="100" fill="red"/>
+      <rect x="-190" y="110" width="100" height="100" fill="lime">
+        <animateMotion begin="100s" dur="1s">
+          <mpath xlink:href="#path" />
+        </animateMotion>
+      </rect>
+    </g>
+  </defs>
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function doTest() {
+      setTimeAndSnapshot(100.5, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+  <use xlink:href="#content"></use>
+</svg>
--- a/layout/reftests/svg/smil/motion/reftest.list
+++ b/layout/reftests/svg/smil/motion/reftest.list
@@ -14,8 +14,9 @@ fuzzy-if(skiaContent,0-1,0-40) == animat
 == animateMotion-values-linear-1.svg animateMotion-values-linear-1-ref.svg
 == animateMotion-values-paced-1a.svg animateMotion-values-paced-1-ref.svg
 fuzzy-if(skiaContent,0-1,0-30) == animateMotion-values-paced-1b.svg animateMotion-values-paced-1-ref.svg
 
 # Tests involving <mpath> sub-element
 == animateMotion-mpath-pathLength-1.svg lime.svg
 == animateMotion-mpath-targetChange-1.svg lime.svg
 == animateMotion-mpath-target-transform-1.svg lime.svg
+== animateMotion-mpath-shadow.svg lime.svg
--- a/layout/reftests/text/auto-hyphenation-af-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-af-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="af">
-Al&shy;le mens&shy;li&shy;ke we&shy;sens word vry, met ge&shy;ly&shy;ke waar&shy;dig&shy;heid en reg&shy;te, ge&shy;bo&shy;re.
+Alle mens&shy;li&shy;ke we&shy;sens word vry, met ge&shy;ly&shy;ke waar&shy;dig&shy;heid en reg&shy;te, ge&shy;bo&shy;re.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-bg-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-bg-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="bg">
-Всич&shy;ки хо&shy;ра се раж&shy;дат сво&shy;бод&shy;ни и рав&shy;ни по дос&shy;тойн&shy;с&shy;т&shy;во и пра&shy;ва.
+Всички хо&shy;ра се раж&shy;дат сво&shy;бод&shy;ни и рав&shy;ни по дос&shy;тойн&shy;с&shy;т&shy;во и пра&shy;ва.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-cy-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-cy-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="cy">
-Gen&shy;ir pawb yn rhydd ac yn gyd&shy;radd â'i gil&shy;ydd mewn urdd&shy;as a hawl&shy;iau.
+Genir pawb yn rhydd ac yn gyd&shy;radd â'i gil&shy;ydd mewn urdd&shy;as a hawl&shy;iau.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-da-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-da-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="da">
-Al&shy;le men&shy;ne&shy;sker er født frie og li&shy;ge i vær&shy;dig&shy;hed og ret&shy;tig&shy;he&shy;der.
+Alle men&shy;ne&shy;sker er født frie og li&shy;ge i vær&shy;dig&shy;hed og ret&shy;tig&shy;he&shy;der.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-es-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-es-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="es">
-To&shy;dos los se&shy;res hu&shy;ma&shy;nos na&shy;cen li&shy;bres e igua&shy;les en dig&shy;ni&shy;dad y de&shy;re&shy;chos
+Todos los se&shy;res hu&shy;ma&shy;nos na&shy;cen li&shy;bres e igua&shy;les en dig&shy;ni&shy;dad y de&shy;re&shy;chos
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-fi-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-fi-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="fi">
-Kaik&shy;ki ih&shy;mi&shy;set syn&shy;ty&shy;vät va&shy;pai&shy;na ja ta&shy;sa&shy;ver&shy;tai&shy;si&shy;na ar&shy;vol&shy;taan ja oi&shy;keuk&shy;sil&shy;taan.
+Kaikki ih&shy;mi&shy;set syn&shy;ty&shy;vät va&shy;pai&shy;na ja ta&shy;sa&shy;ver&shy;tai&shy;si&shy;na ar&shy;vol&shy;taan ja oi&shy;keuk&shy;sil&shy;taan.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-gl-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-gl-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="gl">
-Tó&shy;do&shy;los se&shy;res hu&shy;ma&shy;nos na&shy;cen li&shy;bres e iguais en dig&shy;ni&shy;da&shy;de e de&shy;rei&shy;tos
+Tódolos se&shy;res hu&shy;ma&shy;nos na&shy;cen li&shy;bres e iguais en dig&shy;ni&shy;da&shy;de e de&shy;rei&shy;tos
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-hu-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-hu-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="hu">
-Min&shy;den em&shy;be&shy;ri lény sza&shy;ba&shy;don szü&shy;le&shy;tik és egyen&shy;lő mél&shy;tó&shy;sá&shy;ga és jo&shy;ga van.
+Minden em&shy;be&shy;ri lény sza&shy;ba&shy;don szü&shy;le&shy;tik és egyen&shy;lő mél&shy;tó&shy;sá&shy;ga és jo&shy;ga van.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-ia-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-ia-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="ia">
-To&shy;te le es&shy;se&shy;res hu&shy;man na&shy;sce li&shy;be&shy;re e equal in dig&shy;ni&shy;ta&shy;te e in de&shy;rec&shy;tos
+Tote le es&shy;se&shy;res hu&shy;man na&shy;sce li&shy;be&shy;re e equal in dig&shy;ni&shy;ta&shy;te e in de&shy;rec&shy;tos
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-it-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-it-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="it">
-Tut&shy;ti gli es&shy;se&shy;ri uma&shy;ni na&shy;sco&shy;no li&shy;be&shy;ri ed egua&shy;li in di&shy;gni&shy;tà e di&shy;rit&shy;ti.
+Tutti gli es&shy;se&shy;ri uma&shy;ni na&shy;sco&shy;no li&shy;be&shy;ri ed egua&shy;li in di&shy;gni&shy;tà e di&shy;rit&shy;ti.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-kmr-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-kmr-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="kmr">
-He&shy;mû mi&shy;rov azad û di we&shy;qar û ma&shy;fan de we&shy;k&shy;hev tên din&shy;ya&shy;yê
+Hemû mi&shy;rov azad û di we&shy;qar û ma&shy;fan de we&shy;k&shy;hev tên din&shy;ya&shy;yê
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-la-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-la-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="la">
-Om&shy;nes ho&shy;mi&shy;nes di&shy;gni&shy;ta&shy;te et iu&shy;re li&shy;be&shy;ri et pa&shy;res na&shy;scun&shy;tur
+Omnes ho&shy;mi&shy;nes di&shy;gni&shy;ta&shy;te et iu&shy;re li&shy;be&shy;ri et pa&shy;res na&shy;scun&shy;tur
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-lt-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-lt-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="lt">
-Vi&shy;si žmo&shy;nės gims&shy;ta lais&shy;vi ir ly&shy;gūs sa&shy;vo oru&shy;mu ir tei&shy;sė&shy;mis.
+Visi žmo&shy;nės gims&shy;ta lais&shy;vi ir ly&shy;gūs sa&shy;vo oru&shy;mu ir tei&shy;sė&shy;mis.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-nl-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-nl-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="nl">
-Al&shy;le men&shy;sen wor&shy;den vrij en ge&shy;lijk in waar&shy;dig&shy;heid en rech&shy;ten ge&shy;bo&shy;ren
+Alle men&shy;sen wor&shy;den vrij en ge&shy;lijk in waar&shy;dig&shy;heid en rech&shy;ten ge&shy;bo&shy;ren
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-pl-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-pl-1-ref.html
@@ -6,18 +6,17 @@
 <style>
 body {
   font-family: "Times New Roman", serif; /* prefer TNR to Times (default) on OS X... */
   font-feature-settings: 'kern' off;
 }
 </style>
 </head>
 <body>
-<div style="white-space:pre-wrap;">Uni-
-kod
+<div style="white-space:pre-wrap;">Unikod
 przy-
 pi-
 su-
 je
 uni-
 kal-
 ny
 nu-
--- a/layout/reftests/text/auto-hyphenation-pt-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-pt-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="pt">
-To&shy;dos os se&shy;res hu&shy;ma&shy;nos nas&shy;cem li&shy;vres e iguais em dig&shy;ni&shy;da&shy;de e em di&shy;rei&shy;tos
+Todos os se&shy;res hu&shy;ma&shy;nos nas&shy;cem li&shy;vres e iguais em dig&shy;ni&shy;da&shy;de e em di&shy;rei&shy;tos
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-sv-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-sv-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="sv">
-Al&shy;la män&shy;ni&shy;skor äro föd&shy;da fria och li&shy;ka i vär&shy;de och rät&shy;tig&shy;he&shy;ter
+Alla män&shy;ni&shy;skor äro föd&shy;da fria och li&shy;ka i vär&shy;de och rät&shy;tig&shy;he&shy;ter
 </div>
 </body>
 </html>
--- a/layout/reftests/text/auto-hyphenation-tr-1-ref.html
+++ b/layout/reftests/text/auto-hyphenation-tr-1-ref.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=utf-8">
 </head>
 <body>
 <div style="width:1em; hyphens:manual;" lang="tr">
-Bü&shy;tün in&shy;san&shy;lar hür, hay&shy;si&shy;yet ve hak&shy;lar ba&shy;kı&shy;mın&shy;dan eşit do&shy;ğar&shy;lar.
+Bütün in&shy;san&shy;lar hür, hay&shy;si&shy;yet ve hak&shy;lar ba&shy;kı&shy;mın&shy;dan eşit do&shy;ğar&shy;lar.
 </div>
 </body>
 </html>
--- a/layout/reftests/text/hyphenation-control-3-ref.html
+++ b/layout/reftests/text/hyphenation-control-3-ref.html
@@ -6,12 +6,12 @@ code {
   display:block;
   hyphens:manual;
   border: 1px solid black;
 }
 </style>
 </head>
 <body lang="en-us">
 <code style="width:100ch;">
-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTU-<br />VWXYZsupercalifragilisticexpialidocious-<br />ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu-<br />vwxyzsupercalifragilisticexpialidocious-<br />abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
 </code>
 </body>
 </html>
--- a/layout/reftests/text/hyphenation-control-3.html
+++ b/layout/reftests/text/hyphenation-control-3.html
@@ -10,12 +10,12 @@ code {
 </style>
 </head>
 <body lang="en-us">
 <!--
   Test checks that automatic hyphenation opportunities should honor
   manual hyphenation opportunities even if they are within an extreme long word.
 -->
 <code style="width:100ch;">
-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZsuper&shy;cali&shy;fragi&shy;listic&shy;expiali&shy;docious&shy;ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzsuper&shy;cali&shy;fragi&shy;listic&shy;expiali&shy;docious&shy;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
 </code>
 </body>
 </html>
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -135,16 +135,17 @@ import org.mozilla.gecko.tabs.TabHistory
 import org.mozilla.gecko.tabs.TabHistoryFragment;
 import org.mozilla.gecko.tabs.TabHistoryPage;
 import org.mozilla.gecko.tabs.TabsPanel;
 import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
 import org.mozilla.gecko.telemetry.TelemetryUploadService;
 import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
+import org.mozilla.gecko.toolbar.BrowserToolbar.CommitEventSource;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.toolbar.PwaConfirm;
 import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
 import org.mozilla.gecko.updater.PostUpdateHandler;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ContextUtils;
 import org.mozilla.gecko.util.DrawableUtil;
@@ -1253,18 +1254,19 @@ public class BrowserApp extends GeckoApp
             @Override
             public void onActivate() {
                 enterEditingMode();
             }
         });
 
         mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
             @Override
-            public void onCommitByKey() {
-                if (commitEditingMode()) {
+            public void onCommit(CommitEventSource eventSource) {
+                final boolean didCommit = commitEditingMode();
+                if (didCommit && eventSource == CommitEventSource.KEY_EVENT) {
                     // We're committing in response to a key-down event. Since we'll be hiding the
                     // ToolbarEditLayout, the corresponding key-up event will end up being sent to
                     // Gecko which we don't want, as this messes up tracking of the last user input.
                     mSuppressNextKeyUp = true;
                 }
             }
         });
 
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -89,17 +89,22 @@ public abstract class BrowserToolbar ext
     private static final int LIGHTWEIGHT_THEME_INVERT_ALPHA_END = 179;
     public static final int LIGHTWEIGHT_THEME_INVERT_ALPHA_TABLET = 51;
 
     public interface OnActivateListener {
         public void onActivate();
     }
 
     public interface OnCommitListener {
-        public void onCommitByKey();
+        public void onCommit(CommitEventSource eventSource);
+    }
+
+    public enum CommitEventSource {
+        KEY_EVENT,
+        PRE_IME_KEY_EVENT
     }
 
     public interface OnDismissListener {
         public void onDismiss();
     }
 
     public interface OnFilterListener {
         public void onFilter(String searchText, AutocompleteHandler handler);
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java
@@ -3,16 +3,17 @@
  * 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/. */
 
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.CustomEditText;
 import org.mozilla.gecko.InputMethods;
+import org.mozilla.gecko.toolbar.BrowserToolbar.CommitEventSource;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
 import org.mozilla.gecko.toolbar.ToolbarEditLayout.OnSearchStateChangeListener;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
@@ -600,17 +601,17 @@ public class ToolbarEditText extends Cus
             }
 
             if (keyCode == KeyEvent.KEYCODE_ENTER) {
                 // If the edit text has a composition string, don't submit the text yet.
                 // ENTER is needed to commit the composition string.
                 final Editable content = getText();
                 if (!hasCompositionString(content)) {
                     if (mCommitListener != null) {
-                        mCommitListener.onCommitByKey();
+                        mCommitListener.onCommit(CommitEventSource.PRE_IME_KEY_EVENT);
                     }
 
                     return true;
                 }
             }
 
             if (keyCode == KeyEvent.KEYCODE_BACK) {
                 // Drop the virtual keyboard.
@@ -626,17 +627,17 @@ public class ToolbarEditText extends Cus
         @Override
         public boolean onKey(View v, int keyCode, KeyEvent event) {
             if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) {
                 if (event.getAction() != KeyEvent.ACTION_DOWN) {
                     return true;
                 }
 
                 if (mCommitListener != null) {
-                    mCommitListener.onCommitByKey();
+                    mCommitListener.onCommit(CommitEventSource.KEY_EVENT);
                 }
 
                 return true;
             }
 
             if (GamepadUtils.isBackKey(event)) {
                 if (mDismissListener != null) {
                     mDismissListener.onDismiss();
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -291,16 +291,36 @@ function resolveGeckoURI(aURI) {
     return registry.convertChromeURL(Services.io.newURI(aURI)).spec;
   } else if (aURI.startsWith("resource://")) {
     let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
     return handler.resolveURI(Services.io.newURI(aURI));
   }
   return aURI;
 }
 
+XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
+  Components.Constructor(
+    "@mozilla.org/referrer-info;1",
+    "nsIReferrerInfo",
+    "init"));
+
+function createReferrerInfo(aReferrer) {
+  let referrerUri;
+  try {
+    referrerUri = Services.io.newURI(aReferrer);
+  } catch (ignored) {
+  }
+
+  return new ReferrerInfo(
+    Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
+    true,
+    referrerUri
+  );
+}
+
 /**
  * Cache of commonly used string bundles.
  */
 var Strings = {
   init: function() {
     XPCOMUtils.defineLazyGetter(Strings, "brand", () => Services.strings.createBundle("chrome://branding/locale/brand.properties"));
     XPCOMUtils.defineLazyGetter(Strings, "browser", () => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
     XPCOMUtils.defineLazyGetter(Strings, "reader", () => Services.strings.createBundle("chrome://global/locale/aboutReader.properties"));
@@ -1174,31 +1194,31 @@ var BrowserApp = {
     aBrowser = aBrowser || this.selectedBrowser;
     if (!aBrowser)
       return;
 
     aParams = aParams || {};
 
     let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
     let postData = ("postData" in aParams && aParams.postData) ? aParams.postData : null;
-    let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
+    let referrerInfo = "referrerURI" in aParams ? createReferrerInfo(aParams.referrerURI) : null;
     let charset = "charset" in aParams ? aParams.charset : null;
 
     let tab = this.getTabForBrowser(aBrowser);
     if (tab) {
       if ("userRequested" in aParams) tab.userRequested = aParams.userRequested;
       tab.isSearch = ("isSearch" in aParams) ? aParams.isSearch : false;
     }
     // Don't fall back to System here Bug 1474619
     let triggeringPrincipal = "triggeringPrincipal" in aParams ? aParams.triggeringPrincipal : Services.scriptSecurityManager.getSystemPrincipal();
 
     try {
       aBrowser.loadURI(aURI, {
         flags,
-        referrerURI,
+        referrerInfo,
         charset,
         postData,
         triggeringPrincipal,
       });
     } catch (e) {
       if (tab) {
         let message = {
           type: "Content:LoadError",
@@ -3460,17 +3480,17 @@ nsBrowserAccess.prototype = {
       return tab.browser;
     }
 
     // OPEN_CURRENTWINDOW and illegal values
     let browser = BrowserApp.selectedBrowser;
     if (aURI && browser) {
       browser.loadURI(aURI.spec, {
         flags: loadflags,
-        referrerURI: referrer,
+        referrerInfo: createReferrerInfo(referrer),
         triggeringPrincipal: aTriggeringPrincipal,
       });
     }
 
     return browser;
   },
 
   openURI: function browser_openURI(aURI, aOpener, aWhere, aFlags,
@@ -3754,27 +3774,27 @@ Tab.prototype = {
       this.browser.setAttribute("pending", "true");
     } else {
       let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       if (aParams.disallowInheritPrincipal) {
         flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
       }
 
       let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
-      let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
+      let referrerInfo = "referrerURI" in aParams ? createReferrerInfo(aParams.referrerURI) : null;
       let charset = "charset" in aParams ? aParams.charset : null;
 
       // The search term the user entered to load the current URL
       this.userRequested = "userRequested" in aParams ? aParams.userRequested : "";
       this.isSearch = "isSearch" in aParams ? aParams.isSearch : false;
 
       try {
         this.browser.loadURI(aURL, {
           flags,
-          referrerURI,
+          referrerInfo,
           charset,
           postData,
           triggeringPrincipal: aParams.triggeringPrincipal,
         });
       } catch (e) {
         let message = {
           type: "Content:LoadError",
           tabID: this.id,
--- a/mobile/android/components/geckoview/GeckoViewHistory.cpp
+++ b/mobile/android/components/geckoview/GeckoViewHistory.cpp
@@ -306,37 +306,30 @@ class OnVisitedCallback final : public n
  public:
   explicit OnVisitedCallback(GeckoViewHistory* aHistory,
                              nsIGlobalObject* aGlobalObject, nsIURI* aURI)
       : mHistory(aHistory), mGlobalObject(aGlobalObject), mURI(aURI) {}
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD
-  OnSuccess(JS::HandleValue aData) override {
+  OnSuccess(JS::HandleValue aData, JSContext* aCx) override {
     bool shouldNotify = false;
-    {
-      // Scope `jsapi`.
-      dom::AutoJSAPI jsapi;
-      if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
-        return NS_ERROR_FAILURE;
-      }
-      shouldNotify = ShouldNotifyVisited(jsapi.cx(), aData);
-      JS_ClearPendingException(jsapi.cx());
-    }
+    shouldNotify = ShouldNotifyVisited(aCx, aData);
+    JS_ClearPendingException(aCx);
     if (shouldNotify) {
       AutoTArray<VisitedURI, 1> visitedURIs;
       visitedURIs.AppendElement(VisitedURI{mURI, true});
       mHistory->HandleVisitedState(visitedURIs);
     }
     return NS_OK;
   }
 
   NS_IMETHOD
-  OnError(JS::HandleValue aData) override { return NS_OK; }
+  OnError(JS::HandleValue aData, JSContext* aCx) override { return NS_OK; }
 
  private:
   virtual ~OnVisitedCallback() {}
 
   bool ShouldNotifyVisited(JSContext* aCx, JS::HandleValue aData) {
     if (NS_WARN_IF(!aData.isBoolean())) {
       return false;
     }
@@ -499,35 +492,28 @@ class GetVisitedCallback final : public 
   explicit GetVisitedCallback(GeckoViewHistory* aHistory,
                               nsIGlobalObject* aGlobalObject,
                               const nsTArray<nsCOMPtr<nsIURI>>& aURIs)
       : mHistory(aHistory), mGlobalObject(aGlobalObject), mURIs(aURIs) {}
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD
-  OnSuccess(JS::HandleValue aData) override {
+  OnSuccess(JS::HandleValue aData, JSContext* aCx) override {
     nsTArray<VisitedURI> visitedURIs;
-    {
-      // Scope `jsapi`.
-      dom::AutoJSAPI jsapi;
-      if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) {
-        return NS_ERROR_FAILURE;
-      }
-      if (!ExtractVisitedURIs(jsapi.cx(), aData, visitedURIs)) {
-        JS_ClearPendingException(jsapi.cx());
-        return NS_ERROR_FAILURE;
-      }
+    if (!ExtractVisitedURIs(aCx, aData, visitedURIs)) {
+      JS_ClearPendingException(aCx);
+      return NS_ERROR_FAILURE;
     }
     mHistory->HandleVisitedState(visitedURIs);
     return NS_OK;
   }
 
   NS_IMETHOD
-  OnError(JS::HandleValue aData) override { return NS_OK; }
+  OnError(JS::HandleValue aData, JSContext* aCx) override { return NS_OK; }
 
  private:
   virtual ~GetVisitedCallback() {}
 
   /**
    * Unpacks an array of Boolean visited statuses from the session handler into
    * an array of `VisitedURI` structs. Each element in the array corresponds to
    * a URI in `mURIs`.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
@@ -331,17 +331,18 @@ public class PanZoomController {
      */
     public boolean onMotionEvent(final @NonNull MotionEvent event) {
         ThreadUtils.assertOnUiThread();
 
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_SCROLL) {
             if (event.getDownTime() >= mLastDownTime) {
                 mLastDownTime = event.getDownTime();
-            } else if ((InputDevice.getDevice(event.getDeviceId()).getSources() &
+            } else if ((InputDevice.getDevice(event.getDeviceId()) != null) &&
+                       (InputDevice.getDevice(event.getDeviceId()).getSources() &
                         InputDevice.SOURCE_TOUCHPAD) == InputDevice.SOURCE_TOUCHPAD) {
                 return false;
             }
             return handleScrollEvent(event);
         } else if ((action == MotionEvent.ACTION_HOVER_MOVE) ||
                    (action == MotionEvent.ACTION_HOVER_ENTER) ||
                    (action == MotionEvent.ACTION_HOVER_EXIT)) {
             return handleMouseEvent(event);
--- a/mobile/android/tests/browser/chrome/chrome.ini
+++ b/mobile/android/tests/browser/chrome/chrome.ini
@@ -39,16 +39,17 @@ skip-if = debug
 [test_identity_mode.html]
 [test_media_playback.html]
 [test_migrate_ui.html]
 [test_mozAutoplayMediaBlocked.html]
 [test_network_manager.html]
 [test_offline_page.html]
 skip-if = true # Bug 1241478
 [test_reader_view.html]
+[test_referrer.html]
 [test_resource_substitutions.html]
 [test_restricted_profiles.html]
 [test_select_disabled.html]
 [test_selectoraddtab.html]
 [test_session_clear_history.html]
 [test_session_form_data.html]
 [test_session_parentid.html]
 [test_session_scroll_position.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/chrome/test_referrer.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1549732
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1549732</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript">
+
+  "use strict";
+
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+  ChromeUtils.import("resource://gre/modules/Messaging.jsm");
+
+  // The chrome window
+  let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+  let BrowserApp = chromeWin.BrowserApp;
+
+  // Track the tabs where the tests are happening
+  let tabTest;
+
+  function cleanupTabs() {
+    if (tabTest) {
+      BrowserApp.closeTab(tabTest);
+      tabTest = null;
+    }
+  }
+
+  SimpleTest.registerCleanupFunction(function() {
+    cleanupTabs();
+  });
+
+  const kTestPage = "https://example.com";
+  const kReferrer = "https://foo.org/";
+
+  add_task(async function test_referrer() {
+    tabTest = BrowserApp.addTab(kTestPage, { referrerURI: kReferrer,
+                                              parentId: BrowserApp.selectedTab.id,
+                                              selected: true});
+    await promiseBrowserEvent(tabTest.browser, "DOMContentLoaded");
+
+    // Check that basic_article is now selected
+    is(BrowserApp.selectedBrowser, tabTest.browser, "Target tab is selected after being added.");
+    is(tabTest.browser.contentDocument.referrer, kReferrer, "Target tab has correct referrer");
+
+    cleanupTabs();
+  });
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1549732">Mozilla Bug 1549732</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2492,16 +2492,21 @@ pref("intl.hyphenation-alias.bs-*", "sh"
 
 // Norwegian has two forms, Bokmål and Nynorsk, with "no" as a macrolanguage encompassing both.
 // For "no", we'll alias to "nb" (Bokmål) as that is the more widely used written form.
 pref("intl.hyphenation-alias.no", "nb");
 pref("intl.hyphenation-alias.no-*", "nb");
 pref("intl.hyphenation-alias.nb-*", "nb");
 pref("intl.hyphenation-alias.nn-*", "nn");
 
+// In German, we allow hyphenation of capitalized words; otherwise not.
+pref("intl.hyphenate-capitalized.de-1996", true);
+pref("intl.hyphenate-capitalized.de-1901", true);
+pref("intl.hyphenate-capitalized.de-CH", true);
+
 // All prefs of default font should be "auto".
 pref("font.name.serif.ar", "");
 pref("font.name.sans-serif.ar", "");
 pref("font.name.monospace.ar", "");
 pref("font.name.cursive.ar", "");
 
 pref("font.name.serif.el", "");
 pref("font.name.sans-serif.el", "");
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -1545,25 +1545,29 @@ nsresult nsSocketTransport::InitiateSock
       SOCKET_LOG(
           ("nsSocketTransport::InitiateSocket TCP Fast Open "
            "started [this=%p]\n",
            this));
     }
   }
 
   if (usingSSL && SSLTokensCache::IsEnabled()) {
-    nsTArray<uint8_t> token;
-    nsresult rv2 = SSLTokensCache::Get(mHost, token);
-    if (NS_SUCCEEDED(rv2) && token.Length() != 0) {
-      SECStatus srv =
-          SSL_SetResumptionToken(fd, token.Elements(), token.Length());
-      if (srv == SECFailure) {
-        SOCKET_LOG(("Setting token failed with NSS error %d [host=%s]",
-                    PORT_GetError(), PromiseFlatCString(mHost).get()));
-        SSLTokensCache::Remove(mHost);
+    PRIntn val;
+    // If SSL_NO_CACHE option was set, we must not use the cache
+    if (SSL_OptionGet(fd, SSL_NO_CACHE, &val) == SECSuccess && val == 0) {
+      nsTArray<uint8_t> token;
+      nsresult rv2 = SSLTokensCache::Get(mHost, token);
+      if (NS_SUCCEEDED(rv2) && token.Length() != 0) {
+        SECStatus srv =
+            SSL_SetResumptionToken(fd, token.Elements(), token.Length());
+        if (srv == SECFailure) {
+          SOCKET_LOG(("Setting token failed with NSS error %d [host=%s]",
+                      PORT_GetError(), PromiseFlatCString(mHost).get()));
+          SSLTokensCache::Remove(mHost);
+        }
       }
     }
 
     SSL_SetResumptionTokenCallback(fd, &StoreResumptionToken, this);
     mSSLCallbackSet = true;
   }
 
   bool connectCalled = true;  // This is only needed for telemetry.
--- a/remote/RemoteAgent.jsm
+++ b/remote/RemoteAgent.jsm
@@ -90,22 +90,22 @@ class RemoteAgentClass {
       return;
     }
 
     this.init();
 
     await this.tabs.start();
 
     try {
-      this.server._start(port, host);
-
       // Immediatly instantiate the main process target in order
       // to be accessible via HTTP endpoint on startup
       const mainTarget = this.targets.getMainProcessTarget();
-      log.info(`Remote debugging agent listening on ${mainTarget.wsDebuggerURL}`);
+
+      this.server._start(port, host);
+      dump(`DevTools listening on ${mainTarget.wsDebuggerURL}`);
     } catch (e) {
       throw new Error(`Unable to start remote agent: ${e.message}`, e);
     }
 
     Preferences.set(RecommendedPreferences);
   }
 
   async close() {
--- a/remote/domains/content/Runtime.jsm
+++ b/remote/domains/content/Runtime.jsm
@@ -72,16 +72,51 @@ class Runtime extends ContentProcessDoma
     }
     if (typeof(request.expression) != "string") {
       throw new Error(`Expecting 'expression' attribute to be a string. ` +
         `But was: ${typeof(request.expression)}`);
     }
     return context.evaluate(request.expression);
   }
 
+  callFunctionOn(request) {
+    let context = null;
+    // When an `objectId` is passed, we want to execute the function of a given object
+    // So we first have to find its ExecutionContext
+    if (request.objectId) {
+      for (const ctx of this.contexts.values()) {
+        if (ctx.hasRemoteObject(request.objectId)) {
+          context = ctx;
+          break;
+        }
+      }
+      if (!context) {
+        throw new Error(`Unable to get the context for object with id: ${request.objectId}`);
+      }
+    } else {
+      context = this.contexts.get(request.executionContextId);
+      if (!context) {
+        throw new Error(`Unable to find execution context with id: ${request.executionContextId}`);
+      }
+    }
+    if (typeof(request.functionDeclaration) != "string") {
+      throw new Error("Expect 'functionDeclaration' attribute to be passed and be a string");
+    }
+    if (request.arguments && !Array.isArray(request.arguments)) {
+      throw new Error("Expect 'arguments' to be an array");
+    }
+    if (request.returnByValue && typeof(request.returnByValue) != "boolean") {
+      throw new Error("Expect 'returnByValue' to be a boolean");
+    }
+    if (request.awaitPromise && typeof(request.awaitPromise) != "boolean") {
+      throw new Error("Expect 'awaitPromise' to be a boolean");
+    }
+    return context.callFunctionOn(request.functionDeclaration, request.arguments, request.returnByValue, request.awaitPromise, request.objectId);
+  }
+
   get _debugger() {
     if (this.__debugger) {
       return this.__debugger;
     }
     this.__debugger = new Debugger();
     return this.__debugger;
   }
 
--- a/remote/domains/content/runtime/ExecutionContext.jsm
+++ b/remote/domains/content/runtime/ExecutionContext.jsm
@@ -33,16 +33,20 @@ class ExecutionContext {
 
     this._remoteObjects = new Map();
   }
 
   destructor() {
     this._debugger.removeDebuggee(this._debuggee);
   }
 
+  hasRemoteObject(id) {
+    return this._remoteObjects.has(id);
+  }
+
   /**
    * Evaluate a Javascript expression.
    *
    * @param {String} expression
    *   The JS expression to evaluate against the JS context.
    * @return {Object} A multi-form object depending if the execution succeed or failed.
    *   If the expression failed to evaluate, it will return an object with an
    *   `exceptionDetails` attribute matching the `ExceptionDetails` CDP type.
@@ -54,60 +58,177 @@ class ExecutionContext {
     if (!rv) {
       return {
         exceptionDetails: {
           text: "Evaluation terminated!",
         },
       };
     }
     if (rv.throw) {
-      if (this._debuggee.executeInGlobalWithBindings("e instanceof Error", {e: rv.throw}).return) {
-        return {
-          exceptionDetails: {
-            text: this._debuggee.executeInGlobalWithBindings("e.message", {e: rv.throw}).return,
-          },
-        };
-      }
+      return this._returnError(rv.throw);
+    }
+    return {
+      result: this._toRemoteObject(rv.return),
+    };
+  }
+
+  /**
+   * Given a Debugger.Object reference for an Exception, return a JSON object
+   * describing the exception by following CDP ExceptionDetails specification.
+   */
+  _returnError(exception) {
+    if (this._debuggee.executeInGlobalWithBindings("exception instanceof Error",
+      {exception}).return) {
+      const text = this._debuggee.executeInGlobalWithBindings("exception.message",
+        {exception}).return;
       return {
         exceptionDetails: {
-          exception: this._createRemoteObject(rv.throw),
+          text,
         },
       };
     }
+
+    // If that isn't an Error, consider the exception as a JS value
     return {
-      result: this._createRemoteObject(rv.return),
+      exceptionDetails: {
+        exception: this._toRemoteObject(exception),
+      },
+    };
+  }
+
+  async callFunctionOn(functionDeclaration, callArguments = [], returnByValue = false, awaitPromise = false, objectId = null) {
+    // Map the given objectId to a JS reference.
+    let thisArg = null;
+    if (objectId) {
+      thisArg = this._remoteObjects.get(objectId);
+      if (!thisArg) {
+        throw new Error(`Unable to get target object with id: ${objectId}`);
+      }
+    }
+
+    // First evaluate the function
+    const fun = this._debuggee.executeInGlobal("(" + functionDeclaration + ")");
+    if (!fun) {
+      return {
+        exceptionDetails: {
+          text: "Evaluation terminated!",
+        },
+      };
+    }
+    if (fun.throw) {
+      return this._returnError(fun.throw);
+    }
+
+    // Then map all input arguments, which are matching CDP's CallArguments type,
+    // into JS values
+    const args = callArguments.map(arg => this._fromCallArgument(arg));
+
+    // Finally, call the function with these arguments
+    const rv = fun.return.apply(thisArg, args);
+    if (rv.throw) {
+      return this._returnError(rv.throw);
+    }
+
+    let result = rv.return;
+
+    if (result && result.isPromise && awaitPromise) {
+      if (result.promiseState === "fulfilled") {
+        result = result.promiseValue;
+      } else if (result.promiseState === "rejected") {
+        return this._returnError(result.promiseReason);
+      } else {
+        try {
+          const promiseResult = await result.unsafeDereference();
+          result = this._debuggee.makeDebuggeeValue(promiseResult);
+        } catch (e) {
+          // The promise has been rejected
+          return this._returnError(e);
+        }
+      }
+    }
+
+    if (returnByValue) {
+      return {
+        result: {
+          value: this._serialize(result),
+        },
+      };
+    }
+
+    return {
+      result: this._toRemoteObject(result),
     };
   }
 
   /**
    * Convert a given `Debugger.Object` to a JSON string.
    *
    * @param {Debugger.Object} obj
    *  The object to convert
    * @return {String}
    *  The JSON string
    */
   _serialize(obj) {
+    if (typeof(obj) == "undefined") {
+      return undefined;
+    }
     const result = this._debuggee.executeInGlobalWithBindings("JSON.stringify(e)", {e: obj});
     if (result.throw) {
       throw new Error("Object is not serializable");
     }
     return JSON.parse(result.return);
   }
 
   /**
+   * Given a CDP `CallArgument`, return a JS value that represent this argument.
+   * Note that `CallArgument` is actually very similar to `RemoteObject`
+   */
+  _fromCallArgument(arg) {
+    if (arg.objectId) {
+      if (!this._remoteObjects.has(arg.objectId)) {
+        throw new Error(`Cannot find object with ID: ${arg.objectId}`);
+      }
+      return this._remoteObjects.get(arg.objectId);
+    }
+    if (arg.unserializableValue) {
+      switch (arg.unserializableValue) {
+        case "Infinity": return Infinity;
+        case "-Infinity": return -Infinity;
+        case "-0": return -0;
+        case "NaN": return NaN;
+      }
+    }
+    return this._deserialize(arg.value);
+  }
+
+  /**
+   * Given a JS value, create a copy of it within the debugee compartment.
+   */
+  _deserialize(obj) {
+    if (typeof obj !== "object") {
+      return obj;
+    }
+    const result = this._debuggee.executeInGlobalWithBindings("JSON.parse(obj)",
+      {obj: JSON.stringify(obj)});
+    if (result.throw) {
+      throw new Error("Unable to deserialize object");
+    }
+    return result.return;
+  }
+
+  /**
    * Given a `Debugger.Object` object, return a JSON-serializable description of it
    * matching `RemoteObject` CDP type.
    *
    * @param {Debugger.Object} debuggerObj
    *  The object to serialize
    * @return {RemoteObject}
    *  The serialized description of the given object
    */
-  _createRemoteObject(debuggerObj) {
+  _toRemoteObject(debuggerObj) {
     // First handle all non-primitive values which are going to be wrapped by the
     // Debugger API into Debugger.Object instances
     if (debuggerObj instanceof Debugger.Object) {
       const objectId = uuid();
       this._remoteObjects.set(objectId, debuggerObj);
       const rawObj = debuggerObj.unsafeDereference();
 
       // Map the Debugger API `class` attribute to CDP `subtype`
--- a/remote/test/browser/browser.ini
+++ b/remote/test/browser/browser.ini
@@ -5,12 +5,13 @@ support-files =
   chrome-remote-interface.js
   head.js
 skip-if = debug || asan # bug 1546945
 
 [browser_cdp.js]
 [browser_main_target.js]
 [browser_page_frameNavigated.js]
 [browser_runtime_evaluate.js]
+[browser_runtime_callFunctionOn.js]
 [browser_runtime_executionContext.js]
 skip-if = os == "mac" || (verify && os == 'win') # bug 1547961
 [browser_tabs.js]
 [browser_target.js]
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/browser_runtime_callFunctionOn.js
@@ -0,0 +1,228 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the Runtime.callFunctionOn
+// Also see browser_runtime_evaluate as it covers basic usages of this method.
+
+const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
+
+add_task(async function() {
+  // Open a test page, to prevent debugging the random default page
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+
+  // Start the CDP server
+  await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
+
+  // Retrieve the chrome-remote-interface library object
+  const CDP = await getCDP();
+
+  // Connect to the server
+  const client = await CDP({
+    target(list) {
+      // Ensure debugging the right target, i.e. the one for our test tab.
+      return list.find(target => target.url == TEST_URI);
+    },
+  });
+  ok(true, "CDP client has been instantiated");
+
+  const firstContext = await testRuntimeEnable(client);
+  const contextId = firstContext.id;
+  await testObjectReferences(client, contextId);
+  await testExceptions(client, contextId);
+  await testReturnByValue(client, contextId);
+  await testAwaitPromise(client, contextId);
+  await testObjectId(client, contextId);
+
+  await client.close();
+  ok(true, "The client is closed");
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  await RemoteAgent.close();
+});
+
+async function testRuntimeEnable({ Runtime }) {
+  // Enable watching for new execution context
+  await Runtime.enable();
+  ok(true, "Runtime domain has been enabled");
+
+  // Calling Runtime.enable will emit executionContextCreated for the existing contexts
+  const { context } = await Runtime.executionContextCreated();
+  ok(!!context.id, "The execution context has an id");
+  ok(context.auxData.isDefault, "The execution context is the default one");
+  ok(!!context.auxData.frameId, "The execution context has a frame id set");
+
+  return context;
+}
+
+async function testObjectReferences({ Runtime }, contextId) {
+  // First create a JS object remotely via Runtime.evaluate
+  const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 1 })" });
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, null, "The subtype is null for objects");
+  ok(!!result.objectId, "Got an object id");
+
+  // Then increment the `foo` attribute of this JS object, while returning this
+  // attribute value
+  const { result: result2 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "arg => ++arg.foo",
+    arguments: [{ objectId: result.objectId }],
+  });
+  is(result2.type, "number", "The type is correct");
+  is(result2.subtype, null, "The subtype is null for numbers");
+  is(result2.value, 2, "Updated the existing object and returned the incremented value");
+
+  // Finally, try to pass this JS object and get it back. Ensure that it returns
+  // the same object id. Also increment the attribute again.
+  const { result: result3 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "arg => { arg.foo++; return arg; }",
+    arguments: [{ objectId: result.objectId }],
+  });
+  is(result3.type, "object", "The type is correct");
+  is(result3.subtype, null, "The subtype is null for objects");
+  // Remote object are not having unique id. So you may have multiple object ids
+  // that reference the same remote object
+  ok(!!result3.objectId, "Got an object id");
+  isnot(result3.objectId, result.objectId, "The object id is stable");
+
+  // Assert that we can still access this object and that its foo attribute
+  // has been incremented. Use the second object id we got from previous call
+  // to callFunctionOn.
+  const { result: result4 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "arg => arg.foo",
+    arguments: [{ objectId: result3.objectId }],
+  });
+  is(result4.type, "number", "The type is correct");
+  is(result4.subtype, null, "The subtype is null for numbers");
+  is(result4.value, 3, "Updated the existing object and returned the incremented value");
+}
+
+async function testExceptions({ Runtime }, executionContextId) {
+  // Test error when evaluating the function
+  let { exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "doesNotExists()",
+  });
+  is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client");
+
+  // Test error when calling the function
+  ({ exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => doesNotExists()",
+  }));
+  is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client");
+}
+
+async function testReturnByValue({ Runtime }, executionContextId) {
+  const values = [
+    42,
+    "42",
+    42.00,
+    true,
+    false,
+    null,
+    { foo: true },
+    { foo: { bar: 42, str: "str", array: [1, 2, 3] } },
+    [ 42, "42", true ],
+    [ { foo: true } ],
+  ];
+  for (const value of values) {
+    const { result } = await Runtime.callFunctionOn({
+      executionContextId,
+      functionDeclaration: "() => (" + JSON.stringify(value) + ")",
+      returnByValue: true,
+    });
+    Assert.deepEqual(result.value, value, "The returned value is the same than the input value");
+  }
+
+  // Test undefined individually as JSON.stringify doesn't return a string
+  const { result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => {}",
+    returnByValue: true,
+  });
+  is(result.value, undefined, "The returned value is undefined");
+}
+
+async function testAwaitPromise({ Runtime }, executionContextId) {
+  // First assert promise resolution with awaitPromise
+  let { result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.resolve(42)",
+    awaitPromise: true,
+  });
+  is(result.type, "number", "The type is correct");
+  is(result.subtype, null, "The subtype is null for numbers");
+  is(result.value, 42, "The result is the promise's resolution");
+
+  // Also test promise rejection with awaitPromise
+  let { exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.reject(42)",
+    awaitPromise: true,
+  });
+  is(exceptionDetails.exception.value, 42, "The result is the promise's rejection");
+
+  // Then check delayed promise resolution
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))",
+    awaitPromise: true,
+  }));
+  is(result.type, "number", "The type is correct");
+  is(result.subtype, null, "The subtype is null for numbers");
+  is(result.value, 42, "The result is the promise's resolution");
+
+  // And delayed promise rejection
+  ({ exceptionDetails } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => new Promise((_,r) => setTimeout(() => r(42), 0))",
+    awaitPromise: true,
+  }));
+  is(exceptionDetails.exception.value, 42, "The result is the promise's rejection");
+
+  // Finally assert promise resolution without awaitPromise
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.resolve(42)",
+    awaitPromise: false,
+  }));
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, "promise", "The subtype is promise");
+  ok(!!result.objectId, "We got the object id for the promise");
+  ok(!result.value, "We do not receive any value");
+
+  // As well as promise rejection without awaitPromise
+  ({ result } = await Runtime.callFunctionOn({
+    executionContextId,
+    functionDeclaration: "() => Promise.reject(42)",
+    awaitPromise: false,
+  }));
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, "promise", "The subtype is promise");
+  ok(!!result.objectId, "We got the object id for the promise");
+  ok(!result.exceptionDetails, "We do not receive any exception");
+}
+
+async function testObjectId({ Runtime }, contextId) {
+  // First create an object via Runtime.evaluate
+  const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" });
+  is(result.type, "object", "The type is correct");
+  is(result.subtype, null, "The subtype is null for objects");
+  ok(!!result.objectId, "Got an object id");
+
+  // Then apply a method on this object
+  const { result: result2 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "function () { return this.foo; }",
+    objectId: result.objectId,
+  });
+  is(result2.type, "number", "The type is correct");
+  is(result2.subtype, null, "The subtype is null for numbers");
+  is(result2.value, 42, "We have a good proof that the function was ran against the target object");
+}
--- a/remote/test/browser/browser_runtime_evaluate.js
+++ b/remote/test/browser/browser_runtime_evaluate.js
@@ -23,24 +23,78 @@ add_task(async function() {
       // Ensure debugging the right target, i.e. the one for our test tab.
       return list.find(target => target.url == TEST_URI);
     },
   });
   ok(true, "CDP client has been instantiated");
 
   const firstContext = await testRuntimeEnable(client);
   const contextId = firstContext.id;
+
   await testEvaluate(client, contextId);
-  await testInvalidContextId(client, contextId);
-  await testPrimitiveTypes(client, contextId);
-  await testUnserializable(client, contextId);
-  await testObjectTypes(client, contextId);
-  await testThrowError(client, contextId);
-  await testThrowValue(client, contextId);
-  await testJSError(client, contextId);
+  await testEvaluateInvalidContextId(client, contextId);
+
+  await testCallFunctionOn(client, contextId);
+  await testCallFunctionOnInvalidContextId(client, contextId);
+
+  const { Runtime } = client;
+
+  // First test Runtime.evaluate, which accepts an JS expression string.
+  // This string may have instructions separated with `;` before ending
+  // with a JS value that is returned as a CDP `RemoteObject`.
+  function runtimeEvaluate(expression) {
+    return Runtime.evaluate({ contextId, expression });
+  }
+
+  // Then test Runtime.callFunctionOn, which accepts a JS string, but this
+  // time, it has to be a function. In this first test against callFunctionOn,
+  // we only assert the returned type and ignore the arguments.
+  function callFunctionOn(expression, instruction = false) {
+    if (instruction) {
+      return Runtime.callFunctionOn({
+        executionContextId: contextId,
+        functionDeclaration: `() => { ${expression} }`,
+      });
+    }
+    return Runtime.callFunctionOn({
+      executionContextId: contextId,
+      functionDeclaration: `() => ${expression}`,
+    });
+  }
+
+  // Finally, run another test against Runtime.callFunctionOn in order to assert
+  // the arguments being passed to the executed function.
+  async function callFunctionOnArguments(expression, instruction = false) {
+    // First evaluate the expression via Runtime.evaluate in order to generate the
+    // CDP's `RemoteObject` for the given expression. A previous test already
+    // asserted the returned value of Runtime.evaluate, so we can trust this.
+    const { result }  = await Runtime.evaluate({ contextId, expression });
+
+    // We then pass this RemoteObject as an argument to Runtime.callFunctionOn.
+    return Runtime.callFunctionOn({
+      executionContextId: contextId,
+      functionDeclaration: `arg => arg`,
+      arguments: [result],
+    });
+  }
+
+  for (const fun of [runtimeEvaluate, callFunctionOn, callFunctionOnArguments]) {
+    info("Test " + fun.name);
+    await testPrimitiveTypes(fun);
+    await testUnserializable(fun);
+    await testObjectTypes(fun);
+
+    // Tests involving an instruction (exception throwing, or errors) are not
+    // using any argument. So ignore these particular tests.
+    if (fun != callFunctionOnArguments) {
+      await testThrowError(fun);
+      await testThrowValue(fun);
+      await testJSError(fun);
+    }
+  }
 
   await client.close();
   ok(true, "The client is closed");
 
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   await RemoteAgent.close();
 });
@@ -55,61 +109,76 @@ async function testRuntimeEnable({ Runti
   ok(!!context.id, "The execution context has an id");
   ok(context.auxData.isDefault, "The execution context is the default one");
   ok(!!context.auxData.frameId, "The execution context has a frame id set");
 
   return context;
 }
 
 async function testEvaluate({ Runtime }, contextId) {
-  let { result } = await Runtime.evaluate({ contextId, expression: "location.href" });
+  const { result } = await Runtime.evaluate({ contextId, expression: "location.href" });
   is(result.value, TEST_URI, "Runtime.evaluate works and is against the test page");
 }
 
-async function testInvalidContextId({ Runtime }, contextId) {
+async function testEvaluateInvalidContextId({ Runtime }, contextId) {
   try {
     await Runtime.evaluate({ contextId: -1, expression: "" });
     ok(false, "Evaluate shouldn't pass");
   } catch (e) {
     ok(e.message.includes("Unable to find execution context with id: -1"),
       "Throws with the expected error message");
   }
 }
 
-async function testPrimitiveTypes({ Runtime }, contextId) {
+async function testCallFunctionOn({ Runtime }, executionContextId) {
+  const { result } = await Runtime.callFunctionOn({ executionContextId, functionDeclaration: "() => location.href" });
+  is(result.value, TEST_URI, "Runtime.callFunctionOn works and is against the test page");
+}
+
+async function testCallFunctionOnInvalidContextId({ Runtime }, executionContextId) {
+  try {
+    await Runtime.callFunctionOn({ executionContextId: -1, functionDeclaration: "" });
+    ok(false, "callFunctionOn shouldn't pass");
+  } catch (e) {
+    ok(e.message.includes("Unable to find execution context with id: -1"),
+      "Throws with the expected error message");
+  }
+}
+
+async function testPrimitiveTypes(testFunction) {
   const expressions = [42, "42", true, 4.2];
   for (const expression of expressions) {
-    const { result } = await Runtime.evaluate({ contextId, expression: JSON.stringify(expression) });
+    const { result } = await testFunction(JSON.stringify(expression));
     is(result.value, expression, `Evaluating primitive '${expression}' works`);
     is(result.type, typeof(expression), `${expression} type is correct`);
   }
 
   // undefined doesn't work with JSON.stringify, so test it independently
-  let { result } = await Runtime.evaluate({ contextId, expression: "undefined" });
+  let { result } = await testFunction("undefined");
   is(result.value, undefined, "undefined works");
   is(result.type, "undefined", "undefined type is correct");
 
   // `null` is special as it has its own subtype, is of type 'object' but is returned as
   // a value, without an `objectId` attribute
-  ({ result } = await Runtime.evaluate({ contextId, expression: "null" }));
+  ({ result } = await testFunction("null"));
   is(result.value, null, "Evaluating 'null' works");
   is(result.type, "object", "'null' type is correct");
   is(result.subtype, "null", "'null' subtype is correct");
   ok(!result.objectId, "'null' has no objectId");
 }
 
-async function testUnserializable({ Runtime }, contextId) {
+async function testUnserializable(testFunction) {
   const expressions = ["NaN", "-0", "Infinity", "-Infinity"];
   for (const expression of expressions) {
-    const { result } = await Runtime.evaluate({ contextId, expression });
+    const { result } = await testFunction(expression);
     is(result.unserializableValue, expression, `Evaluating unserializable '${expression}' works`);
   }
 }
 
-async function testObjectTypes({ Runtime }, contextId) {
+async function testObjectTypes(testFunction) {
   const expressions = [
     { expression: "({foo:true})", type: "object", subtype: null },
     { expression: "Symbol('foo')", type: "symbol", subtype: null },
     { expression: "BigInt(42)", type: "bigint", subtype: null },
     { expression: "new Promise(()=>{})", type: "object", subtype: "promise" },
     { expression: "new Int8Array(8)", type: "object", subtype: "typedarray" },
     { expression: "new WeakMap()", type: "object", subtype: "weakmap" },
     { expression: "new WeakSet()", type: "object", subtype: "weakset" },
@@ -117,30 +186,30 @@ async function testObjectTypes({ Runtime
     { expression: "new Set()", type: "object", subtype: "set" },
     { expression: "/foo/", type: "object", subtype: "regexp" },
     { expression: "[1, 2]", type: "object", subtype: "array" },
     { expression: "new Proxy({}, {})", type: "object", subtype: "proxy" },
     { expression: "new Date()", type: "object", subtype: "date" },
   ];
 
   for (const { expression, type, subtype } of expressions) {
-    const { result } = await Runtime.evaluate({ contextId, expression });
+    const { result } = await testFunction(expression);
     is(result.subtype, subtype, `Evaluating '${expression}' has the expected subtype`);
     is(result.type, type, "The type is correct");
     ok(!!result.objectId, "Got an object id");
   }
 }
 
-async function testThrowError({ Runtime }, contextId) {
-  const { exceptionDetails } = await Runtime.evaluate({ contextId, expression: "throw new Error('foo')" });
+async function testThrowError(testFunction) {
+  const { exceptionDetails } = await testFunction("throw new Error('foo')", true);
   is(exceptionDetails.text, "foo", "Exception message is passed to the client");
 }
 
-async function testThrowValue({ Runtime }, contextId) {
-  const { exceptionDetails } = await Runtime.evaluate({ contextId, expression: "throw 'foo'" });
+async function testThrowValue(testFunction) {
+  const { exceptionDetails } = await testFunction("throw 'foo'", true);
   is(exceptionDetails.exception.type, "string", "Exception type is correct");
   is(exceptionDetails.exception.value, "foo", "Exception value is passed as a RemoteObject");
 }
 
-async function testJSError({ Runtime }, contextId) {
-  const { exceptionDetails } = await Runtime.evaluate({ contextId, expression: "doesNotExists()" });
+async function testJSError(testFunction) {
+  const { exceptionDetails } = await testFunction("doesNotExists()", true);
   is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client");
 }
--- a/remote/test/demo.js
+++ b/remote/test/demo.js
@@ -1,17 +1,24 @@
 "use strict";
 
 const CDP = require("chrome-remote-interface");
 
 async function demo() {
   let client;
   try {
     client = await CDP();
-    const {Log, Network, Page} = client;
+    const {Log, Network, Page, Runtime} = client;
+    let { result } = await Runtime.evaluate({expression: "this.obj = {foo:true}; this.obj"});
+    console.log("1", result);
+    ({ result } = await Runtime.evaluate({expression: "this.obj"}));
+    console.log("2", result);
+    ({ result } = await Runtime.evaluate({expression: "this.obj.foo"}));
+    console.log("3", result);
+
 
     // receive console.log messages and print them
     Log.enable();
     Log.entryAdded(({entry}) => {
       const {timestamp, level, text, args} = entry;
       const msg = text || args.join(" ");
       console.log(`${new Date(timestamp)}\t${level.toUpperCase()}\t${msg}`);
     });
--- a/security/manager/ssl/StaticHPKPins.h
+++ b/security/manager/ssl/StaticHPKPins.h
@@ -699,16 +699,17 @@ static const TransportSecurityPreload kP
   { "ch.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "check.torproject.org", true, false, false, -1, &kPinset_tor },
   { "checkout.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "chfr.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "chit.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "chrome-devtools-frontend.appspot.com", true, false, false, -1, &kPinset_google_root_pems },
   { "chrome.com", true, false, false, -1, &kPinset_google_root_pems },
   { "chrome.google.com", true, false, false, -1, &kPinset_google_root_pems },
+  { "chromereporting-pa.googleapis.com", true, false, false, -1, &kPinset_google_root_pems },
   { "chromiumbugs.appspot.com", true, false, false, -1, &kPinset_google_root_pems },
   { "chromiumcodereview.appspot.com", true, false, false, -1, &kPinset_google_root_pems },
   { "cl.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "classroom.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "cloud.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "cn.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "co.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "code.facebook.com", true, false, false, -1, &kPinset_facebook },
@@ -1144,13 +1145,13 @@ static const TransportSecurityPreload kP
   { "youtu.be", true, false, false, -1, &kPinset_google_root_pems },
   { "youtube-nocookie.com", true, false, false, -1, &kPinset_google_root_pems },
   { "youtube.com", true, false, false, -1, &kPinset_google_root_pems },
   { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
   { "za.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "zh.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
 };
 
-// Pinning Preload List Length = 486;
+// Pinning Preload List Length = 487;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1565872296829000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1566217826661000);
--- a/security/manager/ssl/nsSTSPreloadList.inc
+++ b/security/manager/ssl/nsSTSPreloadList.inc
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.                */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1568291484477000);
+const PRTime gPreloadListExpirationTime = INT64_C(1568637004413000);
 %%
 0-1.party, 1
 00100010.net, 1
 0010100.net, 1
 00120012.net, 1
 00130013.net, 1
 00140014.net, 1
 00150015.net, 1
@@ -31,16 +31,17 @@ 00550055.net, 1
 005555.xyz, 1
 00660066.net, 1
 00770077.net, 1
 007kf.com, 1
 008207.com, 1
 008251.com, 1
 008253.com, 1
 008271.com, 1
+0086286.com, 1
 00880088.net, 1
 00990099.net, 1
 009p.com, 1
 00d88.com, 1
 00dani.me, 1
 00f.net, 1
 00wbf.com, 1
 01-edu.org, 1
@@ -875,17 +876,17 @@ 394553.com, 1
 394622.com, 1