Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 02 May 2017 17:28:42 -0700
changeset 356021 5eaf2d70eded608e64f459e6b255b4725ed95efe
parent 355972 bfc7b187005cabbc828ed9f5b61daf139c3cfd90 (current diff)
parent 356020 525326d5ddb15a849d0f8cd29594e88cbda34b0e (diff)
child 356054 21ce8ebf4d88fc468357f51a89d88d96a3b8ed50
push id31754
push userkwierso@gmail.com
push dateWed, 03 May 2017 00:28:51 +0000
treeherdermozilla-central@5eaf2d70eded [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.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 central, a=merge MozReview-Commit-ID: Kf2J7gzSoWd
browser/extensions/screenshots/webextension/icons/icon-highlight-19.png
browser/extensions/screenshots/webextension/icons/icon-highlight-38.png
browser/extensions/screenshots/webextension/icons/icon-starred-19.png
browser/extensions/screenshots/webextension/icons/icon-starred-38.png
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1137,18 +1137,19 @@ nsAccessibilityService::CreateAccessible
     // XBL bindings may use @role attribute to point the accessible type
     // they belong to.
     newAcc = CreateAccessibleByType(content, document);
 
     // Any XUL box can be used as tabpanel, make sure we create a proper
     // accessible for it.
     if (!newAcc && aContext->IsXULTabpanels() &&
         content->GetParent() == aContext->GetContent()) {
-      FrameType frameType = frame->Type();
-      if (frameType == FrameType::Box || frameType == FrameType::Scroll) {
+      LayoutFrameType frameType = frame->Type();
+      if (frameType == LayoutFrameType::Box ||
+          frameType == LayoutFrameType::Scroll) {
         newAcc = new XULTabpanelAccessible(content, document);
       }
     }
   }
 
   if (!newAcc) {
     if (content->IsSVGElement()) {
       SVGGeometryFrame* geometryFrame = do_QueryFrame(frame);
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -632,17 +632,17 @@ Accessible::RelativeBounds(nsIFrame** aB
     bool* hasHitRegionRect = static_cast<bool*>(mContent->GetProperty(nsGkAtoms::hitregion));
 
     if (hasHitRegionRect && mContent->IsElement()) {
       // This is for canvas fallback content
       // Find a canvas frame the found hit region is relative to.
       nsIFrame* canvasFrame = frame->GetParent();
       if (canvasFrame) {
         canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
-          canvasFrame, FrameType::HTMLCanvas);
+          canvasFrame, LayoutFrameType::HTMLCanvas);
       }
 
       // make the canvas the bounding frame
       if (canvasFrame) {
         *aBoundingFrame = canvasFrame;
         dom::HTMLCanvasElement *canvas =
           dom::HTMLCanvasElement::FromContent(canvasFrame->GetContent());
 
--- a/browser/extensions/screenshots/bootstrap.js
+++ b/browser/extensions/screenshots/bootstrap.js
@@ -1,15 +1,15 @@
 /* globals AddonManager, Components, LegacyExtensionsUtils, Services,
    XPCOMUtils */
 
 const OLD_ADDON_PREF_NAME = "extensions.jid1-NeEaf3sAHdKHPA@jetpack.deviceIdInfo";
 const OLD_ADDON_ID = "jid1-NeEaf3sAHdKHPA@jetpack";
 const ADDON_ID = "screenshots@mozilla.org";
-const TELEMETRY_ENABLED_PREF = "toolkit.telemetry.enabled";
+const TELEMETRY_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
 const PREF_BRANCH = "extensions.screenshots.";
 const USER_DISABLE_PREF = "extensions.screenshots.disabled";
 const SYSTEM_DISABLE_PREF = "extensions.screenshots.system-disabled";
 
 const { interfaces: Ci, utils: Cu } = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
@@ -17,50 +17,50 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils",
                                   "resource://gre/modules/LegacyExtensionsUtils.jsm");
 
 let addonResourceURI;
 let appStartupDone;
-const appStartupPromise = new Promise((resolve,reject) => {
+const appStartupPromise = new Promise((resolve, reject) => {
   appStartupDone = resolve;
 });
 
 const prefs = Services.prefs;
 const prefObserver = {
-  register: function() {
-    prefs.addObserver(PREF_BRANCH, this);
+  register() {
+    prefs.addObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
   },
 
-  unregister: function() {
-    prefs.removeObserver(PREF_BRANCH, this);
+  unregister() {
+    prefs.removeObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
   },
 
-  observe: function(aSubject, aTopic, aData) {
+  observe(aSubject, aTopic, aData) {
     // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
     // aData is the name of the pref that's been changed (relative to aSubject)
     if (aData == USER_DISABLE_PREF || aData == SYSTEM_DISABLE_PREF) {
       // eslint-disable-next-line promise/catch-or-return
       appStartupPromise.then(handleStartup);
     }
   }
 };
 
 const appStartupObserver = {
-  register: function() {
-    Services.obs.addObserver(this, "sessionstore-windows-restored");
+  register() {
+    Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
   },
 
-  unregister: function() {
-    Services.obs.removeObserver(this, "sessionstore-windows-restored", false);
+  unregister() {
+    Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
   },
 
-  observe: function() {
+  observe() {
     appStartupDone();
     this.unregister();
   }
 }
 
 const APP_STARTUP = 1;
 function startup(data, reason) { // eslint-disable-line no-unused-vars
   if (reason === APP_STARTUP) {
@@ -71,16 +71,23 @@ function startup(data, reason) { // esli
   prefObserver.register();
   addonResourceURI = data.resourceURI;
   // eslint-disable-next-line promise/catch-or-return
   appStartupPromise.then(handleStartup);
 }
 
 function shutdown(data, reason) { // eslint-disable-line no-unused-vars
   prefObserver.unregister();
+  const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
+    id: ADDON_ID,
+    resourceURI: addonResourceURI
+  });
+  if (webExtension.started) {
+    stop(webExtension);
+  }
 }
 
 function install(data, reason) {} // eslint-disable-line no-unused-vars
 
 function uninstall(data, reason) {} // eslint-disable-line no-unused-vars
 
 function getBoolPref(pref) {
   return prefs.getPrefType(pref) && prefs.getBoolPref(pref);
--- a/browser/extensions/screenshots/install.rdf
+++ b/browser/extensions/screenshots/install.rdf
@@ -7,14 +7,14 @@
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
         <em:minVersion>51.0a1</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
     <em:type>2</em:type>
-    <em:version>6.3.0</em:version>
+    <em:version>6.6.0</em:version>
     <em:bootstrap>true</em:bootstrap>
     <em:homepageURL>https://pageshot.net/</em:homepageURL>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
   </Description>
 </RDF>
--- a/browser/extensions/screenshots/moz.build
+++ b/browser/extensions/screenshots/moz.build
@@ -24,32 +24,40 @@ FINAL_TARGET_FILES.features['screenshots
   'webextension/randomString.js',
   'webextension/sitehelper.js'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ach"] += [
   'webextension/_locales/ach/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["az"] += [
+  'webextension/_locales/az/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["be"] += [
   'webextension/_locales/be/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["bg"] += [
   'webextension/_locales/bg/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["bn_BD"] += [
   'webextension/_locales/bn_BD/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["cs"] += [
   'webextension/_locales/cs/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["cy"] += [
+  'webextension/_locales/cy/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["de"] += [
   'webextension/_locales/de/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["dsb"] += [
   'webextension/_locales/dsb/messages.json'
 ]
 
@@ -84,32 +92,40 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["et"] += [
   'webextension/_locales/et/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["fa"] += [
   'webextension/_locales/fa/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["fi"] += [
+  'webextension/_locales/fi/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["fr"] += [
   'webextension/_locales/fr/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["fy_NL"] += [
   'webextension/_locales/fy_NL/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["gu_IN"] += [
   'webextension/_locales/gu_IN/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["he"] += [
   'webextension/_locales/he/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["hi_IN"] += [
+  'webextension/_locales/hi_IN/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["hsb"] += [
   'webextension/_locales/hsb/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["hu"] += [
   'webextension/_locales/hu/messages.json'
 ]
 
@@ -132,16 +148,20 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["kab"] += [
   'webextension/_locales/kab/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["kk"] += [
   'webextension/_locales/kk/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["km"] += [
+  'webextension/_locales/km/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ko"] += [
   'webextension/_locales/ko/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["lij"] += [
   'webextension/_locales/lij/messages.json'
 ]
 
@@ -152,16 +172,20 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["lt"] += [
   'webextension/_locales/lt/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ms"] += [
   'webextension/_locales/ms/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["my"] += [
+  'webextension/_locales/my/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["nb_NO"] += [
   'webextension/_locales/nb_NO/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["nl"] += [
   'webextension/_locales/nl/messages.json'
 ]
 
@@ -184,16 +208,20 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["pt_PT"] += [
   'webextension/_locales/pt_PT/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["rm"] += [
   'webextension/_locales/rm/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ro"] += [
+  'webextension/_locales/ro/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ru"] += [
   'webextension/_locales/ru/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["sk"] += [
   'webextension/_locales/sk/messages.json'
 ]
 
@@ -208,16 +236,20 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["sr"] += [
   'webextension/_locales/sr/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["sv_SE"] += [
   'webextension/_locales/sv_SE/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["te"] += [
+  'webextension/_locales/te/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["th"] += [
   'webextension/_locales/th/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["tl"] += [
   'webextension/_locales/tl/messages.json'
 ]
 
@@ -228,16 +260,20 @@ FINAL_TARGET_FILES.features['screenshots
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["uk"] += [
   'webextension/_locales/uk/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ur"] += [
   'webextension/_locales/ur/messages.json'
 ]
 
+FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["uz"] += [
+  'webextension/_locales/uz/messages.json'
+]
+
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["zh_CN"] += [
   'webextension/_locales/zh_CN/messages.json'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["zh_TW"] += [
   'webextension/_locales/zh_TW/messages.json'
 ]
 
@@ -257,33 +293,35 @@ FINAL_TARGET_FILES.features['screenshots
   'webextension/build/inlineSelectionCss.js',
   'webextension/build/onboardingCss.js',
   'webextension/build/onboardingHtml.js',
   'webextension/build/raven.js',
   'webextension/build/shot.js'
 ]
 
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["icons"] += [
+  'webextension/icons/back-highlight.svg',
   'webextension/icons/back.svg',
   'webextension/icons/cancel.svg',
   'webextension/icons/copy.png',
   'webextension/icons/done.svg',
   'webextension/icons/download.svg',
   'webextension/icons/icon-128.png',
   'webextension/icons/icon-16.png',
+  'webextension/icons/icon-16.svg',
   'webextension/icons/icon-19.png',
   'webextension/icons/icon-256.png',
   'webextension/icons/icon-32.png',
+  'webextension/icons/icon-32.svg',
   'webextension/icons/icon-38.png',
   'webextension/icons/icon-48.png',
   'webextension/icons/icon-64.png',
-  'webextension/icons/icon-highlight-19.png',
-  'webextension/icons/icon-highlight-38.png',
-  'webextension/icons/icon-starred-19.png',
-  'webextension/icons/icon-starred-38.png',
+  'webextension/icons/icon-highlight-32.svg',
+  'webextension/icons/icon-starred-32.svg',
+  'webextension/icons/icon-welcome-face-without-eyes.svg',
   'webextension/icons/menu-fullpage.svg',
   'webextension/icons/menu-myshot.svg',
   'webextension/icons/menu-visible.svg',
   'webextension/icons/onboarding-1.png',
   'webextension/icons/onboarding-2.png',
   'webextension/icons/onboarding-3.png',
   'webextension/icons/onboarding-4.png'
 ]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+}
--- a/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js
+++ b/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js
@@ -1,20 +1,21 @@
 "use strict";
 
-/* global add_task, is, promiseScreenshotsEnabled, promiseScreenshotsReset,
-   registerCleanupFunction */
-
 function checkElements(expectPresent, l) {
   for (let id of l) {
     is(!!document.getElementById(id), expectPresent, "element " + id + (expectPresent ? " is" : " is not") + " present");
   }
 }
 
 add_task(function*() {
   yield promiseScreenshotsEnabled();
 
   registerCleanupFunction(function* () {
     yield promiseScreenshotsReset();
   });
 
+  yield BrowserTestUtils.waitForCondition(
+    () => document.getElementById("screenshots_mozilla_org-browser-action"),
+    "Screenshots button should be present", 100, 100);
+
   checkElements(true, ["screenshots_mozilla_org-browser-action"]);
 });
--- a/browser/extensions/screenshots/webextension/_locales/ach/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ach/messages.json
@@ -98,18 +98,18 @@
     "message": "Cal malubo"
   },
   "tourPrevious": {
     "message": "Cal mukato"
   },
   "tourDone": {
     "message": "Otum"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Tic ki Firefox Screenshots nyuto ni, i yee $TERMSANDPRIVACYNOTICETERMSLINK$ ki $TERMSANDPRIVACYNOTICEPRIVACYLINK$ me Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Tic ki Firefox Screenshots nyuto, ni i yee $TERMSANDPRIVACYNOTICETERMSLINK$ ki $TERMSANDPRIVACYNOTICEPRIVACYLINK$ me tic me Cloud pa Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/az/messages.json
@@ -0,0 +1,20 @@
+{
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "Saxla"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "Görünən ərazini saxla"
+  },
+  "cancelScreenshot": {
+    "message": "Ləğv et"
+  },
+  "downloadScreenshot": {
+    "message": "Endir"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "Keçid köçürüldü"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/be/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/be/messages.json
@@ -98,18 +98,18 @@
     "message": "Наступны слайд"
   },
   "tourPrevious": {
     "message": "Папярэдні слайд"
   },
   "tourDone": {
     "message": "Гатова"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Выкарыстоўваючы Firefox Screenshots, вы згаджаецеся з яго $TERMSANDPRIVACYNOTICETERMSLINK$ і $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Выкарыстоўваючы Firefox Screenshots, вы згаджаецеся з $TERMSANDPRIVACYNOTICETERMSLINK$ і $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/bg/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/bg/messages.json
@@ -98,26 +98,26 @@
     "message": "Напред"
   },
   "tourPrevious": {
     "message": "Назад"
   },
   "tourDone": {
     "message": "Готово"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Използвайки Firefox Screenshots вие се съгласявате с тези $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Използвайки Firefox Screenshots вие се съгласявате с $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$ на облачните услуги на Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
   },
   "termsAndPrivacyNoticeTermsLink": {
-    "message": "Условия"
+    "message": "Условията"
   },
   "termsAndPrivacyNoticyPrivacyLink": {
-    "message": "Политика на поверителност"
+    "message": "Политиката на поверителност"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/bn_BD/messages.json
@@ -98,26 +98,15 @@
     "message": "পরবর্তী স্লাইড"
   },
   "tourPrevious": {
     "message": "পূর্ববর্তী স্লাইড"
   },
   "tourDone": {
     "message": "সম্পন্ন"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox Screenshots ব্যবহারের জন্য, আপনি স্ক্রিনশটের $TERMSANDPRIVACYNOTICETERMSLINK$ এবং $TERMSANDPRIVACYNOTICEPRIVACYLINK$ নীতিতে আগ্রহী।",
-    "placeholders": {
-      "termsandprivacynoticetermslink": {
-        "content": "$1"
-      },
-      "termsandprivacynoticeprivacylink": {
-        "content": "$2"
-      }
-    }
-  },
   "termsAndPrivacyNoticeTermsLink": {
     "message": "শর্তাবলী"
   },
   "termsAndPrivacyNoticyPrivacyLink": {
     "message": "গোপনীয়তা নীতি"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/cs/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/cs/messages.json
@@ -98,18 +98,18 @@
     "message": "Další snímek"
   },
   "tourPrevious": {
     "message": "Předchozí snímek"
   },
   "tourDone": {
     "message": "Hotovo"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Používáním služby Firefox Screenshots souhlasíte s jejími $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Používáním služby Firefox Screenshots souhlasíte s $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/cy/messages.json
@@ -0,0 +1,123 @@
+{
+  "addonDescription": {
+    "message": "Cymrwch clipiau a lluniau sgrin o'r We a'u cadw dros dro neu'n barhaol."
+  },
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "Cymryd Llun Sgrin Screenshot"
+  },
+  "myShotsLink": {
+    "message": "Fy Lluniau Sgrin"
+  },
+  "screenshotInstructions": {
+    "message": "Llusgwch neu glicio ar y dudalen i ddewis adran. Pwyso ESC i ddiddymu."
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "Cadw"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "Cadw'r gweladwy"
+  },
+  "saveScreenshotFullPage": {
+    "message": "Cadw tudalen lawn"
+  },
+  "cancelScreenshot": {
+    "message": "Diddymu"
+  },
+  "downloadScreenshot": {
+    "message": "Llwytho i Lawr"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "Dolen wedi ei Chadw"
+  },
+  "notificationLinkCopiedDetails": {
+    "message": "Mae'r ddolen i'ch llun wedi ei gopïo i'r clipfwrdd. Pwyswch $META_KEY$-V i'w ludo.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
+  "requestErrorTitle": {
+    "message": "Ddim yn gweithio."
+  },
+  "requestErrorDetails": {
+    "message": "Ymddiheuriadau! Nid oedd modd cadw eich llun. Ceisiwch eto'n hwyrach."
+  },
+  "connectionErrorTitle": {
+    "message": "Nid oes modd i ni gysylltu a'ch lluniau sgrin."
+  },
+  "connectionErrorDetails": {
+    "message": "Gwiriwch eich cysylltiad Rhyngrwyd. Os ydych yn gallu cysylltu â'r Rhyngrwyd, efallai bod anhawster dros dro gyda gwasanaeth lluniau sgrin, Firefox Screnshots."
+  },
+  "loginErrorDetails": {
+    "message": "Nid oedd modd i ni gadw eich llun gan fod yna anhawster gyda gwasanaeth Firefox Screenshots. Ceisiwch eto'n hwyrach."
+  },
+  "unshootablePageErrorTitle": {
+    "message": "Nid oes modd tynnu llun sgrin o'r dudalen."
+  },
+  "unshootablePageErrorDetails": {
+    "message": "Nid yw hwn yn dudalen Gwe safonol, felly does dim modd tynnu llun sgrin ohono."
+  },
+  "selfScreenshotErrorTitle": {
+    "message": "Nid oes modd cymryd llun o dudalen lluniau sgrin Firefox Screenshots!"
+  },
+  "genericErrorTitle": {
+    "message": "Www! Mae Firefox Screenshots wedi mynd yn hurt."
+  },
+  "genericErrorDetails": {
+    "message": "Nid ydym yn gwybod beth sydd wedi ddigwydd. Ceisiwch eto neu dynnu llun o dudalen wahanol?"
+  },
+  "tourBodyOne": {
+    "message": "Cymryd, cadw a rhannu lluniau sgrin heb adael Firefox."
+  },
+  "tourHeaderTwo": {
+    "message": "Cipio Dim ond Beth Rydych ei Angen"
+  },
+  "tourBodyTwo": {
+    "message": "Cliciwch a llusgo i gipio rhan o dudalen. Gallwch hofran i amlygu eich dewis."
+  },
+  "tourHeaderThree": {
+    "message": "Yn Ôl eich Dewis"
+  },
+  "tourBodyThree": {
+    "message": "Cadwch eich lluniau wedi eu golygu i'r We ar gyfer rhannu haws, neu eu llwytho i lawr i'ch cyfrifiadur. Gallwch hefyd glicio ar Fy Lluniau i weld pob llun sydd gennych."
+  },
+  "tourHeaderFour": {
+    "message": "Cipio Ffenestri neu Dudalennau Cyfan"
+  },
+  "tourBodyFour": {
+    "message": "Dewiswch y botymau ar y dde uchod i gipio ardal gweladwy mewn ffenestr neu i gipio tudalen gyfan."
+  },
+  "tourSkip": {
+    "message": "SKIP"
+  },
+  "tourNext": {
+    "message": "Sleid Nesaf"
+  },
+  "tourPrevious": {
+    "message": "Sleid Flaenorol"
+  },
+  "tourDone": {
+    "message": "Gorffen"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Drwy ddefnyddio Firefox Screenshots, rydych yn cytuno i Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Telerau"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Hysbysiad Preifatrwydd"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/de/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/de/messages.json
@@ -98,18 +98,18 @@
     "message": "Nächste Folie"
   },
   "tourPrevious": {
     "message": "Vorherige Folie"
   },
   "tourDone": {
     "message": "Fertig"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Durch die Verwendung von Firefox Screenshots stimmen Sie den entsprechenden $TERMSANDPRIVACYNOTICETERMSLINK$ und dem $TERMSANDPRIVACYNOTICEPRIVACYLINK$ zu.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Durch die Verwendung von Firefox Screenshots stimmen Sie den $TERMSANDPRIVACYNOTICETERMSLINK$ und dem $TERMSANDPRIVACYNOTICEPRIVACYLINK$ von Firefox Cloud Services zu.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/dsb/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/dsb/messages.json
@@ -98,18 +98,18 @@
     "message": "Pśiduce foto"
   },
   "tourPrevious": {
     "message": "Pjerwjejšne foto"
   },
   "tourDone": {
     "message": "Gótowo"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Pśez wužywanje Firefox ScreenShots, zwolijośo do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Pśez wužywanje Firefox ScreenShots, zwolijośo do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/el/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/el/messages.json
@@ -98,18 +98,18 @@
     "message": "Επόμενη διαφάνεια"
   },
   "tourPrevious": {
     "message": "Προηγούμενη διαφάνεια"
   },
   "tourDone": {
     "message": "Τέλος"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Χρησιμοποιώντας το Firefox Screenshots, συμφωνείτε με τους $TERMSANDPRIVACYNOTICETERMSLINK$ Στιγμιότυπων και τη $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Χρησιμοποιώντας το Firefox Screenshots, συμφωνείτε με τους $TERMSANDPRIVACYNOTICETERMSLINK$ και την $TERMSANDPRIVACYNOTICEPRIVACYLINK$ των Υπηρεσιών Cloud του Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/en_US/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/en_US/messages.json
@@ -98,18 +98,18 @@
     "message": "Next Slide"
   },
   "tourPrevious": {
     "message": "Previous Slide"
   },
   "tourDone": {
     "message": "Done"
   },
-  "termsAndPrivacyNotice": {
-    "message": "By using Firefox Screenshots, you agree to the Screenshots $TERMSANDPRIVACYNOTICETERMSLINK$ and $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "By using Firefox Screenshots, you agree to the Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ and $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/es_AR/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/es_AR/messages.json
@@ -98,18 +98,18 @@
     "message": "Próxima diapositiva"
   },
   "tourPrevious": {
     "message": "Diapositiva anterior"
   },
   "tourDone": {
     "message": "Listo"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Al usar Firefox Screenshots, aceptás los $TERMSANDPRIVACYNOTICETERMSLINK$ y $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "AL usar Firefox Screenshots, aceptás los $TERMSANDPRIVACYNOTICETERMSLINK$ y $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de los servicios en la nube de Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/es_CL/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/es_CL/messages.json
@@ -98,18 +98,18 @@
     "message": "Siguiente diapositiva"
   },
   "tourPrevious": {
     "message": "Diapositiva anterior"
   },
   "tourDone": {
     "message": "Hecho"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/es_ES/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/es_ES/messages.json
@@ -98,18 +98,18 @@
     "message": "Diapositiva siguiente"
   },
   "tourPrevious": {
     "message": "Diapositiva anterior"
   },
   "tourDone": {
     "message": "Hecho"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Al usar Firefox Screenshots, aceptas los $TERMSANDPRIVACYNOTICETERMSLINK$ y el $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/es_MX/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/es_MX/messages.json
@@ -98,18 +98,18 @@
     "message": "Siguiente diapositiva"
   },
   "tourPrevious": {
     "message": "Diapositiva anterior"
   },
   "tourDone": {
     "message": "Terminado"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Al usar Firefox Screenshots, estás de acuerdo con los $TERMSANDPRIVACYNOTICETERMSLINK$ y con el $TERMSANDPRIVACYNOTICETERMSLINK$ de Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Al usar Firefox Screenshots, estás de acuerdo con los servicios de Firefox Cloud $TERMSANDPRIVACYNOTICETERMSLINK$ y $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/et/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/et/messages.json
@@ -98,18 +98,18 @@
     "message": "Järgmine slaid"
   },
   "tourPrevious": {
     "message": "Eelmine slaid"
   },
   "tourDone": {
     "message": "Valmis"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox Screenshots kasutamisel nõustud Screenshots $TERMSANDPRIVACYNOTICETERMSLINK$ ja $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox Screenshots kasutamisel nõustud Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ ja $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/fa/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/fa/messages.json
@@ -98,18 +98,18 @@
     "message": "اسلاید بعدی"
   },
   "tourPrevious": {
     "message": "اسلاید قبلی"
   },
   "tourDone": {
     "message": "انجام شد"
   },
-  "termsAndPrivacyNotice": {
-    "message": "با استفاده از سرویسِ تصاویرِ صفحهٔ فایرفاکس، شما با $TERMSANDPRIVACYNOTICETERMSLINK$ و $TERMSANDPRIVACYNOTICEPRIVACYLINK$ موافقت می‌کنید.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "با استفاده از سرویس تصاویرِ صفحه فایرفاکس، شما با شرایط سرویس‌های ابری فایرفاکس $TERMSANDPRIVACYNOTICETERMSLINK$ و $TERMSANDPRIVACYNOTICEPRIVACYLINK$ موافقت می‌کنید.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/fi/messages.json
@@ -0,0 +1,23 @@
+{
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "Tallenna"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "Tallenna näkyvä osuus"
+  },
+  "saveScreenshotFullPage": {
+    "message": "Tallenna koko sivu"
+  },
+  "cancelScreenshot": {
+    "message": "Peruuta"
+  },
+  "downloadScreenshot": {
+    "message": "Lataa"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "Linkki kopioitu"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/fr/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/fr/messages.json
@@ -60,17 +60,17 @@
   },
   "unshootablePageErrorDetails": {
     "message": "Impossible d’effectuer une capture d’écran, car cette page web n’est pas standard."
   },
   "selfScreenshotErrorTitle": {
     "message": "Vous ne pouvez pas effectuer une capture d’écran d’une page Firefox Screenshots."
   },
   "genericErrorTitle": {
-    "message": "Firefox Screenshots semble avoir un petit problème."
+    "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."
   },
   "tourBodyOne": {
     "message": "Effectuez des captures d’écran, enregistrez et partagez-les sans quitter Firefox."
   },
   "tourHeaderTwo": {
@@ -98,18 +98,18 @@
     "message": "Écran suivant"
   },
   "tourPrevious": {
     "message": "Écran précédent"
   },
   "tourDone": {
     "message": "Terminé"
   },
-  "termsAndPrivacyNotice": {
-    "message": "En utilisant Firefox Screenshots, vous acceptez les $TERMSANDPRIVACYNOTICETERMSLINK$ et la $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "En utilisant Firefox Screenshots, vous acceptez les $TERMSANDPRIVACYNOTICETERMSLINK$ et la $TERMSANDPRIVACYNOTICEPRIVACYLINK$ des services en ligne de Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/fy_NL/messages.json
@@ -98,18 +98,18 @@
     "message": "Folgjende ôfbylding"
   },
   "tourPrevious": {
     "message": "Foarige ôfbylding"
   },
   "tourDone": {
     "message": "Dien"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Troch Firefox Screenshots te brûken, gean jo akkoard mei de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ fan Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Troch Firefox Screenshots te brûken, geane jo akkoard mei de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ fan Firefox-cloudtsjinsten.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/gu_IN/messages.json
@@ -98,18 +98,18 @@
     "message": "આગલી સ્લાઇડ"
   },
   "tourPrevious": {
     "message": "પહેલાની સ્લાઇડ"
   },
   "tourDone": {
     "message": "થઈ ગયું"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox સ્ક્રીનશોટ્સ વાપરીને, તમે સ્ક્રીનશૉટ્સ થી સંમત છો $TERMSANDPRIVACYNOTICETERMSLINK$ અને $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox સ્ક્રીનશોટ્સ વાપરીને, તમે Firefox Cloud સેવાઓ સાથે સંમત થાઓ છો $TERMSANDPRIVACYNOTICETERMSLINK$ અને $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/he/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/he/messages.json
@@ -85,10 +85,39 @@
   "tourBodyThree": {
     "message": "שמירת הצילומים החתוכים שלך לאחסון מקוון לצורך שיתוף פשוט יותר, או להוריד אותם למחשב שלך. ניתן גם ללחוץ על כפתור הצילומים שלי כדי למצוא את כל הצילומים שצילמת."
   },
   "tourHeaderFour": {
     "message": "לצלם חלונות או דפים שלמים"
   },
   "tourBodyFour": {
     "message": "נא לבחור בכפתורים שבחלק העליון כדי לצלם את האזור הגלוי בחלון או לצלם את הדף כולו."
+  },
+  "tourSkip": {
+    "message": "דילוג"
+  },
+  "tourNext": {
+    "message": "השקופית הבאה"
+  },
+  "tourPrevious": {
+    "message": "השקופית הקודמת"
+  },
+  "tourDone": {
+    "message": "סיום"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "מעצם השימוש ב־Firefox Screenshots הכללים של שירותי הענן של Firefox‏ $TERMSANDPRIVACYNOTICETERMSLINK$ ו$TERMSANDPRIVACYNOTICEPRIVACYLINK$ מוסכמים עליך.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "תנאים"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "הצהרת פרטיות"
   }
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/hi_IN/messages.json
@@ -0,0 +1,123 @@
+{
+  "addonDescription": {
+    "message": "वेब से फ़ोटो और स्क्रीनशॉट लें और उन्हें अस्थायी या स्थायी रूप से सहेजें."
+  },
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "स्क्रीनशॉट लें"
+  },
+  "myShotsLink": {
+    "message": "मेरे चित्र"
+  },
+  "screenshotInstructions": {
+    "message": "किसी क्षेत्र को चुनने के लिए पृष्ठ पर खींचें या क्लिक करें. रद्द करने के लिए ESC दबाएँ."
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "सहेजें"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "दृश्यमान सहेजें"
+  },
+  "saveScreenshotFullPage": {
+    "message": "पूर्ण पृष्ठ सहेजें"
+  },
+  "cancelScreenshot": {
+    "message": "रद्द करें"
+  },
+  "downloadScreenshot": {
+    "message": "डाउनलोड करें"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "लिंक की नक़ल की गयी"
+  },
+  "notificationLinkCopiedDetails": {
+    "message": "आपके शॉट के लिंक क्लिपबोर्ड पर कॉपी किए गए हैं. पेस्ट करने के लिए $META_KEY$-V दबाएँ.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
+  "requestErrorTitle": {
+    "message": "कार्यरत नहीं है."
+  },
+  "requestErrorDetails": {
+    "message": "क्षमा करें! हम आपके शॉट को सहेज़ नहीं सके. कृपया बाद में पुन: प्रयास करें."
+  },
+  "connectionErrorTitle": {
+    "message": "हम आपके स्क्रीनशॉट से जुड़ नहीं सकते हैं."
+  },
+  "connectionErrorDetails": {
+    "message": "कृपया अपने इंटरनेट संपर्क की जाँच करें. यदि आप इंटरनेट से जुड़ने में सक्षम हैं, तो Firefox स्क्रीनशॉट सेवा के साथ एक अस्थायी समस्या हो सकती है."
+  },
+  "loginErrorDetails": {
+    "message": "हम आपका शॉट सहेज नहीं सके क्योंकि Firefox स्क्रीनशॉट सेवा में कोई समस्या है. कृपया बाद में पुन: प्रयास करें."
+  },
+  "unshootablePageErrorTitle": {
+    "message": "हम इस पृष्ठ का स्क्रीनशॉट नहीं ले सकते."
+  },
+  "unshootablePageErrorDetails": {
+    "message": "यह एक मानक वेब पेज नहीं है, इसलिए आप इसका स्क्रीनशॉट नहीं ले सकते."
+  },
+  "selfScreenshotErrorTitle": {
+    "message": "आप एक Firefox स्क्रीनशॉट पृष्ठ का शॉट नहीं ले सकते!"
+  },
+  "genericErrorTitle": {
+    "message": "ओह! Firefox स्क्रीनशॉट बिगड़ गया."
+  },
+  "genericErrorDetails": {
+    "message": "हम सुनिश्चित नहीं हैं कि अभी क्या हुआ. पुन: प्रयास या एक भिन्न पृष्ठ का एक शॉट लेना चाहते हैं?"
+  },
+  "tourBodyOne": {
+    "message": "Firefox छोड़े बिना स्क्रीनशॉट लें, सहेजें, और साझा करें."
+  },
+  "tourHeaderTwo": {
+    "message": "जो आप चाहते हैं उसे कैद करें"
+  },
+  "tourBodyTwo": {
+    "message": "पृष्ठ के बस एक हिस्से को कैद करने के लिए क्लिक करें और खींचें. आप अपने चयन को हाइलाइट करने के लिए भी जा सकते हैं."
+  },
+  "tourHeaderThree": {
+    "message": "जैसा आप इसे चाहते हैं"
+  },
+  "tourBodyThree": {
+    "message": "आसानी से साझा करने या उन्हें अपने कंप्यूटर पर डाउनलोड करने के लिए अपने क्रॉप किये गये शॉट को वेब पर सहेजें. आपके द्वारा लिए गये सभी शॉट्स को ढूंढने के लिए आप मेरे शॉट्स बटन पर भी क्लिक कर सकते हैं."
+  },
+  "tourHeaderFour": {
+    "message": "विंडोज़ या संपूर्ण पृष्ठों को कैद करें"
+  },
+  "tourBodyFour": {
+    "message": "विंडो में दिखाई देने वाले क्षेत्र या एक पूरे पृष्ठ को कैद करने के लिए ऊपर में दाहिनी तरफ़ के बटन का चयन करें."
+  },
+  "tourSkip": {
+    "message": "SKIP"
+  },
+  "tourNext": {
+    "message": "अगली स्लाइड"
+  },
+  "tourPrevious": {
+    "message": "पिछली स्लाइड"
+  },
+  "tourDone": {
+    "message": "पूर्ण"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox स्क्रीनशॉट का उपयोग करके, आप Firefox क्लाउड सेवाओं $TERMSANDPRIVACYNOTICETERMSLINK$ और $TERMSANDPRIVACYNOTICEPRIVACYLINK$ के लिए सहमत हैं.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "शर्तें"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "गोपनीयता सूचना"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/hsb/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/hsb/messages.json
@@ -98,18 +98,18 @@
     "message": "Přichodne foto"
   },
   "tourPrevious": {
     "message": "Předchadne foto"
   },
   "tourDone": {
     "message": "Hotowo"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Přez wužiwanje Firefox ScreenShots, zwoliće do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Přez wužiwanje Firefox ScreenShots, zwoliće do $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/hu/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/hu/messages.json
@@ -98,18 +98,18 @@
     "message": "Következő dia"
   },
   "tourPrevious": {
     "message": "Előző dia"
   },
   "tourDone": {
     "message": "Kész"
   },
-  "termsAndPrivacyNotice": {
-    "message": "A Firefox képernyőképek használatával, Ön beleegyezik a képernyőképek $TERMSANDPRIVACYNOTICETERMSLINK$ és $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "A Firefox képernyőképek használatával beleegyezik a Firefox felhőszolgáltatások $TERMSANDPRIVACYNOTICETERMSLINK$ és $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/hy_AM/messages.json
@@ -97,10 +97,27 @@
   "tourNext": {
     "message": "Հաջորդ սահիկը"
   },
   "tourPrevious": {
     "message": "Նախորդ սահիկը"
   },
   "tourDone": {
     "message": "Պատրաստ է"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Օգտագործելով Firefox Screenshots-ը՝ դուք ընդունում եք Firefox Cloud ծառայությունների $TERMSANDPRIVACYNOTICETERMSLINK$ը և $TERMSANDPRIVACYNOTICEPRIVACYLINK$ը:",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Պայմաններ"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Գաղտնիության ծանուցում"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/id/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/id/messages.json
@@ -98,18 +98,18 @@
     "message": "Salindia Selanjutnya"
   },
   "tourPrevious": {
     "message": "Salindia Sebelumnya"
   },
   "tourDone": {
     "message": "Selesai"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Dengan menggunakan Firefox Screenshots, Anda setuju dengan $TERMSANDPRIVACYNOTICETERMSLINK$ dan $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Dengan menggunakan Firefox Screenshots, Anda setuju dengan $TERMSANDPRIVACYNOTICETERMSLINK$ dan $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/it/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/it/messages.json
@@ -98,18 +98,18 @@
     "message": "Schermata successiva"
   },
   "tourPrevious": {
     "message": "Schermata precedente"
   },
   "tourDone": {
     "message": "Fine"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Utilizzando Firefox Screenshots si accettano le $TERMSANDPRIVACYNOTICETERMSLINK$ e l’$TERMSANDPRIVACYNOTICEPRIVACYLINK$ del servizio.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Utilizzando Firefox Screenshots si accettano le $TERMSANDPRIVACYNOTICETERMSLINK$ e l’$TERMSANDPRIVACYNOTICEPRIVACYLINK$ di Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/ja/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ja/messages.json
@@ -98,18 +98,18 @@
     "message": "次のスライド"
   },
   "tourPrevious": {
     "message": "前のスライド"
   },
   "tourDone": {
     "message": "完了"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox Screenshots を使うことで、あなたは Screenshots の $TERMSANDPRIVACYNOTICETERMSLINK$ と $TERMSANDPRIVACYNOTICEPRIVACYLINK$ に同意したことになります。",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox Screenshots を使うことで、あなたは Firefox Cloud Services の $TERMSANDPRIVACYNOTICETERMSLINK$ と $TERMSANDPRIVACYNOTICEPRIVACYLINK$ に同意したことになります。",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/kab/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/kab/messages.json
@@ -1,11 +1,11 @@
 {
   "addonDescription": {
-    "message": "Ṭṭef imrayen akked igdilen si Web sakin sekles-iten s wudem askudan neɣ s wudem yezgan."
+    "message": "Ṭṭef imrayen akked igdilen si Web sakin sekles-iten s wudem askudan neγ s wudem yezgan."
   },
   "addonAuthorsList": {
     "message": "Mozilla <screenshots-feedback@mozilla.org>"
   },
   "contextMenuLabel": {
     "message": "Ṭṭef agdil"
   },
   "myShotsLink": {
@@ -98,18 +98,18 @@
     "message": "Tigri n zdat"
   },
   "tourPrevious": {
     "message": "Tigri n deffir"
   },
   "tourDone": {
     "message": "Immed"
   },
-  "termsAndPrivacyNotice": {
-    "message": "S useqdec Firefox Screenshots,  ad tqebleḍ $TERMSANDPRIVACYNOTICETERMSLINK$ n Screenshots akked $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "S useqdec n Firefox Screenshots, ad tqebleḍ tiwuriwin n usigna Firefox $TERMSANDPRIVACYNOTICETERMSLINK$ akked $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/kk/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/kk/messages.json
@@ -44,42 +44,80 @@
     "message": "Жұмыс істемейді."
   },
   "requestErrorDetails": {
     "message": "Кешіріңіз! Сіздің скриншотыңызды сақтай алмадық. Кейінірек қайталап көріңіз."
   },
   "connectionErrorTitle": {
     "message": "Скриншоттарыңызға байланыса алмадық."
   },
+  "connectionErrorDetails": {
+    "message": "Интернетпен байланысыңызды тексеріңіз. Егер сізде интернетпен байланыс бар болса, онда Firefox скриншоттары қызметімен уақытша мәселелер болуы мүмкін."
+  },
+  "loginErrorDetails": {
+    "message": "Скриншотыңызды сақтай алмадық, өйткені Firefox скриншоттары қызметімен мәселе бар болып тұр. Кейінірек қайталап көріңіз."
+  },
   "unshootablePageErrorTitle": {
     "message": "Бұл беттің скриншотын түсіре алмаймыз."
   },
   "unshootablePageErrorDetails": {
     "message": "Бұл қалыпты веб беті емес, сондықтан оның скриншотын түсіру мүмкін емес."
   },
   "selfScreenshotErrorTitle": {
     "message": "Firefox скриншоттары бетінің скриншотын түсіру мүмкін емес!"
   },
   "genericErrorTitle": {
     "message": "Қап! Firefox скриншоттары жасамай қалған сияқты."
   },
+  "genericErrorDetails": {
+    "message": "Не болғанын білмейміз. Қайталап көресіз бе, немесе басқа парақтың скриншотын түсіріп көресіз бе?"
+  },
   "tourBodyOne": {
     "message": "Firefox ішінен скриншоттарды түсіру, сақтау және олармен бөлісу."
   },
   "tourHeaderTwo": {
     "message": "Тек керек нәрсені түсіріңіз"
   },
+  "tourBodyTwo": {
+    "message": "Беттің тек бір бөлігін түсіру үшін тышқанды шертіп, тартыңыз. Таңдауыңызды түспен ерекшелеу үшін үстінен өткізсеңіз болады."
+  },
   "tourHeaderThree": {
     "message": "Өзіңізге керек түрде"
   },
+  "tourBodyThree": {
+    "message": "Қиылған скриншоттарыңыздбен оңай бөлісу үшін оларды интернетте сақтаңыз, немесе өз компьютеріңізге жүктеп алыңыз. Сонымен қатар, сіз жасаған барлық скриншоттарды табу үшін Менің скриншоттарым батырмасына шерте аласыз."
+  },
+  "tourHeaderFour": {
+    "message": "Терезелер немесе толық беттерді түсіріңіз"
+  },
+  "tourBodyFour": {
+    "message": "Жоғарғы оң жақта орналасқан батырмаларды терезенің көрінетін аймағын, немесе толық бетті түсіру үшін қолданыңыз."
+  },
   "tourSkip": {
     "message": "Аттап кету"
   },
   "tourNext": {
     "message": "Келесі слайд"
   },
   "tourPrevious": {
     "message": "Алдыңғы слайд"
   },
   "tourDone": {
     "message": "Дайын"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox скриншоттарын қолдану арқылы, сіз Firefox бұлттық қызметтерінің $TERMSANDPRIVACYNOTICETERMSLINK$ және $TERMSANDPRIVACYNOTICEPRIVACYLINK$ келісесіз.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Қолдану шарттары"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Жекелік ескертуі"
   }
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/km/messages.json
@@ -0,0 +1,5 @@
+{
+  "saveScreenshotSelectedArea": {
+    "message": "រក្សា​ទុក"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/ko/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ko/messages.json
@@ -98,18 +98,18 @@
     "message": "다음 슬라이드"
   },
   "tourPrevious": {
     "message": "이전 슬라이드"
   },
   "tourDone": {
     "message": "완료"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox Screenshots를 사용하므로써, $TERMSANDPRIVACYNOTICETERMSLINK$와 $TERMSANDPRIVACYNOTICEPRIVACYLINK$에 동의하게 됩니다.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox Screenshots을 사용함으로써, Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$과 $TERMSANDPRIVACYNOTICEPRIVACYLINK$에 동의하게 됩니다.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/lij/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/lij/messages.json
@@ -97,10 +97,27 @@
   "tourNext": {
     "message": "Pròscima schermâ"
   },
   "tourPrevious": {
     "message": "Schermâ de primma"
   },
   "tourDone": {
     "message": "Fæto"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Se ti deuvi Firefox Screenshots, ti e d'acordio con $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$ de Firefox Cloud Services.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Termini"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Informativa in sciâ privacy"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/lo/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/lo/messages.json
@@ -1,12 +1,24 @@
 {
+  "addonDescription": {
+    "message": "ຖ່າຍຄຣິບ ແລະ ພາບຫນ້າຈໍຈາກຫນ້າເວັບ ແລ້ວບັນທຶກໄວ້ຊົ່ວຄາວ ຫລື ຖາວອນ."
+  },
   "addonAuthorsList": {
     "message": "Mozilla <screenshots-feedback@mozilla.org>"
   },
+  "contextMenuLabel": {
+    "message": "ຖ່າຍພາບຫນ້າຈໍ"
+  },
+  "myShotsLink": {
+    "message": "ພາບຂອງຂ້ອຍ"
+  },
+  "screenshotInstructions": {
+    "message": "ລາກ ຫລື ຄິກໃສ່ຫນ້າເວັບເພື່ອເລືອກເອົາບ່ອນທີ່ຕ້ອງການ. ກົດ ESC ເພື່ອຍົກເລີກ."
+  },
   "saveScreenshotSelectedArea": {
     "message": "ບັນທຶກ"
   },
   "saveScreenshotVisibleArea": {
     "message": "ບັນທຶກສ່ວນທີ່ເບິງເຫັນໄດ້"
   },
   "saveScreenshotFullPage": {
     "message": "ບັນທຶກຫມົດຫນ້າ"
@@ -15,24 +27,97 @@
     "message": ""
   },
   "downloadScreenshot": {
     "message": "ດາວໂຫລດ"
   },
   "notificationLinkCopiedTitle": {
     "message": "ໄດ້ສຳເນົາລີ້ງໄວ້ແລ້ວ"
   },
+  "notificationLinkCopiedDetails": {
+    "message": "ລີ້ງໄປຫາຮູບພາບຂອງທ່ານໄດ້ຖືກບັນທຶກໄວ້ໃນຄຣິບບອດ. ກົດ $META_KEY$-V ເພື່ອວາງ.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
+  "requestErrorTitle": {
+    "message": "ໃຊ້ວຽກບໍ່ໄດ້."
+  },
+  "requestErrorDetails": {
+    "message": "ຂໍອະໄພ! ພວກເຮົາບໍ່ສາມາດບັນທຶກພາບຂອງທ່ານໄດ້. ກະລູນາລອງໃຫມ່ອີກຄັ້ງ."
+  },
+  "connectionErrorTitle": {
+    "message": "ພວກເຮົາບໍ່ສາມາດເຊື່ອມຕໍ່ໄປຫາພາບຫນ້າຈໍຂອງທ່ານໄດ້."
+  },
+  "connectionErrorDetails": {
+    "message": "ກະລູນາກວດເບິງການເຊື່ອມຕໍ່ກັບອິນເຕີເນັດຂອງທ່ານ. ຖ້າຫາກວ່າທ່ານສາມາດເຊື່ອມຕໍ່ກັບອິນເຕີເນັດໄດ້ແມ່ນ ບໍລິການພາບຖ່າຍຫນ້າຈໍຂອງ Firefox ອາດຈະເກີດມີບັນຫາຊົ່ວຄາວ."
+  },
+  "loginErrorDetails": {
+    "message": "ພວກເຮົາບໍ່ສາມາດບັນທຶກພາບຖ່າຍຂອງທ່ານໄດ້ ເພາະວ່າບໍລິການພາບຖ່າຍຫນ້າຈໍຂອງ Firefox ໄດ້ເກີດມີບັນຫາ. ກະລູນາລອງໃຫມ່ອີກຄັ້ງ."
+  },
   "unshootablePageErrorTitle": {
     "message": "ພວກເຮົາບໍ່ສາມາດຖ່າຍຮູບຫນ້າຈໍຂອງຫນ້ານີ້ໄດ້."
   },
+  "unshootablePageErrorDetails": {
+    "message": "ນີ້ບໍ່ແມ່ນຫນ້າເວັບມາດຕະຖານ, ສະນັ້ນທ່ານຈຶ່ງບໍ່ສາມາດຖ່າຍພາບຫນ້າຈໍໄດ້."
+  },
+  "selfScreenshotErrorTitle": {
+    "message": "ທ່ານບໍ່ສາມາດຖ່າຍພາບຫນ້າຈໍຂອງ Firefox ໄດ້!"
+  },
+  "genericErrorTitle": {
+    "message": "ໂອ! Firefox Screenshots ລວນ."
+  },
+  "genericErrorDetails": {
+    "message": "ພວກເຮົາບໍ່ແນ່ໃຈວ່າມັນຫາກະເກີດຫຍັງຂື້ນ. ກະລຸນາລອງໃຫມ່ອີກຄັ້ງ ຫລື ຖ່າຍພາບຫນ້າຈໍອື່ນລອງເບິງ"
+  },
+  "tourBodyOne": {
+    "message": "ຖ່າຍ, ບັນທຶກ ແລະ ແບ່ງປັນພາບຫນ້າຈໍໂດຍບໍ່ຕ້ອງອອກຈາກ Firefox."
+  },
+  "tourHeaderTwo": {
+    "message": "ຖ່າຍພາບຕາມທີ່ທ່ານຕ້ອງການ"
+  },
+  "tourBodyTwo": {
+    "message": "ຄິກ ຫລື ລາກເພື່ອຖ່າຍພາບສະເພາະບາງສ່ວນຂອງຫນ້າເວັບ. ພ້ອມດຽວກັນນັ້ນທ່ານຍັງສາມາດເລື່ອນມາເພື່ອເນັ້ນພາບທີ່ທ່ານເລືອກ."
+  },
+  "tourHeaderThree": {
+    "message": "ຕາມທີ່ທ່ານມັກ"
+  },
+  "tourBodyThree": {
+    "message": "ບັນທຶກຮູບພາບທີ່ທ່ານໄດ້ຄັອບເອົາໄວ້ລົງໄປໄວ້ໃນເວັບເພື່ອເຮັດໃຫ້ແບ່ງປັນໄດ້ງ່າຍ ຫລື ດາວໂຫລດໄປໄວ້ໃນຄອມພິວເຕີຂອງທ່ານ. ທ່ານຍັງສາມາດຄິກໃສ່ໃນປຸ່ມກົດ \"ຮູບພາບຂອງຂ້ອຍ\" ເພື່ອຊອກຫາຮູບພາບທັງຫມົດທີ່ທ່ານໄດ້ຖ່າຍເອົາໄວ້."
+  },
+  "tourHeaderFour": {
+    "message": "ຖ່າຍພາບວິນໂດ ຫລື ຫມົດທັ້ງຫນ້າ"
+  },
+  "tourBodyFour": {
+    "message": "ເລືອກປຸ່ມກົດທີ່ຢູ່ເທິງເບື້ອງຂວາມືເພື່ອຈັບພາບໃນບໍລິເວນທີ່ເບິງເຫັນໄດ້ໃນວິນໂດ ຫລື ເພື່ອຈັບພາບທັງຫມົດໃນຫນ້າເວັບ."
+  },
   "tourSkip": {
     "message": "ຂ້າມໄປ"
   },
   "tourNext": {
     "message": "ສະໄລດ໌ຕໍ່ໄປ"
   },
   "tourPrevious": {
     "message": "ສະໄລດ໌ກ່ອນຫນ້ານີ້"
   },
   "tourDone": {
     "message": "ສຳເລັດ"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "ການນຳໃຊ້ Firefox Screenshots ແມ່ນທ່ານໄດ້ຍອມຮັບເງືອນໄຂການໃຫ້ບໍລິການຂອງ Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ ແລະ $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "ຂໍ້ກຳນົດ"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "ຄຳເຕືອນກ່ຽວກັບຄວາມເປັນສ່ວນຕົວ"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/lt/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/lt/messages.json
@@ -98,18 +98,18 @@
     "message": "Kita skaidrė"
   },
   "tourPrevious": {
     "message": "Buvusi skaidrė"
   },
   "tourDone": {
     "message": "Baigta"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Naudodamiesi „Firefox Screenshots“, sutinkate su jų $TERMSANDPRIVACYNOTICETERMSLINK$ ir $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Naudodami „Firefox Screenshots“ sutinkate su „Firefox“ tinklo paslaugų $TERMSANDPRIVACYNOTICETERMSLINK$ bei $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/ms/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ms/messages.json
@@ -97,10 +97,27 @@
   "tourNext": {
     "message": "Slaid Seterusnya"
   },
   "tourPrevious": {
     "message": "Slaid Sebelumnya"
   },
   "tourDone": {
     "message": "Selesai"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Dengan menggunakan Firefox Screenshots, anda bersetuju dengan $TERMSANDPRIVACYNOTICETERMSLINK$ dan $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Terma"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Notis Privasi"
   }
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/my/messages.json
@@ -0,0 +1,74 @@
+{
+  "addonDescription": {
+    "message": "ဝဘ်ထံမှ ဓါတ်ပုံများနှင့် မျက်နှာပြင်ပုံဖမ်းချက်များကို ရိုက်ယူပြီး ယာယီ သို့မဟုတ် အမြဲတမ်းသိုလှောင်ရာတွင် သိမ်းဆည်းပါ။"
+  },
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "မျက်နှာပြင်ပုံရိပ် ဖမ်းယူပါ"
+  },
+  "myShotsLink": {
+    "message": "ရိုက်ကူးထားသော ပုံများ"
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "သိမ်းရန်"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "မြင်ရသည်များကို သိမ်းပါ"
+  },
+  "saveScreenshotFullPage": {
+    "message": "စာမျက်နှာတစ်ခုလုံးကို သိမ်းပါ"
+  },
+  "cancelScreenshot": {
+    "message": "မဆောင်ရွက်တော့ပါ"
+  },
+  "downloadScreenshot": {
+    "message": "ဆွဲယူရန်"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "လင့်ခ်ကို ကူယူပြီး"
+  },
+  "requestErrorTitle": {
+    "message": "ပျက်နေသည်"
+  },
+  "requestErrorDetails": {
+    "message": "စိတ်မကောင်းပါ၊ သင်ရိုက်ကူးထားသော ပုံကို မသိမ်းနိုင်ပါ။ နောင်တွင် ပြန်စမ်းကြည့်ပါ။"
+  },
+  "connectionErrorTitle": {
+    "message": "သင်ရိုက်ကူးထားသော မျက်နှာပြင်ပုံရိပ်များထံ မချိတ်ဆက်နိုင်ပါ။"
+  },
+  "unshootablePageErrorTitle": {
+    "message": "ဒီစာမျက်နှာ၏ မျက်နှာပြင်ပုံရိပ်ကို မရိုက်ကူးနိုင်ပါ။"
+  },
+  "tourBodyOne": {
+    "message": "Firefox ကနေ ထွက်ခွာရန် မလိုဘဲ မျက်နှာပြင်ပုံရိပ်များကို ရိုက်ကူး၊ သိမ်းဆည်း၊ မျှဝေပါ။"
+  },
+  "tourHeaderTwo": {
+    "message": "ကိုယ်နှစ်သက်ရာ စာမျက်နှာများကို ဖမ်းယူပါ"
+  },
+  "tourHeaderThree": {
+    "message": "နှစ်သက်သလို"
+  },
+  "tourHeaderFour": {
+    "message": "ဝင်ဒိုးများ သို့မဟုတ် စာမျက်နှာတစ်ခုလုံးကို ဖမ်းယူပါ"
+  },
+  "tourSkip": {
+    "message": "SKIP"
+  },
+  "tourNext": {
+    "message": "နောက်ဆလိုက်"
+  },
+  "tourPrevious": {
+    "message": "အရင်ကဆလိုက်"
+  },
+  "tourDone": {
+    "message": "ပြီးပြီ"
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "စကားရပ်များ"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "ကိုယ်ရေးကာကွယ်မှု သတိပေးချက်"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/nb_NO/messages.json
@@ -98,18 +98,18 @@
     "message": "Neste slide"
   },
   "tourPrevious": {
     "message": "Forrige slide"
   },
   "tourDone": {
     "message": "Ferdig"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/nl/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/nl/messages.json
@@ -98,18 +98,18 @@
     "message": "Volgende slide"
   },
   "tourPrevious": {
     "message": "Vorige slide"
   },
   "tourDone": {
     "message": "Gereed"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Door Firefox Screenshots te gebruiken, gaat u akkoord met de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ van Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Door Firefox Screenshots te gebruiken, gaat u akkoord met de $TERMSANDPRIVACYNOTICETERMSLINK$ en $TERMSANDPRIVACYNOTICEPRIVACYLINK$ van Firefox-cloudservices.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/nn_NO/messages.json
@@ -98,18 +98,18 @@
     "message": "Neste slide"
   },
   "tourPrevious": {
     "message": "Føregåande slide"
   },
   "tourDone": {
     "message": "Ferdig"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Ved å bruke Firefox Screenshots, godtar du $TERMSANDPRIVACYNOTICETERMSLINK$ og $TERMSANDPRIVACYNOTICEPRIVACYLINK$ for Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/pl/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/pl/messages.json
@@ -98,26 +98,26 @@
     "message": "Dalej"
   },
   "tourPrevious": {
     "message": "Wstecz"
   },
   "tourDone": {
     "message": "Zamknij"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Używając Firefox Screenshots, zgadzasz się na $TERMSANDPRIVACYNOTICETERMSLINK$ i $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Używając Firefox Screenshots, zgadzasz się na $TERMSANDPRIVACYNOTICETERMSLINK$ i $TERMSANDPRIVACYNOTICEPRIVACYLINK$ usług Firefox Cloud.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
   },
   "termsAndPrivacyNoticeTermsLink": {
-    "message": "warunki korzystania z usługi"
+    "message": "warunki korzystania"
   },
   "termsAndPrivacyNoticyPrivacyLink": {
     "message": "zasady ochrony prywatności"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/pt_BR/messages.json
@@ -98,18 +98,18 @@
     "message": "Próximo slide"
   },
   "tourPrevious": {
     "message": "Slide anterior"
   },
   "tourDone": {
     "message": "Concluir"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Usando Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Usando o Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$ dos serviços na nuvem do Firefox .",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/pt_PT/messages.json
@@ -98,18 +98,18 @@
     "message": "Diapositivo seguinte"
   },
   "tourPrevious": {
     "message": "Diapositivo anterior"
   },
   "tourDone": {
     "message": "Feito"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Ao utilizar o Firefox Screenshots, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e com a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ do Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Ao utilizar as Capturas de ecrã Firefox, você concorda com os $TERMSANDPRIVACYNOTICETERMSLINK$ e a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ do Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/rm/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/rm/messages.json
@@ -98,18 +98,18 @@
     "message": "Proxim pass"
   },
   "tourPrevious": {
     "message": "Ultim pass"
   },
   "tourDone": {
     "message": "Finì"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Cun utilisar Firefox Screenshots accepteschas ti $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Cun utilisar Firefox Screenshots accepteschas ti $TERMSANDPRIVACYNOTICETERMSLINK$ e $TERMSANDPRIVACYNOTICEPRIVACYLINK$ da Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/ro/messages.json
@@ -0,0 +1,38 @@
+{
+  "addonDescription": {
+    "message": "Realizează decupaje și capturi de ecran de pe web și salvează-le temporar sau permanent."
+  },
+  "contextMenuLabel": {
+    "message": "Realizează o captură de ecran"
+  },
+  "myShotsLink": {
+    "message": "Capturile mele"
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "Salvează"
+  },
+  "cancelScreenshot": {
+    "message": "Renunță"
+  },
+  "downloadScreenshot": {
+    "message": "Descarcă"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "Link copiat"
+  },
+  "tourSkip": {
+    "message": "OMITE"
+  },
+  "tourNext": {
+    "message": "Diapozitivul următor"
+  },
+  "tourPrevious": {
+    "message": "Diapozitivul anterior"
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Termenii"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Politica de confidenţialitate"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/ru/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ru/messages.json
@@ -98,18 +98,18 @@
     "message": "Следующий слайд"
   },
   "tourPrevious": {
     "message": "Предыдущий слайд"
   },
   "tourDone": {
     "message": "Готово"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Используя Firefox Screenshots, вы соглашаетесь с его $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Используя Скриншоты Firefox, вы соглашаетесь с $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$ облачных сервисов Firefox.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/sk/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/sk/messages.json
@@ -98,18 +98,18 @@
     "message": "Ďalšia snímka"
   },
   "tourPrevious": {
     "message": "Predchádzajúca snímka"
   },
   "tourDone": {
     "message": "Hotovo"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Používaním služby Firefox Screenshots vyjadrujete súhlas s $TERMSANDPRIVACYNOTICETERMSLINK$ služby Screenshots a so $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Používaním služby Firefox Screenshots súhlasíte s $TERMSANDPRIVACYNOTICETERMSLINK$ a $TERMSANDPRIVACYNOTICEPRIVACYLINK$ Firefox Cloud Services.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/sl/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/sl/messages.json
@@ -98,18 +98,18 @@
     "message": "Naslednji diapozitiv"
   },
   "tourPrevious": {
     "message": "Prejšnji diapozitiv"
   },
   "tourDone": {
     "message": "Končano"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Z uporabo razširitve Firefox Screenshots se strinjate s $TERMSANDPRIVACYNOTICETERMSLINK$ in $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Z uporabo Firefox Screenshots se strinjate s $TERMSANDPRIVACYNOTICETERMSLINK$ Firefoxovih storitev v oblaku in $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/sq/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/sq/messages.json
@@ -1,14 +1,56 @@
 {
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "Bëni një Foto"
+  },
+  "myShotsLink": {
+    "message": "Shkrepjet e Mia"
+  },
   "saveScreenshotSelectedArea": {
     "message": "Ruaje"
   },
+  "saveScreenshotVisibleArea": {
+    "message": "Ruaj pjesën e dukshme"
+  },
+  "saveScreenshotFullPage": {
+    "message": "Ruaj krejt faqen"
+  },
   "cancelScreenshot": {
     "message": "Anuloje"
   },
   "downloadScreenshot": {
     "message": "Shkarkoje"
   },
   "notificationLinkCopiedTitle": {
     "message": "Lidhja u Kopjua"
+  },
+  "requestErrorDetails": {
+    "message": "Na ndjeni! S’e ruajtëm dot foton tuaj. Ju lutemi, riprovoni më vonë."
+  },
+  "connectionErrorTitle": {
+    "message": "S’lidhemi dot te fotot tuaja."
+  },
+  "unshootablePageErrorTitle": {
+    "message": "S’bëjmë dot foto të kësaj faqeje."
+  },
+  "tourHeaderTwo": {
+    "message": "Fiksoni Në Foto Aq Sa Doni"
+  },
+  "tourHeaderThree": {
+    "message": "Si T’ju Pëlqejë"
+  },
+  "tourHeaderFour": {
+    "message": "Fiksoni Dritare ose Krejt Faqet"
+  },
+  "tourDone": {
+    "message": "U bë"
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "Kushte"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "Shënim Privatësie"
   }
 }
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/sr/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/sr/messages.json
@@ -98,18 +98,18 @@
     "message": "Следећи слајд"
   },
   "tourPrevious": {
     "message": "Претходни слајд"
   },
   "tourDone": {
     "message": "Готово"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Коришћењем услуге Firefox Screenshots, слажете се са $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Коришћењем Firefox Screenshots-а, прихватате Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ и $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/sv_SE/messages.json
@@ -98,18 +98,18 @@
     "message": "Nästa sida"
   },
   "tourPrevious": {
     "message": "Föregående sida"
   },
   "tourDone": {
     "message": "Färdig"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Genom att använda Firefox Screenshots, godkänner du $TERMSANDPRIVACYNOTICETERMSLINK$ och $TERMSANDPRIVACYNOTICEPRIVACYLINK$ för Screenshots.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Genom att använda Firefox Screenshots, godkänner du $TERMSANDPRIVACYNOTICETERMSLINK$ och $TERMSANDPRIVACYNOTICEPRIVACYLINK$ för Firefox molntjänster.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/te/messages.json
@@ -0,0 +1,50 @@
+{
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "ఒక తెరపట్టు తీసుకోండి"
+  },
+  "myShotsLink": {
+    "message": "నా షాట్లు"
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "భద్రపరచు"
+  },
+  "saveScreenshotFullPage": {
+    "message": "పూర్తి పేజీని భద్రపరచు"
+  },
+  "cancelScreenshot": {
+    "message": "రద్దుచేయి"
+  },
+  "downloadScreenshot": {
+    "message": "దింపుకోండి"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "లంకె కాపీ అయింది"
+  },
+  "requestErrorDetails": {
+    "message": "క్షమిచండి! మీ తెరను భద్రపరచలేకపోయాం. దయచేసి కాసేపాగి మళ్ళీ ప్రయత్నించండి."
+  },
+  "tourHeaderThree": {
+    "message": "మీకు నచ్చినట్టుగా"
+  },
+  "tourSkip": {
+    "message": "దాటవేయి"
+  },
+  "tourNext": {
+    "message": "తర్వాతి ఫలకం"
+  },
+  "tourPrevious": {
+    "message": "మునుపటి ఫలకం"
+  },
+  "tourDone": {
+    "message": "పూర్తయింది"
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "నియమాలు"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "అంతరంగికత గమనిక"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/th/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/th/messages.json
@@ -98,18 +98,18 @@
     "message": "ภาพนิ่งถัดไป"
   },
   "tourPrevious": {
     "message": "ภาพนิ่งก่อนหน้า"
   },
   "tourDone": {
     "message": "เสร็จสิ้น"
   },
-  "termsAndPrivacyNotice": {
-    "message": "เพื่อใช้  Firefox Screenshots คุณยอมรับ $TERMSANDPRIVACYNOTICETERMSLINK$ และ $TERMSANDPRIVACYNOTICEPRIVACYLINK$ ของ Screenshots",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "สำหรับการใช้งาน Firefox Screenshots คุณยอมรับใน Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ และ $TERMSANDPRIVACYNOTICEPRIVACYLINK$",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/tl/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/tl/messages.json
@@ -98,18 +98,18 @@
     "message": "Susunod na Slide"
   },
   "tourPrevious": {
     "message": "Nakaraan na Slide"
   },
   "tourDone": {
     "message": "Tapos"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Sa pamamagitan ng paggamit ng Firefox screenshot, sumasang-ayon ka sa mga screenshot $TERMSANDPRIVACYNOTICETERMSLINK$ at $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Sa paggamit ng Firefox Screenshots, tinatanggap mo ang Firefox Cloud Services $TERMSANDPRIVACYNOTICETERMSLINK$ at $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/tr/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/tr/messages.json
@@ -98,18 +98,18 @@
     "message": "Sonraki slayt"
   },
   "tourPrevious": {
     "message": "Önceki slayt"
   },
   "tourDone": {
     "message": "Tamam"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Firefox Screenshots'ı kullandığınızda Screenshosts $TERMSANDPRIVACYNOTICETERMSLINK$ ve $TERMSANDPRIVACYNOTICEPRIVACYLINK$ kabul etmiş sayılırsınız.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Firefox Screenshots'ı kullandığınızda Firefox Bulut Hizmetleri'nin $TERMSANDPRIVACYNOTICETERMSLINK$ ve $TERMSANDPRIVACYNOTICEPRIVACYLINK$ kabul etmiş sayılırsınız.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/uk/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/uk/messages.json
@@ -98,18 +98,18 @@
     "message": "Наступний слайд"
   },
   "tourPrevious": {
     "message": "Попередній слайд"
   },
   "tourDone": {
     "message": "Готово"
   },
-  "termsAndPrivacyNotice": {
-    "message": "Використовуючи Firefox Screenshots, ви погоджуєтеся з його $TERMSANDPRIVACYNOTICETERMSLINK$ та $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "Використовуючи Firefox Screenshots, ви погоджуєтеся з умовами хмарних послуг Firefox: $TERMSANDPRIVACYNOTICETERMSLINK$ та $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/ur/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/ur/messages.json
@@ -59,16 +59,19 @@
     "message": "ہم اس صفحہ کی اسکرین شاٹ نہیں کر سکتے۔"
   },
   "unshootablePageErrorDetails": {
     "message": "یہ ایک میعاری صفحہ نہہیں، تو آپ اسکی اسکرین شاٹ نہیں لے سکتے۔"
   },
   "selfScreenshotErrorTitle": {
     "message": "آپ Firefox اسکرین شاٹس صفحے! کی ایک شاٹ نہیں لے سکت"
   },
+  "genericErrorTitle": {
+    "message": "لاجواب! Firefox Screenshots بہت مشہور ہو گیا۔"
+  },
   "genericErrorDetails": {
     "message": "ہمیں یقین نہیں کہ کیا ہوا تھا۔ خیال رکھ کر پھر کوشش کریں یا بھر مختلف صفحہ کی تصویرلیں؟"
   },
   "tourBodyOne": {
     "message": "۔Firefox کو چھوڑے بغیر اسکرینشاٹس لیں، محفوظ کریں اور شیئر کریں۔"
   },
   "tourHeaderTwo": {
     "message": "جو آپ چاہتے ہیں وہ گرفت کریں"
@@ -94,10 +97,27 @@
   "tourNext": {
     "message": "اگلى سلائيڈ"
   },
   "tourPrevious": {
     "message": "پچھلی سلائڈ"
   },
   "tourDone": {
     "message": "ہوگیا"
+  },
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "۔Firefox Screenshots کے استعمال کے ساتھ  آپ Firefox Cloud Services کے $TERMSANDPRIVACYNOTICETERMSLINK$ اور $TERMSANDPRIVACYNOTICEPRIVACYLINK$ کے ساتھ متفق ہیں۔",
+    "placeholders": {
+      "termsandprivacynoticetermslink": {
+        "content": "$1"
+      },
+      "termsandprivacynoticeprivacylink": {
+        "content": "$2"
+      }
+    }
+  },
+  "termsAndPrivacyNoticeTermsLink": {
+    "message": "شرائط"
+  },
+  "termsAndPrivacyNoticyPrivacyLink": {
+    "message": "اطلاع نامہ نجی نوعیت"
   }
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/_locales/uz/messages.json
@@ -0,0 +1,64 @@
+{
+  "addonAuthorsList": {
+    "message": "Mozilla <screenshots-feedback@mozilla.org>"
+  },
+  "contextMenuLabel": {
+    "message": "Rasmini olish"
+  },
+  "myShotsLink": {
+    "message": "Rasmlarim"
+  },
+  "screenshotInstructions": {
+    "message": "Hududni belgilash uchun sahifa ustiga tashlang yoki bosing. Chiqish uchun “ESC” tugmasidan foydalaning."
+  },
+  "saveScreenshotSelectedArea": {
+    "message": "Saqlash"
+  },
+  "saveScreenshotVisibleArea": {
+    "message": "Ko‘rinadiganini saqlash"
+  },
+  "saveScreenshotFullPage": {
+    "message": "To‘liq sahifani saqlash"
+  },
+  "cancelScreenshot": {
+    "message": "Bekor qilish"
+  },
+  "downloadScreenshot": {
+    "message": "Yuklab olish"
+  },
+  "notificationLinkCopiedTitle": {
+    "message": "Havoladan nusxa olindi"
+  },
+  "notificationLinkCopiedDetails": {
+    "message": "Rasm havolasidan maxsus xotiraga nusxa olindi. Qo‘yish uchun $META_KEY$-V tugmalarini bosing.",
+    "placeholders": {
+      "meta_key": {
+        "content": "$1"
+      }
+    }
+  },
+  "requestErrorTitle": {
+    "message": "Xizmat hozircha ishlamayapti"
+  },
+  "requestErrorDetails": {
+    "message": "Uzr! Rasmni saqlay olmaymiz. Keyinroq urinib ko‘ring."
+  },
+  "connectionErrorTitle": {
+    "message": "Ekran rasmiga ulana olmadik."
+  },
+  "connectionErrorDetails": {
+    "message": "Internetga ulanishni tekshiring. Ulana olsangiz, demak Firefox Screenshot xizmatida vaqtinchalik muammo bo‘lishi mumkin."
+  },
+  "loginErrorDetails": {
+    "message": "Olingan rasmni saqlay olmaymiz, chunki Firefox Screenshot xizmatida muammo mavjud. Keyinroq urinib ko‘ring."
+  },
+  "unshootablePageErrorTitle": {
+    "message": "Bu sahifani rasmga tushira olmaymiz."
+  },
+  "unshootablePageErrorDetails": {
+    "message": "Bu sahifa standart sahifa emas, shuning uchun uni rasmga tushira olmaymiz."
+  },
+  "selfScreenshotErrorTitle": {
+    "message": "Firefox Screenshot sahifasini rasmga tushirish mumkin emas!"
+  }
+}
\ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/zh_CN/messages.json
@@ -98,18 +98,18 @@
     "message": "下一页"
   },
   "tourPrevious": {
     "message": "上一页"
   },
   "tourDone": {
     "message": "完成"
   },
-  "termsAndPrivacyNotice": {
-    "message": "使用 Firefox Screenshots 即代表您同意 Screenshots 的$TERMSANDPRIVACYNOTICETERMSLINK$和$TERMSANDPRIVACYNOTICEPRIVACYLINK$。",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "使用 Firefox Screenshots 即代表您同意 Firefox 云服务的$TERMSANDPRIVACYNOTICETERMSLINK$和$TERMSANDPRIVACYNOTICEPRIVACYLINK$。",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json
+++ b/browser/extensions/screenshots/webextension/_locales/zh_TW/messages.json
@@ -98,18 +98,18 @@
     "message": "下一頁"
   },
   "tourPrevious": {
     "message": "上一頁"
   },
   "tourDone": {
     "message": "完成"
   },
-  "termsAndPrivacyNotice": {
-    "message": "使用 Firefox Screenshots,代表您同意 Screenshots 的 $TERMSANDPRIVACYNOTICETERMSLINK$ 及 $TERMSANDPRIVACYNOTICEPRIVACYLINK$。",
+  "termsAndPrivacyNoticeCloudServices": {
+    "message": "繼續使用 Firefox Screenshots,代表您同意 Firefox 雲端服務的 $TERMSANDPRIVACYNOTICETERMSLINK$ 以及 $TERMSANDPRIVACYNOTICEPRIVACYLINK$。",
     "placeholders": {
       "termsandprivacynoticetermslink": {
         "content": "$1"
       },
       "termsandprivacynoticeprivacylink": {
         "content": "$2"
       }
     }
--- a/browser/extensions/screenshots/webextension/assertIsTrusted.js
+++ b/browser/extensions/screenshots/webextension/assertIsTrusted.js
@@ -1,20 +1,20 @@
 /** For use with addEventListener, assures that any events have event.isTrusted set to true
       https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted
     Should be applied *inside* catcher.watchFunction
 */
 this.assertIsTrusted = function assertIsTrusted(handlerFunction) {
-  return function (event) {
-    if (! event) {
+  return function(event) {
+    if (!event) {
       let exc = new Error("assertIsTrusted did not get an event");
       exc.noPopup = true;
       throw exc;
     }
-    if (! event.isTrusted) {
+    if (!event.isTrusted) {
       let exc = new Error(`Received untrusted event (type: ${event.type})`);
       exc.noPopup = true;
       throw exc;
     }
     return handlerFunction.call(this, event);
   };
 }
 null;
--- a/browser/extensions/screenshots/webextension/background/analytics.js
+++ b/browser/extensions/screenshots/webextension/background/analytics.js
@@ -1,29 +1,29 @@
 /* globals main, auth, catcher, deviceInfo, communication, log */
 
 "use strict";
 
-this.analytics = (function () {
+this.analytics = (function() {
   let exports = {};
 
   let telemetryPrefKnown = false;
   let telemetryPref;
 
-  exports.sendEvent = function (action, label, options) {
+  exports.sendEvent = function(action, label, options) {
     let eventCategory = "addon";
-    if (! telemetryPrefKnown) {
+    if (!telemetryPrefKnown) {
       log.warn("sendEvent called before we were able to refresh");
       return Promise.resolve();
     }
-    if (! telemetryPref) {
+    if (!telemetryPref) {
       log.info(`Cancelled sendEvent ${eventCategory}/${action}/${label || 'none'} ${JSON.stringify(options)}`);
       return Promise.resolve();
     }
-    if (typeof label == "object" && (! options)) {
+    if (typeof label == "object" && (!options)) {
       options = label;
       label = undefined;
     }
     options = options || {};
     let di = deviceInfo();
     return new Promise((resolve, reject) => {
       let url = main.getBackend() + "/event";
       let req = new XMLHttpRequest();
@@ -51,17 +51,17 @@ this.analytics = (function () {
         event: eventCategory,
         action,
         label,
         options
       }));
     });
   };
 
-  exports.refreshTelemetryPref = function () {
+  exports.refreshTelemetryPref = function() {
     return communication.sendToBootstrap("getTelemetryPref").then((result) => {
       telemetryPrefKnown = true;
       if (result === communication.NO_BOOTSTRAP) {
         telemetryPref = true;
       } else {
         telemetryPref = result;
       }
     }, (error) => {
--- a/browser/extensions/screenshots/webextension/background/auth.js
+++ b/browser/extensions/screenshots/webextension/background/auth.js
@@ -1,14 +1,14 @@
 /* globals browser, log */
 /* globals main, makeUuid, deviceInfo, analytics, catcher, buildSettings, communication */
 
 "use strict";
 
-this.auth = (function () {
+this.auth = (function() {
   let exports = {};
 
   let registrationInfo;
   let initialized = false;
   let authHeader = null;
   let sentryPublicDSN = null;
   let abTests = {};
 
@@ -20,17 +20,17 @@ this.auth = (function () {
       registrationInfo = result.registrationInfo;
     } else {
       registrationInfo = generateRegistrationInfo();
       log.info("Generating new device authentication ID", registrationInfo);
       return browser.storage.local.set({registrationInfo});
     }
   }));
 
-  exports.getDeviceId = function () {
+  exports.getDeviceId = function() {
     return registrationInfo && registrationInfo.deviceId;
   };
 
   function generateRegistrationInfo() {
     let info = {
       deviceId: `anon${makeUuid()}`,
       secret: makeUuid(),
       registered: false
@@ -141,49 +141,48 @@ this.auth = (function () {
       }
     }
     if (responseJson.abTests) {
       abTests = responseJson.abTests;
       catcher.watchPromise(browser.storage.local.set({abTests}));
     }
   }
 
-  exports.getDeviceId = function () {
+  exports.getDeviceId = function() {
     return registrationInfo.deviceId;
   };
 
-  exports.authHeaders = function () {
+  exports.authHeaders = function() {
     let initPromise = Promise.resolve();
-    if (! initialized) {
+    if (!initialized) {
       initPromise = login();
     }
     return initPromise.then(() => {
       if (authHeader) {
         return {"x-screenshots-auth": authHeader};
-      } else {
-        log.warn("No auth header available");
-        return {};
       }
+      log.warn("No auth header available");
+      return {};
     });
   };
 
-  exports.getSentryPublicDSN = function () {
+  exports.getSentryPublicDSN = function() {
     return sentryPublicDSN || buildSettings.defaultSentryDsn;
   };
 
-  exports.getAbTests = function () {
+  exports.getAbTests = function() {
     return abTests;
   };
 
-  exports.isRegistered = function () {
+  exports.isRegistered = function() {
     return registrationInfo.registered;
   };
 
-  exports.setDeviceInfoFromOldAddon = function (newDeviceInfo) {
-    if (! (newDeviceInfo.deviceId && newDeviceInfo.secret)) {
+  exports.setDeviceInfoFromOldAddon = function(newDeviceInfo) {
+    if (!(newDeviceInfo.deviceId && newDeviceInfo.secret)) {
       throw new Error("Bad deviceInfo");
     }
     if (registrationInfo.deviceId === newDeviceInfo.deviceId &&
       registrationInfo.secret === newDeviceInfo.secret) {
       // Probably we already imported the information
       return Promise.resolve(false);
     }
     registrationInfo = {
@@ -194,23 +193,18 @@ this.auth = (function () {
     initialized = false;
     return browser.storage.local.set({registrationInfo}).then(() => {
       return true;
     });
   };
 
   communication.register("getAuthInfo", (sender, ownershipCheck) => {
     let info = registrationInfo;
-    let done = Promise.resolve();
     if (info.registered) {
-      done = login({ownershipCheck}).then((result) => {
-        if (result && result.isOwner) {
-          info.isOwner = true;
-        }
+      return login({ownershipCheck}).then((result) => {
+        return {isOwner: result && result.isOwner, deviceId: registrationInfo.deviceId};
       });
     }
-    return done.then(() => {
-      return info;
-    });
+    return Promise.resolve(info);
   });
 
   return exports;
 })();
--- a/browser/extensions/screenshots/webextension/background/communication.js
+++ b/browser/extensions/screenshots/webextension/background/communication.js
@@ -1,80 +1,78 @@
 /* globals browser, catcher, log */
 
 "use strict";
 
-this.communication = (function () {
+this.communication = (function() {
   let exports = {};
 
   let registeredFunctions = {};
 
   browser.runtime.onMessage.addListener(catcher.watchFunction((req, sender, sendResponse) => {
-    if (! (req.funcName in registeredFunctions)) {
+    if (!(req.funcName in registeredFunctions)) {
       log.error(`Received unknown internal message type ${req.funcName}`);
       sendResponse({type: "error", name: "Unknown message type"});
       return;
     }
-    if (! Array.isArray(req.args)) {
+    if (!Array.isArray(req.args)) {
       log.error("Received message with no .args list");
       sendResponse({type: "error", name: "No .args"});
       return;
     }
     let func = registeredFunctions[req.funcName];
     let result;
     try {
       req.args.unshift(sender);
       result = func.apply(null, req.args);
     } catch (e) {
       log.error(`Error in ${req.funcName}:`, e, e.stack);
       // FIXME: should consider using makeError from catcher here:
-      sendResponse({type: "error", message: e+""});
+      sendResponse({type: "error", message: e + "", errorCode: e.errorCode, popupMessage: e.popupMessage});
       return;
     }
     if (result && result.then) {
       result.then((concreteResult) => {
         sendResponse({type: "success", value: concreteResult});
       }).catch((errorResult) => {
         log.error(`Promise error in ${req.funcName}:`, errorResult, errorResult && errorResult.stack);
-        sendResponse({type: "error", message: errorResult+""});
+        sendResponse({type: "error", message: errorResult + "", errorCode: errorResult.errorCode, popupMessage: errorResult.popupMessage});
       });
       return true;
-    } else {
-      sendResponse({type: "success", value: result});
     }
+    sendResponse({type: "success", value: result});
   }));
 
-  exports.register = function (name, func) {
+  exports.register = function(name, func) {
     registeredFunctions[name] = func;
   };
 
   /** Send a message to bootstrap.js
       Technically any worker can listen to this.  If the bootstrap wrapper is not in place, then this
       will *not* fail, and will return a value of exports.NO_BOOTSTRAP  */
-  exports.sendToBootstrap = function (funcName, ...args) {
+  exports.sendToBootstrap = function(funcName, ...args) {
     return browser.runtime.sendMessage({funcName, args}).then((result) => {
       if (result.type === "success") {
         return result.value;
-      } else {
-        throw new Error(`Error in ${funcName}: ${result.name || 'unknown'}`);
       }
+      throw new Error(`Error in ${funcName}: ${result.name || 'unknown'}`);
     }, (error) => {
       if (isBootstrapMissingError(error)) {
         return exports.NO_BOOTSTRAP;
       }
       throw error;
     });
   };
 
   function isBootstrapMissingError(error) {
-    if (! error) {
+    if (!error) {
       return false;
     }
-    return error.errorCode === "NO_RECEIVING_END" ||
-      (! error.errorCode && error.message === "Could not establish connection. Receiving end does not exist.");
+    return ('errorCode' in error && error.errorCode === "NO_RECEIVING_END") ||
+      (!error.errorCode && error.message === "Could not establish connection. Receiving end does not exist.");
   }
 
 
-  // A singleton/sentinal (with a name):
+  // A singleton/sentinel (with a name):
   exports.NO_BOOTSTRAP = {name: "communication.NO_BOOTSTRAP"};
 
   return exports;
 })();
--- a/browser/extensions/screenshots/webextension/background/deviceInfo.js
+++ b/browser/extensions/screenshots/webextension/background/deviceInfo.js
@@ -1,13 +1,13 @@
 /* globals browser, catcher */
 
 "use strict";
 
-this.deviceInfo = (function () {
+this.deviceInfo = (function() {
   let manifest = browser.runtime.getManifest();
 
   let platformInfo = {};
   catcher.watchPromise(browser.runtime.getPlatformInfo().then((info) => {
     platformInfo = info;
   }));
 
   return function deviceInfo() {
@@ -18,17 +18,17 @@ this.deviceInfo = (function () {
     let appName = chromeVersion ? "chrome" : "firefox";
 
     return {
       addonVersion: manifest.version,
       platform: platformInfo.os,
       architecture: platformInfo.arch,
       version: firefoxVersion || chromeVersion,
       // These don't seem to apply to Chrome:
-      //build: system.build,
-      //platformVersion: system.platformVersion,
+      // build: system.build,
+      // platformVersion: system.platformVersion,
       userAgent: navigator.userAgent,
       appVendor: appName,
       appName
     };
   };
 
 })();
--- a/browser/extensions/screenshots/webextension/background/main.js
+++ b/browser/extensions/screenshots/webextension/background/main.js
@@ -1,43 +1,43 @@
-/* globals browser, console, XMLHttpRequest, Image, document, setTimeout, navigator */
-/* globals selectorLoader, analytics, communication, catcher, makeUuid, auth, senderror */
+/* globals browser, XMLHttpRequest, Image, document, setTimeout, navigator */
+/* globals selectorLoader, analytics, communication, catcher, log, makeUuid, auth, senderror */
 
 "use strict";
 
-this.main = (function () {
+this.main = (function() {
   let exports = {};
 
   const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl";
   const { sendEvent } = analytics;
 
   let manifest = browser.runtime.getManifest();
   let backend;
 
   let hasSeenOnboarding;
 
   browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
-    hasSeenOnboarding = !! result.hasSeenOnboarding;
-    if (! hasSeenOnboarding) {
+    hasSeenOnboarding = !!result.hasSeenOnboarding;
+    if (!hasSeenOnboarding) {
       setIconActive(false, null);
       // Note that the branded name 'Firefox Screenshots' is not localized:
       browser.browserAction.setTitle({
         title: "Firefox Screenshots"
       });
     }
   }).catch((error) => {
     log.error("Error getting hasSeenOnboarding:", error);
   });
 
-  exports.setBackend = function (newBackend) {
+  exports.setBackend = function(newBackend) {
     backend = newBackend;
     backend = backend.replace(/\/*$/, "");
   };
 
-  exports.getBackend = function () {
+  exports.getBackend = function() {
     return backend;
   };
 
   communication.register("getBackend", () => {
     return backend;
   });
 
   function getOnboardingUrl() {
@@ -47,19 +47,19 @@ this.main = (function () {
   for (let permission of manifest.permissions) {
     if (/^https?:\/\//.test(permission)) {
       exports.setBackend(permission);
       break;
     }
   }
 
   function setIconActive(active, tabId) {
-    let path = active ? "icons/icon-highlight-38.png" : "icons/icon-38.png";
-    if ((! hasSeenOnboarding) && ! active) {
-      path = "icons/icon-starred-38.png";
+    let path = active ? "icons/icon-highlight-32.svg" : "icons/icon-32.svg";
+    if ((!hasSeenOnboarding) && !active) {
+      path = "icons/icon-starred-32.svg";
     }
     browser.browserAction.setIcon({path, tabId});
   }
 
   function toggleSelector(tab) {
     return analytics.refreshTelemetryPref()
       .then(() => selectorLoader.toggle(tab.id, hasSeenOnboarding))
       .then(active => {
@@ -67,23 +67,35 @@ this.main = (function () {
         return active;
       })
       .catch((error) => {
         error.popupMessage = "UNSHOOTABLE_PAGE";
         throw error;
       });
   }
 
+  function startSelectionWithOnboarding(tab) {
+    return analytics.refreshTelemetryPref().then(() => {
+      return selectorLoader.testIfLoaded(tab.id);
+    }).then((isLoaded) => {
+      if (!isLoaded) {
+        sendEvent("start-shot", "site-request");
+        setIconActive(true, tab.id);
+        selectorLoader.toggle(tab.id, false);
+      }
+    });
+  }
+
   function shouldOpenMyShots(url) {
     return /^about:(?:newtab|blank)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url);
   }
 
   browser.browserAction.onClicked.addListener(catcher.watchFunction((tab) => {
     if (shouldOpenMyShots(tab.url)) {
-      if (! hasSeenOnboarding) {
+      if (!hasSeenOnboarding) {
         catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
           sendEvent("goto-onboarding", "selection-button");
           return forceOnboarding();
         }));
         return;
       }
       catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
         sendEvent("goto-myshots", "about-newtab");
@@ -93,17 +105,17 @@ this.main = (function () {
         .then(() => browser.tabs.update({url: backend + "/shots"})));
     } else {
       catcher.watchPromise(
         toggleSelector(tab)
           .then(active => {
             const event = active ? "start-shot" : "cancel-shot";
             sendEvent(event, "toolbar-button");
           }, (error) => {
-            if ((! hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") {
+            if ((!hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") {
               sendEvent("goto-onboarding", "selection-button");
               return forceOnboarding();
             }
             throw error;
           }));
     }
   }));
 
@@ -121,17 +133,17 @@ this.main = (function () {
   }, () => {
     // Note: unlike most browser.* functions this one does not return a promise
     if (browser.runtime.lastError) {
       catcher.unhandled(new Error(browser.runtime.lastError.message));
     }
   });
 
   browser.contextMenus.onClicked.addListener(catcher.watchFunction((info, tab) => {
-    if (! tab) {
+    if (!tab) {
       // Not in a page/tab context, ignore
       return;
     }
     catcher.watchPromise(
       toggleSelector(tab)
         .then(() => sendEvent("start-shot", "context-menu")));
   }));
 
@@ -142,24 +154,24 @@ this.main = (function () {
     if (isShotOrMyShotPage(url) || /^(?:about|data|moz-extension):/i.test(url) || isBlacklistedUrl(url)) {
       return false;
     }
     return true;
   }
 
   function isShotOrMyShotPage(url) {
     // It's okay to take a shot of any pages except shot pages and My Shots
-    if (! url.startsWith(backend)) {
+    if (!url.startsWith(backend)) {
       return false;
     }
-    let path = url.substr(backend.length).replace(/^\/*/, "").replace(/#.*/, "").replace(/\?.*/, "");
+    let path = url.substr(backend.length).replace(/^\/*/, "").replace(/[?#].*/, "");
     if (path == "shots") {
       return true;
     }
-    if (/^[^/]+\/[^/]+$/.test(url)) {
+    if (/^[^/]+\/[^/]+$/.test(path)) {
       // Blocks {:id}/{:domain}, but not /, /privacy, etc
       return true;
     }
     return false;
   }
 
   function isBlacklistedUrl(url) {
     // These specific domains are not allowed for general WebExtension permission reasons
@@ -168,38 +180,51 @@ this.main = (function () {
     // Note we disable it here to be informative, the security check is done in WebExtension code
     const badDomains = ["addons.mozilla.org", "testpilot.firefox.com"];
     let domain = url.replace(/^https?:\/\//i, "");
     domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
     domain = domain.toLowerCase();
     return badDomains.includes(domain);
   }
 
+  function enableButton(tabId) {
+    browser.browserAction.enable(tabId);
+    // We have to manually toggle the icon state, because disabled toolbar
+    // buttons aren't automatically dimmed for WebExtensions on Windows or
+    // Linux (bug 1204609).
+    setIconActive(false, tabId);
+  }
+
+  function disableButton(tabId) {
+    browser.browserAction.disable(tabId);
+    setIconActive(true, tabId);
+  }
+
   browser.tabs.onUpdated.addListener(catcher.watchFunction((id, info, tab) => {
     if (info.url && tab.active) {
       if (urlEnabled(info.url)) {
-        browser.browserAction.enable(tab.id);
+        enableButton(tab.id);
       } else if (hasSeenOnboarding) {
-        browser.browserAction.disable(tab.id);
+        disableButton(tab.id);
       }
     }
-  }));
+  }, true));
 
   browser.tabs.onActivated.addListener(catcher.watchFunction(({tabId, windowId}) => {
     catcher.watchPromise(browser.tabs.get(tabId).then((tab) => {
       // onActivated may fire before the url is set
       if (!tab.url) {
         return;
       }
       if (urlEnabled(tab.url)) {
-        browser.browserAction.enable(tabId);
+        enableButton(tabId);
       } else if (hasSeenOnboarding) {
-        browser.browserAction.disable(tabId);
+        disableButton(tabId);
       }
-    }));
+    }), true);
   }));
 
   communication.register("sendEvent", (sender, ...args) => {
     catcher.watchPromise(sendEvent(...args));
     // We don't wait for it to complete:
     return null;
   });
 
@@ -222,28 +247,42 @@ this.main = (function () {
   });
 
   communication.register("downloadShot", (sender, info) => {
     // 'data:' urls don't work directly, let's use a Blob
     // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
     const binary = atob(info.url.split(',')[1]); // just the base64 data
     const data = Uint8Array.from(binary, char => char.charCodeAt(0))
     const blob = new Blob([data], {type: "image/png"})
+    let url = URL.createObjectURL(blob);
+    let downloadId;
+    let onChangedCallback = catcher.watchFunction(function(change) {
+      if (!downloadId || downloadId != change.id) {
+        return;
+      }
+      if (change.state && change.state.current != "in_progress") {
+        URL.revokeObjectURL(url);
+        browser.downloads.onChanged.removeListener(onChangedCallback);
+      }
+    });
+    browser.downloads.onChanged.addListener(onChangedCallback)
     return browser.downloads.download({
-      url: URL.createObjectURL(blob),
+      url,
       filename: info.filename
+    }).then((id) => {
+      downloadId = id;
     });
   });
 
   communication.register("closeSelector", (sender) => {
     setIconActive(false, sender.tab.id)
   });
 
   catcher.watchPromise(communication.sendToBootstrap("getOldDeviceInfo").then((deviceInfo) => {
-    if (deviceInfo === communication.NO_BOOTSTRAP || ! deviceInfo) {
+    if (deviceInfo === communication.NO_BOOTSTRAP || !deviceInfo) {
       return;
     }
     deviceInfo = JSON.parse(deviceInfo);
     if (deviceInfo && typeof deviceInfo == "object") {
       return auth.setDeviceInfoFromOldAddon(deviceInfo).then((updated) => {
         if (updated === communication.NO_BOOTSTRAP) {
           throw new Error("bootstrap.js disappeared unexpectedly");
         }
@@ -267,10 +306,25 @@ this.main = (function () {
     sendEvent("abort-start-shot", "frame-page");
     // Note, we only show the error but don't report it, as we know that we can't
     // take shots of these pages:
     senderror.showError({
       popupMessage: "UNSHOOTABLE_PAGE"
     });
   });
 
+  // Note: this signal is only needed until bug 1357589 is fixed.
+  communication.register("openTermsPage", () => {
+    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/about/legal/terms/services/"}));
+  });
+
+  // Note: this signal is also only needed until bug 1357589 is fixed.
+  communication.register("openPrivacyPage", () => {
+    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/privacy/firefox-cloud/"}));
+  });
+
+  // A Screenshots page wants us to start/force onboarding
+  communication.register("requestOnboarding", (sender) => {
+    return startSelectionWithOnboarding(sender.tab);
+  });
+
   return exports;
 })();
--- a/browser/extensions/screenshots/webextension/background/selectorLoader.js
+++ b/browser/extensions/screenshots/webextension/background/selectorLoader.js
@@ -1,13 +1,15 @@
 /* globals browser, catcher, log */
 
 "use strict";
 
-this.selectorLoader = (function () {
+var global = this;
+
+this.selectorLoader = (function() {
   const exports = {};
 
   // These modules are loaded in order, first standardScripts, then optionally onboardingScripts, and then selectorScripts
   // The order is important due to dependencies
   const standardScripts = [
     "build/buildSettings.js",
     "log.js",
     "catcher.js",
@@ -32,31 +34,54 @@ this.selectorLoader = (function () {
 
   // These are loaded on request (by the selector worker) to activate the onboarding:
   const onboardingScripts = [
     "build/onboardingCss.js",
     "build/onboardingHtml.js",
     "onboarding/slides.js"
   ];
 
-  exports.unloadIfLoaded = function (tabId) {
+  exports.unloadIfLoaded = function(tabId) {
     return browser.tabs.executeScript(tabId, {
       code: "this.selectorLoader && this.selectorLoader.unloadModules()",
       runAt: "document_start"
     }).then(result => {
       return result && result[0];
     });
   };
 
-  exports.loadModules = function (tabId, hasSeenOnboarding) {
+  exports.testIfLoaded = function(tabId) {
+    if (loadingTabs.has(tabId)) {
+      return true;
+    }
+    return browser.tabs.executeScript(tabId, {
+      code: "!!this.selectorLoader",
+      runAt: "document_start"
+    }).then(result => {
+      return result && result[0];
+    });
+  };
+
+  let loadingTabs = new Set();
+
+  exports.loadModules = function(tabId, hasSeenOnboarding) {
+    let promise;
+    loadingTabs.add(tabId);
     if (hasSeenOnboarding) {
-      return executeModules(tabId, standardScripts.concat(selectorScripts));
+      promise = executeModules(tabId, standardScripts.concat(selectorScripts));
     } else {
-      return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts));
+      promise = executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts));
     }
+    return promise.then((result) => {
+      loadingTabs.delete(tabId);
+      return result;
+    }, (error) => {
+      loadingTabs.delete(tabId);
+      throw error;
+    });
   };
 
   function executeModules(tabId, scripts) {
     let lastPromise = Promise.resolve(null);
     scripts.forEach((file) => {
       lastPromise = lastPromise.then(() => {
         return browser.tabs.executeScript(tabId, {
           file,
@@ -74,17 +99,17 @@ this.selectorLoader = (function () {
     },
     (error) => {
       exports.unloadIfLoaded(tabId);
       catcher.unhandled(error);
       throw error;
     });
   }
 
-  exports.unloadModules = function () {
+  exports.unloadModules = function() {
     const watchFunction = catcher.watchFunction;
     let allScripts = standardScripts.concat(onboardingScripts).concat(selectorScripts);
     const moduleNames = allScripts.map((filename) =>
       filename.replace(/^.*\//, "").replace(/\.js$/, ""));
     moduleNames.reverse();
     for (let moduleName of moduleNames) {
       let moduleObj = global[moduleName];
       if (moduleObj && moduleObj.unload) {
@@ -94,17 +119,17 @@ this.selectorLoader = (function () {
           // ignore (watchFunction handles it)
         }
       }
       delete global[moduleName];
     }
     return true;
   };
 
-  exports.toggle = function (tabId, hasSeenOnboarding) {
+  exports.toggle = function(tabId, hasSeenOnboarding) {
     return exports.unloadIfLoaded(tabId)
       .then(wasLoaded => {
         if (!wasLoaded) {
           exports.loadModules(tabId, hasSeenOnboarding);
         }
         return !wasLoaded;
       })
   };
--- a/browser/extensions/screenshots/webextension/background/senderror.js
+++ b/browser/extensions/screenshots/webextension/background/senderror.js
@@ -1,15 +1,17 @@
 /* globals analytics, browser, communication, makeUuid, Raven, catcher, auth, log */
 
 "use strict";
 
-this.senderror = (function () {
+this.senderror = (function() {
   let exports = {};
 
+  let manifest = browser.runtime.getManifest();
+
   // Do not show an error more than every ERROR_TIME_LIMIT milliseconds:
   const ERROR_TIME_LIMIT = 3000;
 
   let messages = {
     REQUEST_ERROR: {
       title: browser.i18n.getMessage("requestErrorTitle"),
       info: browser.i18n.getMessage("requestErrorDetails")
     },
@@ -43,24 +45,24 @@ this.senderror = (function () {
   };
 
   communication.register("reportError", (sender, error) => {
     catcher.unhandled(error);
   });
 
   let lastErrorTime;
 
-  exports.showError = function (error) {
+  exports.showError = function(error) {
     if (lastErrorTime && (Date.now() - lastErrorTime) < ERROR_TIME_LIMIT) {
       return;
     }
     lastErrorTime = Date.now();
     let id = makeUuid();
     let popupMessage = error.popupMessage || "generic";
-    if (! messages[popupMessage]) {
+    if (!messages[popupMessage]) {
       popupMessage = "generic";
     }
     let title = messages[popupMessage].title;
     let message = messages[popupMessage].info || '';
     let showMessage = messages[popupMessage].showMessage;
     if (error.message && showMessage) {
       if (message) {
         message += "\n" + error.message;
@@ -71,47 +73,47 @@ this.senderror = (function () {
     browser.notifications.create(id, {
       type: "basic",
       // FIXME: need iconUrl for an image, see #2239
       title,
       message
     });
   };
 
-  exports.reportError = function (e) {
+  exports.reportError = function(e) {
     if (!analytics.getTelemetryPrefSync()) {
       log.error("Telemetry disabled. Not sending critical error:", e);
       return;
     }
     let dsn = auth.getSentryPublicDSN();
-    if (! dsn) {
+    if (!dsn) {
       log.warn("Error:", e);
       return;
     }
-    if (! Raven.isSetup()) {
+    if (!Raven.isSetup()) {
       Raven.config(dsn).install();
     }
     let exception = new Error(e.message);
     exception.stack = e.multilineStack || e.stack || undefined;
     let rest = {};
     for (let attr in e) {
-      if (! ["name", "message", "stack", "multilineStack", "popupMessage", "version", "sentryPublicDSN", "help"].includes(attr)) {
+      if (!["name", "message", "stack", "multilineStack", "popupMessage", "version", "sentryPublicDSN", "help"].includes(attr)) {
         rest[attr] = e[attr];
       }
     }
     rest.stack = e.multilineStack || e.stack;
     Raven.captureException(exception, {
       logger: 'addon',
-      tags: {version: e.version, category: e.popupMessage},
+      tags: {version: manifest.version, category: e.popupMessage},
       message: exception.message,
       extra: rest
     });
   };
 
   catcher.registerHandler((errorObj) => {
-    if (! errorObj.noPopup) {
+    if (!errorObj.noPopup) {
       exports.showError(errorObj);
     }
     exports.reportError(errorObj);
   });
 
   return exports;
 })();
--- a/browser/extensions/screenshots/webextension/background/takeshot.js
+++ b/browser/extensions/screenshots/webextension/background/takeshot.js
@@ -1,23 +1,24 @@
 /* globals communication, shot, main, auth, catcher, analytics, browser */
 
 "use strict";
 
-this.takeshot = (function () {
+this.takeshot = (function() {
   let exports = {};
   const Shot = shot.AbstractShot;
   const { sendEvent } = analytics;
 
   communication.register("takeShot", catcher.watchFunction((sender, options) => {
     let { captureType, captureText, scroll, selectedPos, shotId, shot } = options;
     shot = new Shot(main.getBackend(), shotId, shot);
+    shot.favicon = sender.tab.favIconUrl;
     let capturePromise = Promise.resolve();
     let openedTab;
-    if (! shot.clipNames().length) {
+    if (!shot.clipNames().length) {
       // canvas.drawWindow isn't available, so we fall back to captureVisibleTab
       capturePromise = screenshotPage(selectedPos, scroll).then((dataUrl) => {
         shot.addClip({
           createdDate: Date.now(),
           image: {
             url: dataUrl,
             captureType,
             text: captureText,
@@ -95,34 +96,34 @@ this.takeshot = (function () {
       });
     }));
   }
 
   function uploadShot(shot) {
     return auth.authHeaders().then((headers) => {
       headers["content-type"] = "application/json";
       let body = JSON.stringify(shot.asJson());
-      let req = new Request(shot.jsonUrl, {
+      sendEvent("upload", "started", {eventValue: Math.floor(body.length / 1000)});
+      return fetch(shot.jsonUrl, {
         method: "PUT",
         mode: "cors",
         headers,
         body
       });
-      sendEvent("upload", "started", {eventValue: Math.floor(body.length / 1000)});
-      return fetch(req);
     }).then((resp) => {
-      if (! resp.ok) {
+      if (!resp.ok) {
         sendEvent("upload-failed", `status-${resp.status}`);
         let exc = new Error(`Response failed with status ${resp.status}`);
         exc.popupMessage = "REQUEST_ERROR";
         throw exc;
       } else {
         sendEvent("upload", "success");
       }
     }, (error) => {
       // FIXME: I'm not sure what exceptions we can expect
       sendEvent("upload-failed", "connection");
+      error.popupMessage = "CONNECTION_ERROR";
       throw error;
     });
   }
 
   return exports;
 })();
--- a/browser/extensions/screenshots/webextension/build/buildSettings.js
+++ b/browser/extensions/screenshots/webextension/build/buildSettings.js
@@ -1,6 +1,6 @@
 window.buildSettings = {
-  defaultSentryDsn: "",
+  defaultSentryDsn: "https://97d8afa496f94764ae255e739b147f4b@sentry.prod.mozaws.net/139",
   logLevel: "" || "warn"
 };
 null;
 
--- a/browser/extensions/screenshots/webextension/build/inlineSelectionCss.js
+++ b/browser/extensions/screenshots/webextension/build/inlineSelectionCss.js
@@ -11,96 +11,106 @@ window.inlineSelectionCss = `
   font-weight: 400;
   height: 40px;
   min-width: 40px;
   outline: none;
   padding: 0 10px;
   position: relative;
   text-align: center;
   text-decoration: none;
-  transition: background 150ms;
+  transition: background 150ms cubic-bezier(0.07, 0.95, 0, 1), border 150ms cubic-bezier(0.07, 0.95, 0, 1);
   user-select: none;
   white-space: nowrap; }
   .button.small, .small.highlight-button-cancel, .small.highlight-button-save, .small.highlight-button-download {
     height: 32px;
     line-height: 32px;
     padding: 0 8px; }
   .button.tiny, .tiny.highlight-button-cancel, .tiny.highlight-button-save, .tiny.highlight-button-download {
-    font-size: 12px;
-    height: 22px;
-    line-height: 12px;
-    padding: 2px 6px; }
+    font-size: 14px;
+    height: 26px;
+    border: 1px solid #c7c7c7; }
+    .button.tiny:hover, .tiny.highlight-button-cancel:hover, .tiny.highlight-button-save:hover, .tiny.highlight-button-download:hover, .button.tiny:focus, .tiny.highlight-button-cancel:focus, .tiny.highlight-button-save:focus, .tiny.highlight-button-download:focus {
+      background: #ebebeb;
+      border-color: #989898; }
+    .button.tiny:active, .tiny.highlight-button-cancel:active, .tiny.highlight-button-save:active, .tiny.highlight-button-download:active {
+      background: #dedede;
+      border-color: #989898; }
   .button.set-width--medium, .set-width--medium.highlight-button-cancel, .set-width--medium.highlight-button-save, .set-width--medium.highlight-button-download {
     max-width: 200px; }
-  .button.inline, .inline.highlight-button-cancel, .inline.highlight-button-save, .inline.highlight-button-download {
-    display: inline-block; }
   .button.block-button, .block-button.highlight-button-cancel, .block-button.highlight-button-save, .block-button.highlight-button-download {
     display: flex;
     align-items: center;
     justify-content: center;
+    box-sizing: border-box;
     border: none;
-    border-right: 1px solid rgba(0, 0, 0, 0.1);
+    border-right: 1px solid #c7c7c7;
     box-shadow: none;
     border-radius: 0;
-    height: 100%;
-    line-height: 100%;
-    padding: 0 20px;
-    margin-right: 20px;
-    flex: 0 0 155px; }
-  .button .arrow-icon, .highlight-button-cancel .arrow-icon, .highlight-button-save .arrow-icon, .highlight-button-download .arrow-icon {
-    display: inline-block;
-    position: relative;
-    top: 1px;
-    flex: 0 0 18px;
-    height: 16px;
-    opacity: .6;
-    background-image: url(../img/arrow-page-right-16.svg);
-    background-position: right center;
-    background-repeat: no-repeat; }
+    flex-shrink: 0;
+    font-size: 20px;
+    height: 100px;
+    line-height: 100%; }
+    @media (max-width: 719px) {
+      .button.block-button, .block-button.highlight-button-cancel, .block-button.highlight-button-save, .block-button.highlight-button-download {
+        justify-content: flex-start;
+        padding: 10px;
+        font-size: 16px;
+        height: 72px;
+        flex: 1 0;
+        margin-right: 10px; } }
+    .button.block-button:hover, .block-button.highlight-button-cancel:hover, .block-button.highlight-button-save:hover, .block-button.highlight-button-download:hover {
+      background: #ebebeb; }
+    .button.block-button:active, .block-button.highlight-button-cancel:active, .block-button.highlight-button-save:active, .block-button.highlight-button-download:active {
+      background: #dedede; }
 
 .inverse-color-scheme {
-  background: #383E49;
-  color: #FFF; }
+  background: #3e3d40;
+  color: #f5f5f7; }
   .inverse-color-scheme a {
-    color: #0996F8; }
-  .inverse-color-scheme .large-icon {
-    filter: invert(100%); }
+    color: #e1e1e6; }
 
 .default-color-scheme {
-  background: #f2f2f2;
-  color: #000; }
+  background: #f5f5f7;
+  color: #3e3d40; }
   .default-color-scheme a {
-    color: #0996F8; }
+    color: #009ec0; }
+
+.highlight-color-scheme {
+  background: #009ec0;
+  color: #fff; }
+  .highlight-color-scheme a {
+    color: #fff;
+    text-decoration: underline; }
 
 .button.primary, .primary.highlight-button-cancel, .highlight-button-save, .primary.highlight-button-download {
-  background-color: #0996F8;
-  color: #FFF; }
+  background-color: #009ec0;
+  color: #fff; }
   .button.primary:hover, .primary.highlight-button-cancel:hover, .highlight-button-save:hover, .primary.highlight-button-download:hover, .button.primary:focus, .primary.highlight-button-cancel:focus, .highlight-button-save:focus, .primary.highlight-button-download:focus {
-    background-color: #0681d7; }
+    background-color: #00819c; }
   .button.primary:active, .primary.highlight-button-cancel:active, .highlight-button-save:active, .primary.highlight-button-download:active {
-    background-color: #0573be; }
+    background-color: #006c83; }
 
 .button.secondary, .highlight-button-cancel, .secondary.highlight-button-save, .highlight-button-download {
-  background: #EDEDED;
-  color: #000; }
-  .button.secondary:hover, .highlight-button-cancel:hover, .secondary.highlight-button-save:hover, .highlight-button-download:hover, .button.secondary:focus, .highlight-button-cancel:focus, .secondary.highlight-button-save:focus, .highlight-button-download:focus {
-    background-color: #dbdbdb; }
-  .button.secondary:active, .highlight-button-cancel:active, .secondary.highlight-button-save:active, .highlight-button-download:active {
-    background-color: #cecece; }
+  background-color: #f5f5f7;
+  color: #3e3d40; }
+  .button.secondary:hover, .highlight-button-cancel:hover, .secondary.highlight-button-save:hover, .highlight-button-download:hover {
+    background-color: #ebebeb; }
+  .button.secondary:hover, .highlight-button-cancel:hover, .secondary.highlight-button-save:hover, .highlight-button-download:hover {
+    background-color: #dedede; }
 
 .button.warning, .warning.highlight-button-cancel, .warning.highlight-button-save, .warning.highlight-button-download {
-  color: #FFF;
+  color: #fff;
   background: #d92215; }
   .button.warning:hover, .warning.highlight-button-cancel:hover, .warning.highlight-button-save:hover, .warning.highlight-button-download:hover, .button.warning:focus, .warning.highlight-button-cancel:focus, .warning.highlight-button-save:focus, .warning.highlight-button-download:focus {
     background: #b81d12; }
   .button.warning:active, .warning.highlight-button-cancel:active, .warning.highlight-button-save:active, .warning.highlight-button-download:active {
     background: #a11910; }
 
 .subtitle-link {
-  color: #0996F8; }
+  color: #009ec0; }
 
 @keyframes fade-in {
   0% {
     opacity: 0; }
   100% {
     opacity: 1; } }
 
 @keyframes pop {
@@ -214,17 +224,17 @@ window.inlineSelectionCss = `
   height: 60px;
   right: -30px;
   width: 60px; }
 
 .mover-target:hover .mover {
   transform: scale(1.05); }
 
 .mover {
-  background-color: #FFF;
+  background-color: #fff;
   border-radius: 50%;
   box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
   height: 16px;
   opacity: 1;
   position: relative;
   transition: transform 125ms cubic-bezier(0.07, 0.95, 0, 1);
   width: 16px; }
   .small-selection .mover {
@@ -283,50 +293,42 @@ window.inlineSelectionCss = `
   align-items: center;
   justify-content: center;
   bottom: -55px;
   position: absolute;
   right: 5px;
   z-index: 6; }
   .bottom-selection .highlight-buttons {
     bottom: 5px; }
+  .left-selection .highlight-buttons {
+    right: auto;
+    left: 5px; }
 
 .highlight-button-cancel {
-  background-color: #ededed;
   background-image: url("MOZ_EXTENSION/icons/cancel.svg");
   background-position: center center;
   background-repeat: no-repeat;
   background-size: 18px 18px;
   margin: 5px;
   width: 40px; }
 
 .highlight-button-save {
   font-size: 18px;
   margin: 5px;
   min-width: 80px; }
 
 .highlight-button-download {
-  background-color: #ededed;
   background-image: url("MOZ_EXTENSION/icons/download.svg");
   background-position: center center;
   background-repeat: no-repeat;
   background-size: 18px 18px;
   display: block;
   margin: 5px;
   width: 40px; }
 
-.highlight-button-cancel,
-.highlight-button-download {
-  transition: background-color cubic-bezier(0.07, 0.95, 0, 1) 250ms; }
-  .highlight-button-cancel:hover, .highlight-button-cancel:focus, .highlight-button-cancel:active,
-  .highlight-button-download:hover,
-  .highlight-button-download:focus,
-  .highlight-button-download:active {
-    background-color: #dbdbdb; }
-
 .pixel-dimensions {
   position: absolute;
   pointer-events: none;
   font-weight: bold;
   font-family: sans-serif;
   font-size: 70%;
   color: #000;
   text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; }
@@ -339,27 +341,71 @@ window.inlineSelectionCss = `
   left: 0;
   margin: 0;
   padding: 0;
   pointer-events: none;
   position: absolute;
   top: 0;
   width: 100%; }
 
+.face-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  margin: auto;
+  width: 64px;
+  height: 64px;
+  transform: translateY(-45px); }
+
+.eye {
+  background-color: #fff;
+  width: 10.8px;
+  height: 14.6px;
+  position: absolute;
+  border-radius: 100%;
+  overflow: hidden;
+  left: 16.4px;
+  top: 19.8px; }
+
+.eyeball {
+  position: absolute;
+  width: 6px;
+  height: 6px;
+  background-color: #000;
+  border-radius: 50%;
+  left: 2.4px;
+  top: 4.3px;
+  z-index: 10; }
+
+.left {
+  margin-left: 0; }
+
+.right {
+  margin-left: 20px; }
+
+.face {
+  width: 62.4px;
+  height: 62.4px;
+  display: block;
+  background-image: url("MOZ_EXTENSION/icons/icon-welcome-face-without-eyes.svg"); }
+
 .preview-instructions {
   display: flex;
   align-items: center;
   justify-content: center;
   animation: pulse 125mm cubic-bezier(0.07, 0.95, 0, 1);
   color: #fff;
   font-family: -apple-system, BlinkMacSystemFont, sans-serif;
   font-size: 24px;
   line-height: 32px;
   text-align: center;
-  width: 400px; }
+  width: 400px;
+  margin-top: 45px; }
 
 .myshots-all-buttons-container {
   display: flex;
   flex-direction: row-reverse;
   background: #f5f5f5;
   border-radius: 1px;
   box-sizing: border-box;
   height: 80px;
@@ -407,17 +453,17 @@ window.inlineSelectionCss = `
 .myshots-button-container {
   display: flex;
   align-items: center;
   justify-content: center; }
 
 /* styleMyShotsButton test: */
 .styleMyShotsButton-bright .myshots-button {
   color: #fff;
-  background: #0996F8; }
+  background: #009ec0; }
 
 .styleMyShotsButton-bright .myshots-text-pre,
 .styleMyShotsButton-bright .myshots-text-post {
   filter: brightness(20); }
 
 /* end styleMyShotsButton test */
 @keyframes pulse {
   0% {
--- a/browser/extensions/screenshots/webextension/build/onboardingCss.js
+++ b/browser/extensions/screenshots/webextension/build/onboardingCss.js
@@ -57,17 +57,17 @@ body {
   animation: fade-in 250ms forwards cubic-bezier(0.07, 0.95, 0, 1);
   opacity: 0; }
 
 .slide {
   display: flex;
   align-items: center;
   flex-direction: column;
   justify-content: center;
-  background-color: #f2f2f2;
+  background-color: #f5f5f7;
   border-radius: 5px;
   height: 520px;
   overflow: hidden;
   width: 700px; }
   .slide .slide-image {
     background-size: 700px 378px;
     flex: 0 0 360px;
     font-size: 16px;
@@ -86,18 +86,17 @@ body {
     font-weight: 400;
     margin: 0 0 10px; }
     .slide h1 sup {
       background: #00d1e6;
       border-radius: 2px;
       color: #fff;
       font-size: 16px;
       margin-left: 5px;
-      padding: 2px;
-      text-transform: uppercase; }
+      padding: 2px; }
   .slide p {
     animation-duration: 350ms;
     font-size: 16px;
     line-height: 23px;
     margin: 0;
     width: 75%; }
   .slide .slide-content-aligner h1 {
     font-size: 34px; }
@@ -141,17 +140,17 @@ body {
 #slide-status-container {
   display: flex;
   align-items: center;
   justify-content: center;
   padding-top: 15px; }
 
 .goto-slide {
   background: transparent;
-  background-color: #f2f2f2;
+  background-color: #f5f5f7;
   border-radius: 50%;
   border: 0;
   flex: 0 0 9px;
   height: 9px;
   margin: 0 4px;
   opacity: 0.7;
   padding: 0;
   transition: height 100ms cubic-bezier(0.07, 0.95, 0, 1), opacity 100ms cubic-bezier(0.07, 0.95, 0, 1); }
@@ -177,68 +176,101 @@ body {
   margin-top: -70px;
   position: absolute;
   text-align: center;
   top: 50%;
   transition: background-color 150ms cubic-bezier(0.07, 0.95, 0, 1), background-size 250ms cubic-bezier(0.07, 0.95, 0, 1);
   width: 70px; }
 
 #prev {
+  background-image: url("MOZ_EXTENSION/icons/back.svg");
   left: 50%;
   margin-left: -385px; }
 
 #next,
 #done {
   left: 50%;
   margin-left: 315px; }
 
 #prev,
 #next,
 #done {
   background-position: center center;
   background-repeat: no-repeat;
   background-size: 20px 20px; }
+  #prev:hover,
+  #next:hover,
+  #done:hover {
+    background-color: #fff;
+    background-size: 22px 22px; }
+  #prev:active,
+  #next:active,
+  #done:active {
+    background-color: #fff;
+    background-size: 24px 24px; }
 
 #next {
+  background-image: url("MOZ_EXTENSION/icons/back.svg");
   transform: rotate(180deg); }
+  .active-slide-1 #next {
+    background-image: url("MOZ_EXTENSION/icons/back-highlight.svg"); }
 
 #skip {
   background: none;
   border: 0;
   color: #fff;
   font-size: 16px;
   left: 50%;
   margin-left: -330px;
   margin-top: 257px;
   opacity: 0.7;
   position: absolute;
   top: 50%;
   transition: opacity 100ms cubic-bezier(0.07, 0.95, 0, 1);
   z-index: 10; }
 
-#prev:hover,
-#next:hover,
-#done:hover {
-  background-color: #fff;
-  background-size: 22px 22px; }
-
-#prev:active,
-#next:active,
-#done:active {
-  background-color: #fff;
-  background-size: 24px 24px; }
-
 #skip:hover {
   opacity: 1; }
 
 .active-slide-1 #prev,
 .active-slide-4 #next {
   display: none; }
 
 #done {
+  background-image: url("MOZ_EXTENSION/icons/done.svg");
   display: none; }
 
 .active-slide-4 #done {
   display: inline-block; }
 
+/* for smaller screen sizes */
+@media screen and (max-width: 768px) {
+  .slide {
+    height: 360px;
+    width: 450px; }
+    .slide .slide-image {
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+      flex: 0 0 200px; }
+    .slide .slide-content {
+      flex: 0 0 160px; }
+      .slide .slide-content h1 {
+        font-size: 24px; }
+      .slide .slide-content p {
+        font-size: 14px;
+        line-height: 21px;
+        width: 85%; }
+      .slide .slide-content .onboarding-legal-notice {
+        font-size: 10px;
+        line-height: 16px; }
+  #skip {
+    margin-left: -205px;
+    margin-top: 177px; }
+  #prev {
+    margin-left: -260px; }
+  #next,
+  #done {
+    margin-left: 190px; } }
+
 `;
 null;
 
--- a/browser/extensions/screenshots/webextension/build/onboardingHtml.js
+++ b/browser/extensions/screenshots/webextension/build/onboardingHtml.js
@@ -41,19 +41,19 @@ window.onboardingHtml = `
           <div class="slide-content">
             <h1 data-l10n-id="tourHeaderFour"></h1>
             <p data-l10n-id="tourBodyFour"></p>
           </div>
         </div>
 
         <!-- Clickable elements should be buttons for accessibility -->
         <button id="skip" data-l10n-id="tourSkip" tabindex=1>Skip</button>
-        <button id="prev" tabindex=2 data-l10n-label-id="tourPrevious" style="background-image: url('MOZ_EXTENSION/icons/back.svg');"></button>
-        <button id="next" tabindex=3 data-l10n-label-id="tourNext" style="background-image: url('MOZ_EXTENSION/icons/back.svg');"/></button>
-        <button id="done" tabindex=4 data-l10n-label-id="tourDone" style="background-image: url('MOZ_EXTENSION/icons/done.svg')"></button>
+        <button id="prev" tabindex=2 data-l10n-label-id="tourPrevious"></button>
+        <button id="next" tabindex=3 data-l10n-label-id="tourNext"></button>
+        <button id="done" tabindex=4 data-l10n-label-id="tourDone"></button>
         <div id="slide-status-container">
           <button class="goto-slide goto-slide-1" data-number="1" tabindex=4></button>
           <button class="goto-slide goto-slide-2" data-number="2" tabindex=5></button>
           <button class="goto-slide goto-slide-3" data-number="3" tabindex=6></button>
           <button class="goto-slide goto-slide-4" data-number="4" tabindex=7></button>
         </div>
         <!-- FIXME: Need to put in privacy / etc links -->
       </div>
--- a/browser/extensions/screenshots/webextension/build/raven.js
+++ b/browser/extensions/screenshots/webextension/build/raven.js
@@ -1,9 +1,9 @@
-/*! Raven.js 3.14.0 (6b817d7) | github.com/getsentry/raven-js */
+/*! Raven.js 3.14.2 (5cf57e1) | github.com/getsentry/raven-js */
 
 /*
  * Includes TraceKit
  * https://github.com/getsentry/TraceKit
  *
  * Copyright 2017 Matt Robenolt and other contributors
  * Released under the BSD license
  * https://github.com/getsentry/raven-js/blob/master/LICENSE
@@ -150,17 +150,17 @@ function Raven() {
  * @this {Raven}
  */
 
 Raven.prototype = {
     // Hardcode version string so that raven source can be loaded directly via
     // webpack (using a build step causes webpack #1617). Grunt verifies that
     // this value matches package.json during build.
     //   See: https://github.com/getsentry/raven-js/issues/465
-    VERSION: '3.14.0',
+    VERSION: '3.14.2',
 
     debug: false,
 
     TraceKit: TraceKit, // alias to TraceKit
 
     /*
      * Configure Raven with a DSN and extra options
      *
@@ -1409,29 +1409,31 @@ Raven.prototype = {
     },
 
     /**
      * Truncate breadcrumb values (right now just URLs)
      */
     _trimBreadcrumbs: function (breadcrumbs) {
         // known breadcrumb properties with urls
         // TODO: also consider arbitrary prop values that start with (https?)?://
-        var urlprops = {to: 1, from: 1, url: 1},
+        var urlProps = ['to', 'from', 'url'],
+            urlProp,
             crumb,
             data;
 
-        for (var i = 0; i < breadcrumbs.values.length; i++) {
+        for (var i = 0; i < breadcrumbs.values.length; ++i) {
             crumb = breadcrumbs.values[i];
-            if (!crumb.hasOwnProperty('data'))
+            if (!crumb.hasOwnProperty('data') || !isObject(crumb.data))
                 continue;
 
             data = crumb.data;
-            for (var prop in urlprops) {
-                if (data.hasOwnProperty(prop)) {
-                    data[prop] = truncate(data[prop], this._globalOptions.maxUrlLength);
+            for (var j = 0; j < urlProps.length; ++j) {
+                urlProp = urlProps[j];
+                if (data.hasOwnProperty(urlProp)) {
+                    data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength);
                 }
             }
         }
     },
 
     _getHttpData: function() {
         if (!this._hasNavigator && !this._hasDocument) return;
         var httpData = {};
@@ -2122,30 +2124,32 @@ module.exports = Raven;
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 },{"3":3}],5:[function(_dereq_,module,exports){
 'use strict';
 
 function isObject(what) {
     return typeof what === 'object' && what !== null;
 }
 
-// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
+// Yanked from https://git.io/vS8DV re-used under CC0
 // with some tiny modifications
-function isError(what) {
-    var toString = {}.toString.call(what);
-    return isObject(what) &&
-        toString === '[object Error]' ||
-        toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions
-        what instanceof Error;
+function isError(value) {
+  switch ({}.toString.call(value)) {
+    case '[object Error]': return true;
+    case '[object Exception]': return true;
+    case '[object DOMException]': return true;
+    default: return value instanceof Error;
+  }
 }
 
 module.exports = {
     isObject: isObject,
     isError: isError
 };
+
 },{}],6:[function(_dereq_,module,exports){
 (function (global){
 'use strict';
 
 var utils = _dereq_(5);
 
 /*
  TraceKit - Cross brower stack traces
@@ -2463,37 +2467,16 @@ TraceKit.report = (function reportModule
  * This is okay for tracing (because you are likely to be calling
  * computeStackTrace from the function you want to be the topmost element
  * of the stack trace anyway), but not okay for logging unhandled
  * exceptions (because your catch block will likely be far away from the
  * inner function that actually caused the exception).
  *
  */
 TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
-    /**
-     * Escapes special characters, except for whitespace, in a string to be
-     * used inside a regular expression as a string literal.
-     * @param {string} text The string.
-     * @return {string} The escaped string literal.
-     */
-    function escapeRegExp(text) {
-        return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
-    }
-
-    /**
-     * Escapes special characters in a string to be used inside a regular
-     * expression as a string literal. Also ensures that HTML entities will
-     * be matched the same as their literal friends.
-     * @param {string} body The string.
-     * @return {string} The escaped string.
-     */
-    function escapeCodeAsRegExpForMatchingInsideHTML(body) {
-        return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
-    }
-
     // Contents of Exception in various browsers.
     //
     // SAFARI:
     // ex.message = Can't find variable: qq
     // ex.line = 59
     // ex.sourceId = 580238192
     // ex.sourceURL = http://...
     // ex.expressionBeginOffset = 96
--- a/browser/extensions/screenshots/webextension/build/shot.js
+++ b/browser/extensions/screenshots/webextension/build/shot.js
@@ -29,38 +29,38 @@ function isUrl(url) {
   }
   if ((/^view-source:/i).test(url)) {
     return isUrl(url.substr("view-source:".length));
   }
   return (/^https?:\/\/[a-z0-9\.\-]+[a-z0-9](:[0-9]+)?\/?/i).test(url);
 }
 
 function assertUrl(url) {
-  if (! url) {
+  if (!url) {
     throw new Error("Empty value is not URL");
   }
-  if (! isUrl(url)) {
+  if (!isUrl(url)) {
     let exc = new Error("Not a URL");
     exc.scheme = url.split(":")[0];
     throw exc;
   }
 }
 
 function assertOrigin(url) {
   assertUrl(url);
   if (url.search(/^https?:/i) != -1) {
     let match = (/^https?:\/\/[^/:]+\/?$/i).exec(url);
-    if (! match) {
+    if (!match) {
       throw new Error("Bad origin, might include path");
     }
   }
 }
 
 function originFromUrl(url) {
-  if (! url) {
+  if (!url) {
     return null;
   }
   if (url.search(/^https?:/i) == -1) {
     // Non-HTTP URLs don't have an origin
     return null;
   }
   let match = (/^https?:\/\/[^/:]+/i).exec(url);
   if (match) {
@@ -72,17 +72,17 @@ function originFromUrl(url) {
 /** Check if the given object has all of the required attributes, and no extra
     attributes exception those in optional */
 function checkObject(obj, required, optional) {
   if (typeof obj != "object" || obj === null) {
     throw new Error("Cannot check non-object: " + (typeof obj) + " that is " + JSON.stringify(obj));
   }
   required = required || [];
   for (let attr of required) {
-    if (! (attr in obj)) {
+    if (!(attr in obj)) {
       return false;
     }
   }
   optional = optional || [];
   for (let attr in obj) {
     if (required.indexOf(attr) == -1 && optional.indexOf(attr) == -1) {
       return false;
     }
@@ -141,54 +141,54 @@ function resolveUrl(base, url) {
 function deepEqual(a, b) {
   if ((a === null || a === undefined) && (b === null || b === undefined)) {
     return true;
   }
   if (typeof a != "object" || typeof b != "object") {
     return a === b;
   }
   if (Array.isArray(a)) {
-    if (! Array.isArray(b)) {
+    if (!Array.isArray(b)) {
       return false;
     }
     if (a.length != b.length) {
       return false;
     }
-    for (let i=0; i<a.length; i++) {
-      if (! deepEqual(a[i], b[i])) {
+    for (let i = 0; i < a.length; i++) {
+      if (!deepEqual(a[i], b[i])) {
         return false;
       }
     }
   }
   if (Array.isArray(b)) {
     return false;
   }
   let seen = new Set();
   for (let attr of Object.keys(a)) {
-    if (! deepEqual(a[attr], b[attr])) {
+    if (!deepEqual(a[attr], b[attr])) {
       return false;
     }
     seen.add(attr);
   }
   for (let attr of Object.keys(b)) {
-    if (! seen.has(attr)) {
-      if (! deepEqual(a[attr], b[attr])) {
+    if (!seen.has(attr)) {
+      if (!deepEqual(a[attr], b[attr])) {
         return false;
       }
     }
   }
   return true;
 }
 
 function makeRandomId() {
   // Note: this isn't for secure contexts, only for non-conflicting IDs
   let id = "";
   while (id.length < 12) {
     let num;
-    if (! id) {
+    if (!id) {
       num = Date.now() % Math.pow(36, 3);
     } else {
       num = Math.floor(Math.random() * Math.pow(36, 3));
     }
     id += num.toString(36);
   }
   return id;
 }
@@ -197,17 +197,17 @@ class AbstractShot {
 
   constructor(backend, id, attrs) {
     attrs = attrs || {};
     assert((/^[a-zA-Z0-9]+\/[a-z0-9\.-]+$/).test(id), "Bad ID (should be alphanumeric):", JSON.stringify(id));
     this._backend = backend;
     this._id = id;
     this.origin = attrs.origin || null;
     this.fullUrl = attrs.fullUrl || null;
-    if ((! attrs.fullUrl) && attrs.url) {
+    if ((!attrs.fullUrl) && attrs.url) {
       console.warn("Received deprecated attribute .url");
       this.fullUrl = attrs.url;
     }
     this.docTitle = attrs.docTitle || null;
     this.userTitle = attrs.userTitle || null;
     this.createdDate = attrs.createdDate || Date.now();
     this.favicon = attrs.favicon || null;
     this.siteName = attrs.siteName || null;
@@ -249,31 +249,31 @@ class AbstractShot {
       if (attr == "clips") {
         continue;
       }
       if (typeof json[attr] == "object" && typeof this[attr] == "object" && this[attr] !== null) {
         let val = this[attr];
         if (val.asJson) {
           val = val.asJson();
         }
-        if (! deepEqual(json[attr], val)) {
+        if (!deepEqual(json[attr], val)) {
           this[attr] = json[attr];
         }
       } else if (json[attr] !== this[attr] &&
           (json[attr] || this[attr])) {
         this[attr] = json[attr];
       }
     }
     if (json.clips) {
       for (let clipId in json.clips) {
-        if (! json.clips[clipId]) {
+        if (!json.clips[clipId]) {
           this.delClip(clipId);
-        } else if (! this.getClip(clipId)) {
+        } else if (!this.getClip(clipId)) {
           this.setClip(clipId, json.clips[clipId]);
-        } else if (! deepEqual(this.getClip(clipId).asJson(), json.clips[clipId])) {
+        } else if (!deepEqual(this.getClip(clipId).asJson(), json.clips[clipId])) {
           this.setClip(clipId, json.clips[clipId]);
         }
       }
     }
 
   }
 
   /** Returns a JSON version of this shot */
@@ -355,32 +355,31 @@ class AbstractShot {
       const excedingchars = (clipFilenameBytesSize - 246) / 2; // 251 - 5 for ellipsis "[...]"
       clipFilename = clipFilename.substring(0, clipFilename.length - excedingchars);
       clipFilename = clipFilename + '[...]';
     }
     return clipFilename + '.png';
   }
 
   get urlDisplay() {
-    if (! this.url) {
+    if (!this.url) {
       return null;
     }
     if (this.url.search(/^https?/i) != -1) {
       let txt = this.url;
       txt = txt.replace(/^[a-z]+:\/\//i, "");
       txt = txt.replace(/\/.*/, "");
       txt = txt.replace(/^www\./i, "");
       return txt;
     } else if (this.url.startsWith("data:")) {
       return "data:url";
-    } else {
-      let txt = this.url;
-      txt = txt.replace(/\?.*/, "");
-      return txt;
     }
+    let txt = this.url;
+    txt = txt.replace(/\?.*/, "");
+    return txt;
   }
 
   get viewUrl() {
     let url = this.backend + "/" + this.id;
     return url;
   }
 
   get creatingUrl() {
@@ -467,17 +466,17 @@ class AbstractShot {
     if (val) {
       val = resolveUrl(this.url, val);
     }
     this._favicon = val;
   }
 
   clipNames() {
     let names = Object.getOwnPropertyNames(this._clips);
-    names.sort(function (a, b) {
+    names.sort(function(a, b) {
       return a.sortOrder < b.sortOrder ? 1 : 0;
     });
     return names;
   }
   getClip(name) {
     return this._clips[name];
   }
   addClip(val) {
@@ -485,17 +484,17 @@ class AbstractShot {
     this.setClip(name, val);
     return name;
   }
   setClip(name, val) {
     let clip = new this.Clip(this, name, val);
     this._clips[name] = clip;
   }
   delClip(name) {
-    if (! this._clips[name]) {
+    if (!this._clips[name]) {
       throw new Error("No existing clip with id: " + name);
     }
     delete this._clips[name];
   }
   biggestClipSortOrder() {
     let biggest = 0;
     for (let clipId in this._clips) {
       biggest = Math.max(biggest, this._clips[clipId].sortOrder);
@@ -510,40 +509,40 @@ class AbstractShot {
       console.warn("Tried to update the url of a clip with no image:", clip);
     }
   }
 
   get siteName() {
     return this._siteName || null;
   }
   set siteName(val) {
-    assert(typeof val == "string" || ! val);
+    assert(typeof val == "string" || !val);
     this._siteName = val;
   }
 
   get documentSize() {
     return this._documentSize;
   }
   set documentSize(val) {
-    assert(typeof val == "object" || ! val);
+    assert(typeof val == "object" || !val);
     if (val) {
       assert(checkObject(val, ["height", "width"], "Bad attr to documentSize:", Object.keys(val)));
       assert(typeof val.height == "number");
       assert(typeof val.width == "number");
       this._documentSize = val;
     } else {
       this._documentSize = null;
     }
   }
 
   get fullScreenThumbnail() {
     return this._fullScreenThumbnail;
   }
   set fullScreenThumbnail(val) {
-    assert(typeof val == "string" || ! val);
+    assert(typeof val == "string" || !val);
     if (val) {
       assert(isUrl(val));
       this._fullScreenThumbnail = val;
     } else {
       this._fullScreenThumbnail = null;
     }
   }
 
@@ -551,17 +550,17 @@ class AbstractShot {
     return this._abTests;
   }
   set abTests(val) {
     if (val === null || val === undefined) {
       this._abTests = null;
       return;
     }
     assert(typeof val == "object", "abTests should be an object, not:", typeof val);
-    assert(! Array.isArray(val), "abTests should not be an Array");
+    assert(!Array.isArray(val), "abTests should not be an Array");
     for (let name in val) {
       assert(val[name] && typeof val[name] == "string", `abTests.${name} should be a string:`, typeof val[name]);
     }
     this._abTests = val;
   }
 
 }
 
@@ -601,23 +600,23 @@ player player:width player:height player
 class _Image {
   // FIXME: either we have to notify the shot of updates, or make
   // this read-only
   constructor(json) {
     assert(typeof json === "object", "Clip Image given a non-object", json);
     assert(checkObject(json, ["url"], ["dimensions", "title", "alt"]), "Bad attrs for Image:", Object.keys(json));
     assert(isUrl(json.url), "Bad Image url:", json.url);
     this.url = json.url;
-    assert((! json.dimensions) ||
+    assert((!json.dimensions) ||
            (typeof json.dimensions.x == "number" && typeof json.dimensions.y == "number"),
            "Bad Image dimensions:", json.dimensions);
     this.dimensions = json.dimensions;
-    assert(typeof json.title == "string" || ! json.title, "Bad Image title:", json.title);
+    assert(typeof json.title == "string" || !json.title, "Bad Image title:", json.title);
     this.title = json.title;
-    assert(typeof json.alt == "string" || ! json.alt, "Bad Image alt:", json.alt);
+    assert(typeof json.alt == "string" || !json.alt, "Bad Image alt:", json.alt);
     this.alt = json.alt;
   }
 
   asJson() {
     return jsonify(this, ["url"], ["dimensions"]);
   }
 }
 
@@ -627,17 +626,17 @@ AbstractShot.prototype.Image = _Image;
 class _Clip {
   constructor(shot, id, json) {
     this._shot = shot;
     assert(checkObject(json, ["createdDate", "image"], ["sortOrder"]), "Bad attrs for Clip:", Object.keys(json));
     assert(typeof id == "string" && id, "Bad Clip id:", id);
     this._id = id;
     this.createdDate = json.createdDate;
     if ('sortOrder' in json) {
-      assert(typeof json.sortOrder == "number" || ! json.sortOrder, "Bad Clip sortOrder:", json.sortOrder);
+      assert(typeof json.sortOrder == "number" || !json.sortOrder, "Bad Clip sortOrder:", json.sortOrder);
     }
     if ('sortOrder' in json) {
       this.sortOrder = json.sortOrder;
     } else {
       let biggestOrder = shot.biggestClipSortOrder();
       this.sortOrder = biggestOrder + 100;
     }
     this.image = json.image;
@@ -654,32 +653,32 @@ class _Clip {
   get id() {
     return this._id;
   }
 
   get createdDate() {
     return this._createdDate;
   }
   set createdDate(val) {
-    assert(typeof val == "number" || ! val, "Bad Clip createdDate:", val);
+    assert(typeof val == "number" || !val, "Bad Clip createdDate:", val);
     this._createdDate = val;
   }
 
   get image() {
     return this._image;
   }
   set image(image) {
-    if (! image) {
+    if (!image) {
       this._image = undefined;
       return;
     }
     assert(checkObject(image, ["url"], ["dimensions", "text", "location", "captureType"]), "Bad attrs for Clip Image:", Object.keys(image));
     assert(isUrl(image.url), "Bad Clip image URL:", image.url);
-    assert(image.captureType == "madeSelection" || image.captureType == "selection" || image.captureType == "visible" || image.captureType == "auto" || image.captureType == "fullPage" || ! image.captureType, "Bad image.captureType:", image.captureType);
-    assert(typeof image.text == "string" || ! image.text, "Bad Clip image text:", image.text);
+    assert(image.captureType == "madeSelection" || image.captureType == "selection" || image.captureType == "visible" || image.captureType == "auto" || image.captureType == "fullPage" || !image.captureType, "Bad image.captureType:", image.captureType);
+    assert(typeof image.text == "string" || !image.text, "Bad Clip image text:", image.text);
     if (image.dimensions) {
       assert(typeof image.dimensions.x == "number" && typeof image.dimensions.y == "number", "Bad Clip image dimensions:", image.dimensions);
     }
     assert(image.location &&
       typeof image.location.left == "number" &&
       typeof image.location.right == "number" &&
       typeof image.location.top == "number" &&
       typeof image.location.bottom == "number", "Bad Clip image pixel location:", image.location);
@@ -697,23 +696,24 @@ class _Clip {
     }
     this._image = image;
   }
 
   isDataUrl() {
     if (this.image) {
       return this.image.url.startsWith("data:");
     }
+    return false;
   }
 
   get sortOrder() {
     return this._sortOrder || null;
   }
   set sortOrder(val) {
-    assert(typeof val == "number" || ! val, "Bad Clip sortOrder:", val);
+    assert(typeof val == "number" || !val, "Bad Clip sortOrder:", val);
     this._sortOrder = val;
   }
 
 }
 
 AbstractShot.prototype.Clip = _Clip;
 
 if (typeof exports != "undefined") {
--- a/browser/extensions/screenshots/webextension/catcher.js
+++ b/browser/extensions/screenshots/webextension/catcher.js
@@ -1,25 +1,25 @@
-/* globals log */
-
 "use strict";
 
 var global = this;
 
-this.catcher = (function () {
+this.catcher = (function() {
   let exports = {};
 
   let handler;
 
   let queue = [];
 
-  exports.unhandled = function (error, info) {
+  let log = global.log;
+
+  exports.unhandled = function(error, info) {
     log.error("Unhandled error:", error, info);
     let e = makeError(error, info);
-    if (! handler) {
+    if (!handler) {
       queue.push(e);
     } else {
       handler(e);
     }
   };
 
   /** Turn an exception into an error object */
   function makeError(exc, info) {
@@ -42,36 +42,41 @@ this.catcher = (function () {
         result[attr] = info[attr];
       }
     }
     return result;
   }
 
   /** Wrap the function, and if it raises any exceptions then call unhandled() */
   exports.watchFunction = function watchFunction(func) {
-    return function () {
+    return function() {
       try {
         return func.apply(this, arguments);
       } catch (e) {
         exports.unhandled(e);
         throw e;
       }
     };
   };
 
-  exports.watchPromise = function watchPromise(promise) {
+  exports.watchPromise = function watchPromise(promise, quiet) {
     return promise.catch((e) => {
-      log.error("------Error in promise:", e);
-      log.error(e.stack);
-      exports.unhandled(makeError(e));
+      if (quiet) {
+        log.debug("------Error in promise:", e);
+        log.debug(e.stack);
+      } else {
+        log.error("------Error in promise:", e);
+        log.error(e.stack);
+        exports.unhandled(makeError(e));
+      }
       throw e;
     });
   };
 
-  exports.registerHandler = function (h) {
+  exports.registerHandler = function(h) {
     if (handler) {
       log.error("registerHandler called after handler was already registered");
       return;
     }
     handler = h;
     for (let error of queue) {
       handler(error);
     }
--- a/browser/extensions/screenshots/webextension/clipboard.js
+++ b/browser/extensions/screenshots/webextension/clipboard.js
@@ -1,16 +1,16 @@
 /* globals catcher */
 
 "use strict";
 
-this.clipboard = (function () {
+this.clipboard = (function() {
   let exports = {};
 
-  exports.copy = function (text) {
+  exports.copy = function(text) {
     let el = document.createElement("textarea");
     document.body.appendChild(el);
     el.value = text;
     el.select();
     const copied = document.execCommand("copy");
     document.body.removeChild(el);
     if (!copied) {
       catcher.unhandled(new Error("Clipboard copy failed"));
--- a/browser/extensions/screenshots/webextension/domainFromUrl.js
+++ b/browser/extensions/screenshots/webextension/domainFromUrl.js
@@ -1,29 +1,29 @@
 /** Returns the domain of a URL, but safely and in ASCII; URLs without domains
     (such as about:blank) return the scheme, Unicode domains get stripped down
     to ASCII */
 
 "use strict";
 
-this.domainFromUrl = (function () {
+this.domainFromUrl = (function() {
 
   return function urlDomainForId(location) { // eslint-disable-line no-unused-vars
     let domain = location.hostname;
     if (!domain) {
       domain = location.origin.split(":")[0];
-      if (! domain) {
+      if (!domain) {
         domain = "unknown";
       }
     }
     if (domain.search(/^[a-z0-9.\-]+$/i) === -1) {
       // Probably a unicode domain; we could use punycode but it wouldn't decode
       // well in the URL anyway.  Instead we'll punt.
       domain = domain.replace(/[^a-z0-9.\-]/ig, "");
-      if (! domain) {
+      if (!domain) {
         domain = "site";
       }
     }
     return domain;
   };
 
 })();
 null;
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/back-highlight.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#009EC0;}
+</style>
+<path id="path-1_1_" class="st0" fill="#3D3D40" d="M18.8,8.5H4.2l5.4-5.4c0.5-0.5,0.5-1.2,0-1.8s-1.2-0.5-1.8,0L0.4,8.9c-0.5,0.5-0.5,1.2,0,1.8
+	l7.5,7.5c0.2,0.2,0.5,0.4,0.9,0.4s0.6-0.1,0.9-0.4c0.5-0.5,0.5-1.2,0-1.8L4.2,11h14.5c0.8,0,1.2-0.5,1.2-1.2S19.5,8.5,18.8,8.5z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/icon-16.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>icon-16</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Onboarding" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" fill="#4D4D4D">
+            <path d="M11,2 L15,2 L15,4 L11,4 L11,2 Z M17,2 L21,2 L21,4 L17,4 L17,2 Z M14,28 L18,28 L21,28 L21,30 L14,30 L14,28 Z M28,11 L30,11 L30,15 L28,15 L28,11 Z M28,17 L30,17 L30,21 L28,21 L28,17 Z M30,3.00292933 L30,9 L28,9 L28,4.49769878 C28,4.21484375 27.7771727,4 27.5023012,4 L23,4 L23,2 L28.9970707,2 C29.5621186,2 30,2.44902676 30,3.00292933 Z M28.9970707,30 L23,30 L23,28 L27.5023012,28 C27.7851562,28 28,27.7771727 28,27.5023012 L28,23 L30,23 L30,28.9970707 C30,29.5621186 29.5509732,30 28.9970707,30 Z M9,2 L9,4 L4.49769878,4 C4.21484375,4 4,4.22595492 4,4.50468445 L4,6 L2,6 L2,3.0093689 C2,2.44335318 2.44902676,2 3.00292933,2 L9,2 Z" id="Combined-Shape"></path>
+            <path d="M7.5,18 C4.46243388,18 2,15.5375661 2,12.5 C2,9.46243388 4.46243388,7 7.5,7 C10.5375661,7 13,9.46243388 13,12.5 C13,15.5375661 10.5375661,18 7.5,18 Z M7.5,15.25 C9.01878306,15.25 10.25,14.0187831 10.25,12.5 C10.25,10.9812169 9.01878306,9.75 7.5,9.75 C5.98121694,9.75 4.75,10.9812169 4.75,12.5 C4.75,14.0187831 5.98121694,15.25 7.5,15.25 Z" id="Combined-Shape"></path>
+            <path d="M7.5,30 C4.46243388,30 2,27.5375661 2,24.5 C2,21.4624339 4.46243388,19 7.5,19 C10.5375661,19 13,21.4624339 13,24.5 C13,27.5375661 10.5375661,30 7.5,30 Z M7.5,27.25 C9.01878306,27.25 10.25,26.0187831 10.25,24.5 C10.25,22.9812169 9.01878306,21.75 7.5,21.75 C5.98121694,21.75 4.75,22.9812169 4.75,24.5 C4.75,26.0187831 5.98121694,27.25 7.5,27.25 Z" id="Combined-Shape-Copy"></path>
+            <path d="M17.5,17.1107468 C15.3660747,15.6044466 13.2177087,14.087953 11.6169865,12.9580314 C10.6291131,12.260709 8.43228761,15.6061621 9.47511461,16.3422753 L14.0318913,19.5588235 L17.5,17.1107468 Z M20.9681087,19.5588235 C23.6164424,21.4282356 25.6533664,22.8660643 25.6533664,22.8660643 C26.5557642,23.5030509 26.7709224,24.7509686 26.1339357,25.6533664 C25.4969491,26.5557642 24.2490314,26.7709224 23.3466336,26.1339357 L17.5,22.0069002 C18.6001,21.2303591 19.7896836,20.390653 20.9681087,19.5588235 Z" id="Combined-Shape" fill-rule="nonzero"></path>
+            <path d="M12.0225886,23.7556612 L25.6533664,14.1339357 C26.5557642,13.4969491 26.7709224,12.2490314 26.1339357,11.3466336 C25.4969491,10.4442358 24.2490314,10.2290776 23.3466336,10.8660643 L9.80606657,20.4241116 C8.65292225,21.2380958 10.8995145,24.5484194 12.0225886,23.7556612 Z" id="Line" fill-rule="nonzero"></path>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/icon-32.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>icon-32</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Onboarding" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" fill="#4D4D4D">
+            <path d="M11,2 L15,2 L15,4 L11,4 L11,2 Z M17,2 L21,2 L21,4 L17,4 L17,2 Z M14,28 L18,28 L21,28 L21,30 L14,30 L14,28 Z M28,11 L30,11 L30,15 L28,15 L28,11 Z M28,17 L30,17 L30,21 L28,21 L28,17 Z M30,3.00292933 L30,9 L28,9 L28,4.49769878 C28,4.21484375 27.7771727,4 27.5023012,4 L23,4 L23,2 L28.9970707,2 C29.5621186,2 30,2.44902676 30,3.00292933 Z M28.9970707,30 L23,30 L23,28 L27.5023012,28 C27.7851562,28 28,27.7771727 28,27.5023012 L28,23 L30,23 L30,28.9970707 C30,29.5621186 29.5509732,30 28.9970707,30 Z M9,2 L9,4 L4.49769878,4 C4.21484375,4 4,4.22595492 4,4.50468445 L4,6 L2,6 L2,3.0093689 C2,2.44335318 2.44902676,2 3.00292933,2 L9,2 Z" id="Combined-Shape"></path>
+            <path d="M7.5,18 C4.46243388,18 2,15.5375661 2,12.5 C2,9.46243388 4.46243388,7 7.5,7 C10.5375661,7 13,9.46243388 13,12.5 C13,15.5375661 10.5375661,18 7.5,18 Z M7.5,15.25 C9.01878306,15.25 10.25,14.0187831 10.25,12.5 C10.25,10.9812169 9.01878306,9.75 7.5,9.75 C5.98121694,9.75 4.75,10.9812169 4.75,12.5 C4.75,14.0187831 5.98121694,15.25 7.5,15.25 Z" id="Combined-Shape"></path>
+            <path d="M7.5,30 C4.46243388,30 2,27.5375661 2,24.5 C2,21.4624339 4.46243388,19 7.5,19 C10.5375661,19 13,21.4624339 13,24.5 C13,27.5375661 10.5375661,30 7.5,30 Z M7.5,27.25 C9.01878306,27.25 10.25,26.0187831 10.25,24.5 C10.25,22.9812169 9.01878306,21.75 7.5,21.75 C5.98121694,21.75 4.75,22.9812169 4.75,24.5 C4.75,26.0187831 5.98121694,27.25 7.5,27.25 Z" id="Combined-Shape-Copy"></path>
+            <path d="M17.5,17.1107468 C15.3660747,15.6044466 13.2177087,14.087953 11.6169865,12.9580314 C10.6291131,12.260709 8.43228761,15.6061621 9.47511461,16.3422753 L14.0318913,19.5588235 L17.5,17.1107468 Z M20.9681087,19.5588235 C23.6164424,21.4282356 25.6533664,22.8660643 25.6533664,22.8660643 C26.5557642,23.5030509 26.7709224,24.7509686 26.1339357,25.6533664 C25.4969491,26.5557642 24.2490314,26.7709224 23.3466336,26.1339357 L17.5,22.0069002 C18.6001,21.2303591 19.7896836,20.390653 20.9681087,19.5588235 Z" id="Combined-Shape" fill-rule="nonzero"></path>
+            <path d="M12.0225886,23.7556612 L25.6533664,14.1339357 C26.5557642,13.4969491 26.7709224,12.2490314 26.1339357,11.3466336 C25.4969491,10.4442358 24.2490314,10.2290776 23.3466336,10.8660643 L9.80606657,20.4241116 C8.65292225,21.2380958 10.8995145,24.5484194 12.0225886,23.7556612 Z" id="Line" fill-rule="nonzero"></path>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
deleted file mode 100644
index 6f84b582bce39b345cbde70c073eef92ca47e50a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/icon-highlight-32.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>icon-32</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Onboarding" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon" fill="#989898">
+            <path d="M11,2 L15,2 L15,4 L11,4 L11,2 Z M17,2 L21,2 L21,4 L17,4 L17,2 Z M14,28 L18,28 L21,28 L21,30 L14,30 L14,28 Z M28,11 L30,11 L30,15 L28,15 L28,11 Z M28,17 L30,17 L30,21 L28,21 L28,17 Z M30,3.00292933 L30,9 L28,9 L28,4.49769878 C28,4.21484375 27.7771727,4 27.5023012,4 L23,4 L23,2 L28.9970707,2 C29.5621186,2 30,2.44902676 30,3.00292933 Z M28.9970707,30 L23,30 L23,28 L27.5023012,28 C27.7851562,28 28,27.7771727 28,27.5023012 L28,23 L30,23 L30,28.9970707 C30,29.5621186 29.5509732,30 28.9970707,30 Z M9,2 L9,4 L4.49769878,4 C4.21484375,4 4,4.22595492 4,4.50468445 L4,6 L2,6 L2,3.0093689 C2,2.44335318 2.44902676,2 3.00292933,2 L9,2 Z" id="Combined-Shape"></path>
+            <path d="M7.5,18 C4.46243388,18 2,15.5375661 2,12.5 C2,9.46243388 4.46243388,7 7.5,7 C10.5375661,7 13,9.46243388 13,12.5 C13,15.5375661 10.5375661,18 7.5,18 Z M7.5,15.25 C9.01878306,15.25 10.25,14.0187831 10.25,12.5 C10.25,10.9812169 9.01878306,9.75 7.5,9.75 C5.98121694,9.75 4.75,10.9812169 4.75,12.5 C4.75,14.0187831 5.98121694,15.25 7.5,15.25 Z" id="Combined-Shape"></path>
+            <path d="M7.5,30 C4.46243388,30 2,27.5375661 2,24.5 C2,21.4624339 4.46243388,19 7.5,19 C10.5375661,19 13,21.4624339 13,24.5 C13,27.5375661 10.5375661,30 7.5,30 Z M7.5,27.25 C9.01878306,27.25 10.25,26.0187831 10.25,24.5 C10.25,22.9812169 9.01878306,21.75 7.5,21.75 C5.98121694,21.75 4.75,22.9812169 4.75,24.5 C4.75,26.0187831 5.98121694,27.25 7.5,27.25 Z" id="Combined-Shape-Copy"></path>
+            <path d="M17.5,17.1107468 C15.3660747,15.6044466 13.2177087,14.087953 11.6169865,12.9580314 C10.6291131,12.260709 8.43228761,15.6061621 9.47511461,16.3422753 L14.0318913,19.5588235 L17.5,17.1107468 Z M20.9681087,19.5588235 C23.6164424,21.4282356 25.6533664,22.8660643 25.6533664,22.8660643 C26.5557642,23.5030509 26.7709224,24.7509686 26.1339357,25.6533664 C25.4969491,26.5557642 24.2490314,26.7709224 23.3466336,26.1339357 L17.5,22.0069002 C18.6001,21.2303591 19.7896836,20.390653 20.9681087,19.5588235 Z" id="Combined-Shape" fill-rule="nonzero"></path>
+            <path d="M12.0225886,23.7556612 L25.6533664,14.1339357 C26.5557642,13.4969491 26.7709224,12.2490314 26.1339357,11.3466336 C25.4969491,10.4442358 24.2490314,10.2290776 23.3466336,10.8660643 L9.80606657,20.4241116 C8.65292225,21.2380958 10.8995145,24.5484194 12.0225886,23.7556612 Z" id="Line" fill-rule="nonzero"></path>
+        </g>
+    </g>
+</svg>
deleted file mode 100644
index d1b63f79fba34471259e6bf091011fd10d6edb3e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 395afb5abfaafa67a51c9ada8acde311f4d239e1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/icon-starred-32.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>icon-starred-32</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Onboarding" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon-starred">
+            <path d="M11,2 L15,2 L15,4 L11,4 L11,2 Z M17,2 L19,2 L19,4 L17,4 L17,2 Z M14,28 L18,28 L21,28 L21,30 L14,30 L14,28 Z M28,18 L30,18 L30,21 L28,21 L28,18 Z M28.9970707,30 L23,30 L23,28 L27.5023012,28 C27.7851562,28 28,27.7771727 28,27.5023012 L28,23 L30,23 L30,28.9970707 C30,29.5621186 29.5509732,30 28.9970707,30 Z M9,2 L9,4 L4.49769878,4 C4.21484375,4 4,4.22595492 4,4.50468445 L4,6 L2,6 L2,3.0093689 C2,2.44335318 2.44902676,2 3.00292933,2 L9,2 Z" id="Combined-Shape" fill="#4D4D4D"></path>
+            <path d="M7.5,18 C4.46243388,18 2,15.5375661 2,12.5 C2,9.46243388 4.46243388,7 7.5,7 C10.5375661,7 13,9.46243388 13,12.5 C13,15.5375661 10.5375661,18 7.5,18 Z M7.5,15.25 C9.01878306,15.25 10.25,14.0187831 10.25,12.5 C10.25,10.9812169 9.01878306,9.75 7.5,9.75 C5.98121694,9.75 4.75,10.9812169 4.75,12.5 C4.75,14.0187831 5.98121694,15.25 7.5,15.25 Z" id="Combined-Shape" fill="#4D4D4D"></path>
+            <path d="M7.5,30 C4.46243388,30 2,27.5375661 2,24.5 C2,21.4624339 4.46243388,19 7.5,19 C10.5375661,19 13,21.4624339 13,24.5 C13,27.5375661 10.5375661,30 7.5,30 Z M7.5,27.25 C9.01878306,27.25 10.25,26.0187831 10.25,24.5 C10.25,22.9812169 9.01878306,21.75 7.5,21.75 C5.98121694,21.75 4.75,22.9812169 4.75,24.5 C4.75,26.0187831 5.98121694,27.25 7.5,27.25 Z" id="Combined-Shape-Copy" fill="#4D4D4D"></path>
+            <path d="M19.5621306,18.4336316 L12.0225886,23.7556612 C10.8995145,24.5484194 8.65292225,21.2380958 9.80606657,20.4241116 L12.5318913,18.5 L9.47511461,16.3422753 C8.43228761,15.6061621 10.6291131,12.260709 11.6169865,12.9580314 L16,16.0519233 L16.2999464,15.8401964 C16.308319,16.6949285 16.7256347,17.5052335 17.4378266,18.0070314 C18.0631741,18.4476404 18.8359546,18.5940831 19.5621306,18.4336316 Z M20.9681087,19.5588235 L25.6533664,22.8660643 C26.5557642,23.5030509 26.7709224,24.7509686 26.1339357,25.6533664 C25.4969491,26.5557642 24.2490314,26.7709224 23.3466336,26.1339357 L17.5,22.0069002 C18.6001,21.2303591 19.7896836,20.390653 20.9681087,19.5588235 Z" id="Combined-Shape" fill="#4D4D4D" fill-rule="nonzero"></path>
+            <path d="M31.0298163,5.60065296 L26.6315676,4.81413617 L24.5599053,0.655603441 C24.3597524,0.253892444 23.949655,-8.95905326e-17 23.5009482,0 C23.0522413,5.97270217e-17 22.642144,0.253892444 22.441991,0.655603441 L20.3692531,4.81413617 L15.9796095,5.59850107 C15.5518938,5.67324058 15.1994617,5.97631422 15.0614174,6.38809939 C14.9233731,6.79988456 15.0218952,7.25422244 15.3180974,7.57178672 L18.4664647,10.9577899 L17.8114064,15.6489161 C17.7498225,16.0883409 17.9391303,16.5253075 18.3017873,16.7808292 C18.6644442,17.036351 19.1395182,17.0674963 19.5324134,16.8615077 L23.5004103,14.7913869 L27.4694829,16.8647355 C27.8623781,17.0707241 28.3374521,17.0395788 28.7001091,16.7840571 C29.062766,16.5285353 29.2520739,16.0915688 29.19049,15.652144 L28.534356,10.9577899 L31.6816477,7.57286266 C31.9779332,7.25540997 32.0766057,6.80113504 31.9387231,6.38932166 C31.8019739,5.98045861 31.4537759,5.67832329 31.0298163,5.60065296 Z" id="Shape" fill="#FF1AD9" fill-rule="nonzero"></path>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
deleted file mode 100644
index 99d13992568063a9323ac793c22dbfb94fa0b572..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/icons/icon-welcome-face-without-eyes.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+</style>
+<g id="Visual-design">
+	<g id="_x31_.2-Div-selection" transform="translate(-575.000000, -503.000000)">
+		<g id="Introduction" transform="translate(250.000000, 503.000000)">
+			<g id="icon-welcomeface" transform="translate(325.000000, 0.000000)">
+				<g id="Layer_1_1_">
+					<path id="Shape" class="st0" d="M11.4,0.9v2.9h-6c-0.9,0-1.5,0.8-1.5,1.5v6H0.8V3.8c0-1.7,1.4-3.1,3.1-3.1h7.6v0.2H11.4z"/>
+					<path id="Shape_1_" class="st0" d="M63.2,11.4h-3.1v-6c0-0.8-0.6-1.5-1.5-1.5h-6v-3h7.6c1.7,0,3.1,1.4,3.1,3.1L63.2,11.4
+						L63.2,11.4z"/>
+					<path id="Shape_2_" class="st0" d="M52.6,63.2v-3.1h6c0.9,0,1.5-0.6,1.5-1.5v-6h3.1v7.6c0,1.7-1.4,3.1-3.1,3.1L52.6,63.2
+						L52.6,63.2z"/>
+					<path id="Shape_3_" class="st0" d="M0.8,52.7h3.1v6c0,0.9,0.6,1.5,1.5,1.5h6v3.1H3.8c-1.7,0-3.1-1.4-3.1-3.1L0.8,52.7L0.8,52.7
+						z"/>
+					<path id="Shape_6_" class="st0" d="M33.3,49.2H33c-4.6-0.1-7.8-3.6-7.9-3.8c-0.6-0.8-0.6-2,0.1-2.7c0.8-0.8,1.9-0.6,2.6,0.1
+						c0,0,2.3,2.6,5.2,2.6c1.8,0,3.6-0.9,5.2-2.6c0.8-0.8,1.9-0.8,2.7,0s0.8,1.9,0,2.7C38.7,47.9,36,49.2,33.3,49.2z"/>
+				</g>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>
--- a/browser/extensions/screenshots/webextension/icons/menu-myshot.svg
+++ b/browser/extensions/screenshots/webextension/icons/menu-myshot.svg
@@ -1,17 +1,1 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 46 46" style="enable-background:new 0 0 46 46;" xml:space="preserve">
-<style type="text/css">
-	.st0{fill:#3E3D40;}
-</style>
-<path class="st0" d="M11,29v6c0,0.5,0.4,1,1,1h6v-7H11z"/>
-<rect x="20" y="11" class="st0" width="7" height="7"/>
-<rect x="11" y="20" class="st0" width="7" height="7"/>
-<path class="st0" d="M18,11h-6c-0.5,0-1,0.4-1,1v6h7V11z"/>
-<rect x="20" y="29" class="st0" width="7" height="7"/>
-<path class="st0" d="M35,11h-6v7h7v-6C36,11.5,35.6,11,35,11z"/>
-<path class="st0" d="M29,36h6c0.5,0,1-0.4,1-1v-6h-7V36z"/>
-<rect x="20" y="20" class="st0" width="7" height="7"/>
-<rect x="29" y="20" class="st0" width="7" height="7"/>
-</svg>
+<svg width="46" height="46" viewBox="0 0 46 46" xmlns="http://www.w3.org/2000/svg"><title>Screenshots</title><path d="M11 11.995c0-.55.455-.995.995-.995h23.01c.55 0 .995.455.995.995v23.01c0 .55-.455.995-.995.995h-23.01c-.55 0-.995-.455-.995-.995v-23.01zM11 25v-2h7v2h-7zm9-5h7v-2h-7v2zm9 5h7v-2h-7v2zm-9 4h7v-2h-7v2zm-2-18h2v25h-2V11zm9 0h2v25h-2V11z" fill="#3E3D40" fill-rule="evenodd"/></svg>
index 7f0cc5c56af63fa32d980947a5058956b9f37718..3e7696f121b7fbe3e5a58cf6eefe57aedbb2fddb
GIT binary patch
literal 35808
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
--- a/browser/extensions/screenshots/webextension/log.js
+++ b/browser/extensions/screenshots/webextension/log.js
@@ -1,17 +1,17 @@
 /* globals buildSettings */
 
 "use strict";
 
-this.log = (function () {
+this.log = (function() {
   let exports = {};
 
   const levels = ["debug", "info", "warn", "error"];
-  if (! levels.includes(buildSettings.logLevel)) {
+  if (!levels.includes(buildSettings.logLevel)) {
     console.warn("Invalid buildSettings.logLevel:", buildSettings.logLevel);
   }
   let shouldLog = {};
 
   {
     let startLogging = false;
     for (let level of levels) {
       if (buildSettings.logLevel === level) {
--- a/browser/extensions/screenshots/webextension/makeUuid.js
+++ b/browser/extensions/screenshots/webextension/makeUuid.js
@@ -1,19 +1,19 @@
 "use strict";
 
-this.makeUuid = (function () {
+this.makeUuid = (function() {
 
   // generates a v4 UUID
   return function makeUuid() { // eslint-disable-line no-unused-vars
     // get sixteen unsigned 8 bit random values
     var randomValues = window
       .crypto
       .getRandomValues(new Uint8Array(36));
 
     return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
       var i = Array.prototype.slice.call(arguments).slice(-2)[0]; // grab the `offset` parameter
-      var r = randomValues[i]%16|0, v = c === 'x' ? r : (r&0x3|0x8);
+      var r = randomValues[i] % 16|0, v = c === 'x' ? r : (r & 0x3 | 0x8);
       return v.toString(16);
     });
   };
 })();
 null;
--- a/browser/extensions/screenshots/webextension/manifest.json
+++ b/browser/extensions/screenshots/webextension/manifest.json
@@ -1,12 +1,12 @@
 {
   "manifest_version": 2,
   "name": "Firefox Screenshots",
-  "version": "6.3.0",
+  "version": "6.6.0",
   "description": "__MSG_addonDescription__",
   "author": "__MSG_addonAuthorsList__",
   "homepage_url": "https://github.com/mozilla-services/screenshots",
   "applications": {
     "gecko": {
       "id": "screenshots@mozilla.org"
     }
   },
@@ -16,18 +16,18 @@
     "32": "icons/icon-32.png",
     "48": "icons/icon-48.png",
     "64": "icons/icon-64.png",
     "128": "icons/icon-128.png",
     "256": "icons/icon-256.png"
   },
   "browser_action": {
     "default_icon": {
-      "19": "icons/icon-19.png",
-      "38": "icons/icon-38.png"
+      "16": "icons/icon-16.svg",
+      "32": "icons/icon-32.svg"
     },
     "default_title": "__MSG_contextMenuLabel__",
     "browser_style": false
   },
   "background": {
     "scripts": [
       "build/buildSettings.js",
       "log.js",
@@ -59,24 +59,26 @@
     }
   ],
   "web_accessible_resources": [
     "blank.html",
     "icons/cancel.svg",
     "icons/download.svg",
     "icons/icon-256.png",
     "icons/back.svg",
+    "icons/back-highlight.svg",
     "icons/menu-fullpage.svg",
     "icons/menu-visible.svg",
     "icons/menu-myshot.svg",
     "icons/onboarding-1.png",
     "icons/onboarding-2.png",
     "icons/onboarding-3.png",
     "icons/onboarding-4.png",
-    "icons/done.svg"
+    "icons/done.svg",
+    "icons/icon-welcome-face-without-eyes.svg"
   ],
   "permissions": [
     "activeTab",
     "downloads",
     "tabs",
     "storage",
     "notifications",
     "clipboardWrite",
--- a/browser/extensions/screenshots/webextension/onboarding/slides.html
+++ b/browser/extensions/screenshots/webextension/onboarding/slides.html
@@ -39,19 +39,19 @@
           <div class="slide-content">
             <h1 data-l10n-id="tourHeaderFour"></h1>
             <p data-l10n-id="tourBodyFour"></p>
           </div>
         </div>
 
         <!-- Clickable elements should be buttons for accessibility -->
         <button id="skip" data-l10n-id="tourSkip" tabindex=1>Skip</button>
-        <button id="prev" tabindex=2 data-l10n-label-id="tourPrevious" style="background-image: url('MOZ_EXTENSION/icons/back.svg');"></button>
-        <button id="next" tabindex=3 data-l10n-label-id="tourNext" style="background-image: url('MOZ_EXTENSION/icons/back.svg');"/></button>
-        <button id="done" tabindex=4 data-l10n-label-id="tourDone" style="background-image: url('MOZ_EXTENSION/icons/done.svg')"></button>
+        <button id="prev" tabindex=2 data-l10n-label-id="tourPrevious"></button>
+        <button id="next" tabindex=3 data-l10n-label-id="tourNext"></button>
+        <button id="done" tabindex=4 data-l10n-label-id="tourDone"></button>
         <div id="slide-status-container">
           <button class="goto-slide goto-slide-1" data-number="1" tabindex=4></button>
           <button class="goto-slide goto-slide-2" data-number="2" tabindex=5></button>
           <button class="goto-slide goto-slide-3" data-number="3" tabindex=6></button>
           <button class="goto-slide goto-slide-4" data-number="4" tabindex=7></button>
         </div>
         <!-- FIXME: Need to put in privacy / etc links -->
       </div>
--- a/browser/extensions/screenshots/webextension/onboarding/slides.js
+++ b/browser/extensions/screenshots/webextension/onboarding/slides.js
@@ -1,25 +1,24 @@
-/* globals catcher, onboardingHtml, onboardingCss, browser, util, shooter, callBackground, assertIsTrusted */
+/* globals log, catcher, onboardingHtml, onboardingCss, browser, util, shooter, callBackground, assertIsTrusted */
 
 "use strict";
 
-this.slides = (function () {
+this.slides = (function() {
   let exports = {};
 
   const { watchFunction } = catcher;
 
   let iframe;
   let doc;
   let currentSlide = 1;
   let numberOfSlides;
   let callbacks;
-  let backend;
 
-  exports.display = function (addCallbacks) {
+  exports.display = function(addCallbacks) {
     if (iframe) {
       throw new Error("Attemted to call slides.display() twice");
     }
     return new Promise((resolve, reject) => {
       callbacks = addCallbacks;
       // FIXME: a lot of this iframe logic is in ui.js; maybe move to util.js
       iframe = document.createElement("iframe");
       iframe.src = browser.extension.getURL("blank.html");
@@ -41,36 +40,31 @@ this.slides = (function () {
           html,
           "text/html"
         );
         doc = iframe.contentDocument;
         doc.replaceChild(
           doc.adoptNode(parsedDom.documentElement),
           doc.documentElement
         );
-        doc.addEventListener("keyup", onKeyUp, false);
-        callBackground("getBackend").then((backendResult) => {
-          backend = backendResult;
-          localizeText(doc);
-          activateSlide(doc);
-          resolve();
-        }).catch((error) => {
-          // Handled in communication.js
-        });
+        doc.addEventListener("keyup", onKeyUp);
+        localizeText(doc);
+        activateSlide(doc);
+        resolve();
       });
       document.body.appendChild(iframe);
       iframe.focus();
-      window.addEventListener("resize", onResize, false);
+      window.addEventListener("resize", onResize);
     });
   };
 
-  exports.remove = exports.unload = function () {
-    window.removeEventListener("resize", onResize, false);
+  exports.remove = exports.unload = function() {
+    window.removeEventListener("resize", onResize);
     if (doc) {
-      doc.removeEventListener("keyup", onKeyUp, false);
+      doc.removeEventListener("keyup", onKeyUp);
     }
     util.removeNode(iframe);
     iframe = doc = null;
     currentSlide = 1;
     numberOfSlides = undefined;
     callbacks = undefined;
   };
 
@@ -82,117 +76,136 @@ this.slides = (function () {
       el.textContent = text;
     }
     els = doc.querySelectorAll("[data-l10n-label-id]");
     for (let el of els) {
       let id = el.getAttribute("data-l10n-label-id");
       let text = browser.i18n.getMessage(id);
       el.setAttribute("aria-label", text);
     }
-    // termsAndPrivacyNotice is a more complicated substitution:
+    // termsAndPrivacyNoticeCloudServices is a more complicated substitution:
     let termsContainer = doc.querySelector(".onboarding-legal-notice");
     termsContainer.innerHTML = "";
-    let termsSentinal = "__TERMS__";
-    let privacySentinal = "__PRIVACY__";
-    let sentinalSplitter = "!!!";
+    let termsSentinel = "__TERMS__";
+    let privacySentinel = "__PRIVACY__";
+    let sentinelSplitter = "!!!";
     let linkTexts = {
-      [termsSentinal]: browser.i18n.getMessage("termsAndPrivacyNoticeTermsLink"),
-      [privacySentinal]: browser.i18n.getMessage("termsAndPrivacyNoticyPrivacyLink")
+      [termsSentinel]: browser.i18n.getMessage("termsAndPrivacyNoticeTermsLink"),
+      [privacySentinel]: browser.i18n.getMessage("termsAndPrivacyNoticyPrivacyLink")
     };
     let linkUrls = {
-      [termsSentinal]: "https://www.mozilla.org/about/legal/terms/services/",
-      [privacySentinal]: "https://www.mozilla.org/privacy/firefox-cloud/"
+      [termsSentinel]: "https://www.mozilla.org/about/legal/terms/services/",
+      [privacySentinel]: "https://www.mozilla.org/privacy/firefox-cloud/"
     };
     let text = browser.i18n.getMessage(
-      "termsAndPrivacyNotice",
-      [sentinalSplitter + termsSentinal + sentinalSplitter,
-       sentinalSplitter + privacySentinal + sentinalSplitter]);
-    let parts = text.split(sentinalSplitter);
+      "termsAndPrivacyNoticeCloudServices",
+      [sentinelSplitter + termsSentinel + sentinelSplitter,
+       sentinelSplitter + privacySentinel + sentinelSplitter]);
+    let parts = text.split(sentinelSplitter);
     for (let part of parts) {
       let el;
-      if (part === termsSentinal || part === privacySentinal) {
+      if (part === termsSentinel || part === privacySentinel) {
         el = doc.createElement("a");
         el.href = linkUrls[part];
         el.textContent = linkTexts[part];
         el.target = "_blank";
+        el.id = (part === termsSentinel) ? "terms" : "privacy";
       } else {
         el = doc.createTextNode(part);
       }
       termsContainer.appendChild(el);
     }
   }
 
   function activateSlide(doc) {
     numberOfSlides = parseInt(doc.querySelector("[data-number-of-slides]").getAttribute("data-number-of-slides"), 10);
     doc.querySelector("#next").addEventListener("click", watchFunction(assertIsTrusted(() => {
       shooter.sendEvent("navigate-slide", "next");
       next();
-    })), false);
+    })));
     doc.querySelector("#prev").addEventListener("click", watchFunction(assertIsTrusted(() => {
       shooter.sendEvent("navigate-slide", "prev");
       prev();
-    })), false);
+    })));
     for (let el of doc.querySelectorAll(".goto-slide")) {
       el.addEventListener("click", watchFunction(assertIsTrusted((event) => {
         shooter.sendEvent("navigate-slide", "goto");
         let el = event.target;
         let index = parseInt(el.getAttribute("data-number"), 10);
         setSlide(index);
-      })), false);
+      })));
     }
     doc.querySelector("#skip").addEventListener("click", watchFunction(assertIsTrusted((event) => {
       shooter.sendEvent("cancel-slides", "skip");
       callbacks.onEnd();
-    })), false);
+    })));
     doc.querySelector("#done").addEventListener("click", watchFunction(assertIsTrusted((event) => {
       shooter.sendEvent("finish-slides", "done");
       callbacks.onEnd();
-    })), false);
+    })));
+    // Note: e10s breaks the terms and privacy anchor tags. Work around this by
+    // manually opening the correct URLs on click until bug 1357589 is fixed.
+    doc.querySelector("#terms").addEventListener("click", watchFunction(assertIsTrusted((event) => {
+      event.preventDefault();
+      callBackground("openTermsPage");
+    })));
+    doc.querySelector("#privacy").addEventListener("click", watchFunction(assertIsTrusted((event) => {
+      event.preventDefault();
+      callBackground("openPrivacyPage");
+    })));
     setSlide(1);
   }
 
   function next() {
     setSlide(currentSlide + 1);
   }
 
   function prev() {
     setSlide(currentSlide - 1);
   }
 
-  const onResize = catcher.watchFunction(function () {
-    if (! iframe) {
+  const onResize = catcher.watchFunction(function() {
+    if (!iframe) {
       log.warn("slides onResize called when iframe is not setup");
       return;
     }
     updateIframeSize();
   });
 
   function updateIframeSize() {
     iframe.style.height = window.innerHeight + "px";
     iframe.style.width = window.innerWidth + "px";
   }
 
-  const onKeyUp = catcher.watchFunction(assertIsTrusted(function (event) {
+  const onKeyUp = catcher.watchFunction(assertIsTrusted(function(event) {
     if ((event.key || event.code) === "Escape") {
       shooter.sendEvent("cancel-slides", "keyboard-escape");
       callbacks.onEnd();
     }
+    if ((event.key || event.code) === "ArrowRight") {
+      shooter.sendEvent("navigate-slide", "keyboard-arrowright");
+      next();
+    }
+    if ((event.key || event.code) === "ArrowLeft") {
+      shooter.sendEvent("navigate-slide", "keyboard-arrowleft");
+      prev();
+    }
   }));
 
   function setSlide(index) {
     if (index < 1) {
       index = 1;
     }
     if (index > numberOfSlides) {
       index = numberOfSlides;
     }
     shooter.sendEvent("visited-slide", `slide-${index}`);
     currentSlide = index;
     let slideEl = doc.querySelector("#slide-container");
-    for (let i=1; i<=numberOfSlides; i++) {
+    for (let i = 1; i <= numberOfSlides; i++) {
       let className = `active-slide-${i}`;
       if (i == currentSlide) {
         slideEl.classList.add(className);
       } else {
         slideEl.classList.remove(className);
       }
     }
   }
--- a/browser/extensions/screenshots/webextension/randomString.js
+++ b/browser/extensions/screenshots/webextension/randomString.js
@@ -1,14 +1,14 @@
 /* exported randomString */
 
 "use strict";
 
 this.randomString = function randomString(length, chars) {
   let randomStringChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
   chars = chars || randomStringChars;
   let result = "";
-  for (let i=0; i<length; i++) {
+  for (let i = 0; i < length; i++) {
     result += chars[Math.floor(Math.random() * chars.length)]
   }
   return result;
 }
 null;
--- a/browser/extensions/screenshots/webextension/selector/callBackground.js
+++ b/browser/extensions/screenshots/webextension/selector/callBackground.js
@@ -1,19 +1,25 @@
 /* globals browser, log */
 
 "use strict";
 
-this.callBackground = function callBackground (funcName, ...args) {
+this.callBackground = function callBackground(funcName, ...args) {
   return browser.runtime.sendMessage({funcName, args}).then((result) => {
     if (result.type === "success") {
       return result.value;
     } else if (result.type === "error") {
       let exc = new Error(result.message);
       exc.name = "BackgroundError";
+      if ('errorCode' in result) {
+        exc.errorCode = result.errorCode;
+      }
+      if ('popupMessage' in result) {
+        exc.popupMessage = result.popupMessage;
+      }
       throw exc;
     } else {
       log.error("Unexpected background result:", result);
       let exc = new Error(`Bad response type from background page: ${result.type || undefined}`);
       exc.resultType = result.type || "undefined";
       throw exc;
     }
   });
--- a/browser/extensions/screenshots/webextension/selector/documentMetadata.js
+++ b/browser/extensions/screenshots/webextension/selector/documentMetadata.js
@@ -1,11 +1,11 @@
 "use strict";
 
-this.documentMetadata = (function () {
+this.documentMetadata = (function() {
 
   function findSiteName() {
     let el = document.querySelector("meta[property='og:site_name']");
     if (el) {
       return el.getAttribute("content");
     }
     // nytimes.com uses this property:
     el = document.querySelector("meta[name='cre']");
@@ -37,17 +37,17 @@ this.documentMetadata = (function () {
       if (elems.length > 1) {
         value = [];
         for (let elem of elems) {
           let v = elem.getAttribute("content");
           if (v) {
             value.push(v);
           }
         }
-        if (! value.length) {
+        if (!value.length) {
           value = null;
         }
       } else if (elems.length === 1) {
         value = elems[0].getAttribute("content");
       }
       if (value) {
         openGraph[prop] = value;
       }
--- a/browser/extensions/screenshots/webextension/selector/shooter.js
+++ b/browser/extensions/screenshots/webextension/selector/shooter.js
@@ -1,22 +1,24 @@
-/* globals callBackground, documentMetadata, uicontrol, util, ui, catcher */
-/* globals XMLHttpRequest, window, location, alert, console, domainFromUrl, randomString */
-/* globals clipboard, document, setTimeout, location */
+/* globals global, documentMetadata, util, uicontrol, ui, catcher */
+/* globals XMLHttpRequest, window, location, alert, domainFromUrl, randomString */
+/* globals document, setTimeout, location */
 
 "use strict";
 
-this.shooter = (function () { // eslint-disable-line no-unused-vars
+this.shooter = (function() { // eslint-disable-line no-unused-vars
   let exports = {};
   const { AbstractShot } = window.shot;
 
   const RANDOM_STRING_LENGTH = 16;
   let backend;
   let shot;
   let supportsDrawWindow;
+  const callBackground = global.callBackground;
+  const clipboard = global.clipboard;
 
   function regexpEscape(str) {
     // http://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript
     return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
   }
 
   function sanitizeError(data) {
     const href = new RegExp(regexpEscape(window.location.href), 'g');
@@ -30,21 +32,21 @@ this.shooter = (function () { // eslint-
 
   catcher.registerHandler((errorObj) => {
     callBackground("reportError", sanitizeError(errorObj));
   });
 
   {
     let canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
     let ctx = canvas.getContext('2d');
-    supportsDrawWindow = !! ctx.drawWindow;
+    supportsDrawWindow = !!ctx.drawWindow;
   }
 
   function screenshotPage(selectedPos) {
-    if (! supportsDrawWindow) {
+    if (!supportsDrawWindow) {
       return null;
     }
     let height = selectedPos.bottom - selectedPos.top;
     let width = selectedPos.right - selectedPos.left;
     let canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
     canvas.width = width * window.devicePixelRatio;
     canvas.height = height * window.devicePixelRatio;
     let ctx = canvas.getContext('2d');
@@ -57,20 +59,22 @@ this.shooter = (function () { // eslint-
     } finally {
       ui.iframe.unhide();
     }
     return canvas.toDataURL();
   }
 
   let isSaving = null;
 
-  exports.takeShot = function (captureType, selectedPos) {
+  exports.takeShot = function(captureType, selectedPos) {
     // isSaving indicates we're aleady in the middle of saving
     // we use a timeout so in the case of a failure the button will
     // still start working again
+    const uicontrol = global.uicontrol;
+    let deactivateAfterFinish = true;
     if (isSaving) {
       return;
     }
     isSaving = setTimeout(() => {
       isSaving = null;
     }, 1000);
     selectedPos = selectedPos.asJson();
     let captureText = util.captureEnclosedText(selectedPos);
@@ -101,44 +105,54 @@ this.shooter = (function () { // eslint-
       },
       selectedPos,
       shotId: shot.id,
       shot: shot.asJson()
     }).then((url) => {
       const copied = clipboard.copy(url);
       return callBackground("openShot", { url, copied });
     }, (error) => {
+      if ('popupMessage' in error && (error.popupMessage == "REQUEST_ERROR" || error.popupMessage == 'CONNECTION_ERROR')) {
+        // The error has been signaled to the user, but unlike other errors (or
+        // success) we should not abort the selection
+        deactivateAfterFinish = false;
+        return;
+      }
       if (error.name != "BackgroundError") {
         // BackgroundError errors are reported in the Background page
         throw error;
       }
-    }).then(() => uicontrol.deactivate()));
+    }).then(() => {
+      if (deactivateAfterFinish) {
+        uicontrol.deactivate();
+      }
+    }));
   };
 
-  exports.downloadShot = function (selectedPos) {
+  exports.downloadShot = function(selectedPos) {
     let dataUrl = screenshotPage(selectedPos);
     let promise = Promise.resolve(dataUrl);
-    if (! dataUrl) {
+    if (!dataUrl) {
       promise = callBackground(
         "screenshotPage",
         selectedPos.asJson(),
         {
           scrollX: window.scrollX,
           scrollY: window.scrollY,
           innerHeight: window.innerHeight,
           innerWidth: window.innerWidth
         });
     }
     catcher.watchPromise(promise.then((dataUrl) => {
       ui.triggerDownload(dataUrl, shot.filename);
       uicontrol.deactivate();
     }));
   };
 
-  exports.sendEvent = function (...args) {
+  exports.sendEvent = function(...args) {
     callBackground("sendEvent", ...args);
   };
 
   shot = new AbstractShot(
     backend,
     randomString(RANDOM_STRING_LENGTH) + "/" + domainFromUrl(location),
     {
       origin: window.shot.originFromUrl(location.href)
--- a/browser/extensions/screenshots/webextension/selector/ui.js
+++ b/browser/extensions/screenshots/webextension/selector/ui.js
@@ -1,14 +1,14 @@
-/* globals window, document, console, browser */
-/* globals util, catcher, inlineSelectionCss, callBackground, assertIsTrusted */
+/* globals window, document, browser */
+/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted */
 
 "use strict";
 
-this.ui = (function () { // eslint-disable-line no-unused-vars
+this.ui = (function() { // eslint-disable-line no-unused-vars
   let exports = {};
   const SAVE_BUTTON_HEIGHT = 50;
 
   const { watchFunction } = catcher;
 
   // The <body> tag itself can have margins and offsets, which need to be used when
   // setting the position of the boxEl.
   function getBodyRect() {
@@ -26,17 +26,17 @@ this.ui = (function () { // eslint-disab
     // *not* necessary on http://patriciogonzalezvivo.com/2015/thebookofshaders/
     // (actually causes mis-selection there)
     // *is* necessary on http://atirip.com/2015/03/17/sorry-sad-state-of-matrix-transforms-in-browsers/
     cached = {top: 0, bottom: 0, left: 0, right: 0};
     getBodyRect.cached = cached;
     return cached;
   }
 
-  exports.isHeader = function (el) {
+  exports.isHeader = function(el) {
     while (el) {
       if (el.classList &&
           (el.classList.contains("myshots-button") ||
            el.classList.contains("visible") ||
            el.classList.contains("full-page"))) {
         return true;
       }
       el = el.parentNode;
@@ -44,17 +44,17 @@ this.ui = (function () { // eslint-disab
     return false;
   }
 
   let substitutedCss = inlineSelectionCss.replace(/MOZ_EXTENSION([^\"]+)/g, (match, filename) => {
     return browser.extension.getURL(filename);
   });
 
   function makeEl(tagName, className) {
-    if (! iframe.document()) {
+    if (!iframe.document()) {
       throw new Error("Attempted makeEl before iframe was initialized");
     }
     let el = iframe.document().createElement(tagName);
     if (className) {
       el.className = className;
     }
     return el;
   }
@@ -73,19 +73,19 @@ this.ui = (function () { // eslint-disab
     addClassName: "",
     sizeTracking: {
       timer: null,
       windowDelayer: null,
       lastHeight: null,
       lastWidth: null
     },
     document: null,
-    display: function (installHandlerOnDocument) {
+    display(installHandlerOnDocument) {
       return new Promise((resolve, reject) => {
-        if (! this.element) {
+        if (!this.element) {
           this.element = document.createElement("iframe");
           this.element.src = browser.extension.getURL("blank.html");
           this.element.id = "firefox-screenshots-selection-iframe";
           this.element.style.display = "none";
           this.element.style.zIndex = "99999999999";
           this.element.style.border = "none";
           this.element.style.position = "absolute";
           this.element.style.top = "0";
@@ -109,29 +109,29 @@ this.ui = (function () { // eslint-disab
           });
           document.body.appendChild(this.element);
         } else {
           resolve();
         }
       });
     },
 
-    hide: function () {
+    hide() {
       this.element.style.display = "none";
       this.stopSizeWatch();
     },
 
-    unhide: function () {
+    unhide() {
       this.updateElementSize();
       this.element.style.display = "";
       this.initSizeWatch();
       this.element.focus();
     },
 
-    updateElementSize: function (force) {
+    updateElementSize(force) {
       // Note: if someone sizes down the page, then the iframe will keep the
       // document from naturally shrinking.  We use force to temporarily hide
       // the element so that we can tell if the document shrinks
       const visible = this.element.style.display !== "none";
       if (force && visible) {
         this.element.style.display = "none";
       }
       let height = Math.max(
@@ -154,85 +154,90 @@ this.ui = (function () { // eslint-disab
         this.sizeTracking.lastWidth = width;
         this.element.style.width = width + "px";
       }
       if (force && visible) {
         this.element.style.display = "";
       }
     },
 
-    initSizeWatch: function () {
+    initSizeWatch() {
       this.stopSizeWatch();
       this.sizeTracking.timer = setInterval(watchFunction(this.updateElementSize.bind(this)), 2000);
       window.addEventListener("resize", this.onResize, true);
     },
 
-    stopSizeWatch: function () {
+    stopSizeWatch() {
       if (this.sizeTracking.timer) {
         clearTimeout(this.sizeTracking.timer);
         this.sizeTracking.timer = null;
       }
       if (this.sizeTracking.windowDelayer) {
         clearTimeout(this.sizeTracking.windowDelayer);
         this.sizeTracking.windowDelayer = null;
       }
       this.sizeTracking.lastHeight = this.sizeTracking.lastWidth = null;
       window.removeEventListener("resize", this.onResize, true);
     },
 
-    getElementFromPoint: function (x, y) {
+    getElementFromPoint(x, y) {
       this.element.style.pointerEvents = "none";
       let el;
       try {
         el = document.elementFromPoint(x, y);
       } finally {
         this.element.style.pointerEvents = "";
       }
       return el;
     },
 
-    remove: function () {
+    remove() {
       this.stopSizeWatch();
       util.removeNode(this.element);
       this.element = this.document = null;
     }
   };
 
   iframeSelection.onResize = watchFunction(onResize.bind(iframeSelection));
 
   let iframePreSelection = exports.iframePreSelection = {
     element: null,
     document: null,
     sizeTracking: {
       windowDelayer: null
     },
-    display: function (installHandlerOnDocument, standardOverlayCallbacks) {
+    display(installHandlerOnDocument, standardOverlayCallbacks) {
       return new Promise((resolve, reject) => {
-        if (! this.element) {
+        if (!this.element) {
           this.element = document.createElement("iframe");
           this.element.src = browser.extension.getURL("blank.html");
           this.element.id = "firefox-screenshots-preselection-iframe";
           this.element.style.zIndex = "99999999999";
           this.element.style.border = "none";
           this.element.style.position = "fixed";
           this.element.style.top = "0";
           this.element.style.left = "0";
           this.element.style.margin = "0";
           this.element.scrolling = "no";
           this.updateElementSize();
           this.element.onload = watchFunction(() => {
             this.document = this.element.contentDocument;
-            this.document.documentElement.innerHTML= `
+            this.document.documentElement.innerHTML = `
                <head>
                 <style>${substitutedCss}</style>
                 <title></title>
                </head>
                <body>
                  <div class="preview-overlay">
                    <div class="fixed-container">
+                     <div class="face-container">
+                       <div class="eye left"><div class="eyeball"></div></div>
+                       <div class="eye right"><div class="eyeball"></div></div>
+                       <div class="face"></div>
+                     </div>
                      <div class="preview-instructions"></div>
                      <div class="myshots-all-buttons-container">
                        <button class="myshots-button myshots-link" tabindex="1"></button>
                        <div class="spacer"></div>
                        <button class="myshots-button visible" tabindex="2"></button>
                        <button class="myshots-button full-page" tabindex="3"></button>
                      </div>
                    </div>
@@ -243,128 +248,128 @@ this.ui = (function () { // eslint-disab
               this.document.body.className = this.addClassName;
             }
             const overlay = this.document.querySelector(".preview-overlay");
             overlay.querySelector(".preview-instructions").textContent = browser.i18n.getMessage("screenshotInstructions");
             overlay.querySelector(".myshots-link").textContent = browser.i18n.getMessage("myShotsLink");
             overlay.querySelector(".visible").textContent = browser.i18n.getMessage("saveScreenshotVisibleArea");
             overlay.querySelector(".full-page").textContent = browser.i18n.getMessage("saveScreenshotFullPage");
             overlay.querySelector(".myshots-button").addEventListener(
-              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onOpenMyShots)), false);
+              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onOpenMyShots)));
             overlay.querySelector(".visible").addEventListener(
-              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)), false);
+              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)));
             overlay.querySelector(".full-page").addEventListener(
-              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)), false);
+              "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)));
             resolve();
           });
           document.body.appendChild(this.element);
           this.unhide();
         } else {
           resolve();
         }
       });
     },
 
-    updateElementSize: function () {
+    updateElementSize() {
       this.element.style.height = window.innerHeight + "px";
       this.element.style.width = window.innerWidth + "px";
     },
 
-    hide: function () {
-      window.removeEventListener("scroll", this.onScroll, false);
+    hide() {
+      window.removeEventListener("scroll", this.onScroll);
       window.removeEventListener("resize", this.onResize, true);
       if (this.element) {
         this.element.style.display = "none";
       }
     },
 
-    unhide: function () {
+    unhide() {
       this.updateElementSize();
-      window.addEventListener("scroll", this.onScroll, false);
+      window.addEventListener("scroll", this.onScroll);
       window.addEventListener("resize", this.onResize, true);
       this.element.style.display = "";
       this.element.focus();
     },
 
-    onScroll: function () {
+    onScroll() {
       exports.HoverBox.hide();
     },
 
-    getElementFromPoint: function (x, y) {
+    getElementFromPoint(x, y) {
       this.element.style.pointerEvents = "none";
       let el;
       try {
         el = document.elementFromPoint(x, y);
       } finally {
         this.element.style.pointerEvents = "";
       }
       return el;
     },
 
-    remove: function () {
+    remove() {
       this.hide();
       util.removeNode(this.element);
       this.element = null;
       this.document = null;
     }
   };
 
   iframePreSelection.onResize = watchFunction(onResize.bind(iframePreSelection));
 
   let iframe = exports.iframe = {
     currentIframe: iframePreSelection,
-    display: function (installHandlerOnDocument, standardOverlayCallbacks) {
+    display(installHandlerOnDocument, standardOverlayCallbacks) {
       return iframeSelection.display(installHandlerOnDocument)
         .then(() => iframePreSelection.display(installHandlerOnDocument, standardOverlayCallbacks));
     },
 
-    hide: function () {
+    hide() {
       this.currentIframe.hide();
     },
 
-    unhide: function () {
+    unhide() {
       this.currentIframe.unhide();
     },
 
-    getElementFromPoint: function (x, y) {
+    getElementFromPoint(x, y) {
       return this.currentIframe.getElementFromPoint(x, y);
     },
 
-    remove: function () {
+    remove() {
       iframeSelection.remove();
       iframePreSelection.remove();
     },
 
-    document: function () {
+    document() {
       return this.currentIframe.document;
     },
 
-    useSelection: function () {
+    useSelection() {
       if (this.currentIframe === iframePreSelection) {
         this.hide();
       }
       this.currentIframe = iframeSelection;
       this.unhide();
     },
 
-    usePreSelection: function () {
+    usePreSelection() {
       if (this.currentIframe === iframeSelection) {
         this.hide();
       }
       this.currentIframe = iframePreSelection;
       this.unhide();
     }
   };
 
   let movements = ["topLeft", "top", "topRight", "left", "right", "bottomLeft", "bottom", "bottomRight"];
 
   /** Creates the selection box */
   exports.Box = {
 
-    display: function (pos, callbacks) {
+    display(pos, callbacks) {
       this._createEl();
       if (callbacks !== undefined && callbacks.cancel) {
         // We use onclick here because we don't want addEventListener
         // to add multiple event handlers to the same button
         this.cancel.onclick = watchFunction(assertIsTrusted(callbacks.cancel));
         this.cancel.style.display = "";
       } else {
         this.cancel.style.display = "none";
@@ -410,48 +415,55 @@ this.ui = (function () { // eslint-disab
 
       // if the selection bounding box is w/in SAVE_BUTTON_HEIGHT px of the bottom of
       // the window, flip controls into the box
       if (pos.bottom > ((winBottom + pageYOffset) - SAVE_BUTTON_HEIGHT)) {
         this.el.classList.add("bottom-selection");
       } else {
         this.el.classList.remove("bottom-selection");
       }
+
+      if (pos.right < 200) {
+        this.el.classList.add("left-selection");
+      } else {
+        this.el.classList.remove("left-selection");
+      }
+
       this.el.style.top = (pos.top - bodyRect.top) + "px";
       this.el.style.left = (pos.left - bodyRect.left) + "px";
       this.el.style.height = (pos.bottom - pos.top - bodyRect.top) + "px";
       this.el.style.width = (pos.right - pos.left - bodyRect.left) + "px";
       this.bgTop.style.top = "0px";
       this.bgTop.style.height = (pos.top - bodyRect.top) + "px";
       this.bgTop.style.left = "0px";
       this.bgTop.style.width = docWidth + "px";
       this.bgBottom.style.top = (pos.bottom - bodyRect.top) + "px";
       this.bgBottom.style.height = docHeight - (pos.bottom - bodyRect.top) + "px";
       this.bgBottom.style.left = "0px";
       this.bgBottom.style.width = docWidth + "px";
       this.bgLeft.style.top = (pos.top - bodyRect.top) + "px";
-      this.bgLeft.style.height = pos.bottom - pos.top  + "px";
+      this.bgLeft.style.height = pos.bottom - pos.top + "px";
       this.bgLeft.style.left = "0px";
       this.bgLeft.style.width = (pos.left - bodyRect.left) + "px";
       this.bgRight.style.top = (pos.top - bodyRect.top) + "px";
       this.bgRight.style.height = pos.bottom - pos.top + "px";
       this.bgRight.style.left = (pos.right - bodyRect.left) + "px";
       this.bgRight.style.width = docWidth - (pos.right - bodyRect.left) + "px";
     },
 
-    remove: function () {
+    remove() {
       for (let name of ["el", "bgTop", "bgLeft", "bgRight", "bgBottom"]) {
         if (name in this) {
           util.removeNode(this[name]);
           this[name] = null;
         }
       }
     },
 
-    _createEl: function () {
+    _createEl() {
       let boxEl = this.el;
       if (boxEl) {
         return;
       }
       boxEl = makeEl("div", "highlight");
       let buttons = makeEl("div", "highlight-buttons");
       let cancel = makeEl("button", "highlight-button-cancel");
       cancel.title = browser.i18n.getMessage("cancelScreenshot");
@@ -480,17 +492,17 @@ this.ui = (function () { // eslint-disab
       this.bgRight = makeEl("div", "bghighlight");
       iframe.document().body.appendChild(this.bgRight);
       this.bgBottom = makeEl("div", "bghighlight");
       iframe.document().body.appendChild(this.bgBottom);
       iframe.document().body.appendChild(boxEl);
       this.el = boxEl;
     },
 
-    draggerDirection: function (target) {
+    draggerDirection(target) {
       while (target) {
         if (target.nodeType == document.ELEMENT_NODE) {
           if (target.classList.contains("mover-target")) {
             for (let name of movements) {
               if (target.classList.contains("direction-" + name)) {
                 return name;
               }
             }
@@ -498,30 +510,30 @@ this.ui = (function () { // eslint-disab
             log.warn("Got mover-target that wasn't a specific direction");
           }
         }
         target = target.parentNode;
       }
       return null;
     },
 
-    isSelection: function (target) {
+    isSelection(target) {
       while (target) {
         if (target.tagName === "BUTTON") {
           return false;
         }
         if (target.nodeType == document.ELEMENT_NODE && target.classList.contains("highlight")) {
           return true;
         }
         target = target.parentNode;
       }
       return false;
     },
 
-    isControl: function (target) {
+    isControl(target) {
       while (target) {
         if (target.nodeType === document.ELEMENT_NODE && target.classList.contains("highlight-buttons")) {
           return true;
         }
         target = target.parentNode;
       }
       return false;
     },
@@ -532,78 +544,78 @@ this.ui = (function () { // eslint-disab
     boxRightEl: null,
     boxBottomEl: null
   };
 
   exports.HoverBox = {
 
     el: null,
 
-    display: function (rect) {
-      if (! this.el) {
+    display(rect) {
+      if (!this.el) {
         this.el = makeEl("div", "hover-highlight");
         iframe.document().body.appendChild(this.el);
       }
       this.el.style.display = "";
       this.el.style.top = (rect.top - 1) + "px";
       this.el.style.left = (rect.left - 1) + "px";
       this.el.style.width = (rect.right - rect.left + 2) + "px";
       this.el.style.height = (rect.bottom - rect.top + 2) + "px";
     },
 
-    hide: function () {
+    hide() {
       if (this.el) {
         this.el.style.display = "none";
       }
     },
 
-    remove: function () {
+    remove() {
       util.removeNode(this.el);
       this.el = null;
     }
   };
 
   exports.PixelDimensions = {
     el: null,
     xEl: null,
     yEl: null,
-    display: function (xPos, yPos, x, y) {
-      if (! this.el) {
+    display(xPos, yPos, x, y) {
+      if (!this.el) {
         this.el = makeEl("div", "pixel-dimensions");
         this.xEl = makeEl("div");
         this.el.appendChild(this.xEl);
         this.yEl = makeEl("div");
         this.el.appendChild(this.yEl);
         iframe.document().body.appendChild(this.el);
       }
       this.xEl.textContent = x;
       this.yEl.textContent = y;
       this.el.style.top = (yPos + 12) + "px";
       this.el.style.left = (xPos + 12) + "px";
     },
-    remove: function () {
+    remove() {
       util.removeNode(this.el);
       this.el = this.xEl = this.yEl = null;
     }
   };
 
   /** Removes every UI this module creates */
-  exports.remove = function () {
+  exports.remove = function() {
     for (let name in exports) {
       if (name.startsWith("iframe")) {
         continue;
       }
       if (typeof exports[name] == "object" && exports[name].remove) {
         exports[name].remove();
       }
     }
     exports.iframe.remove();
   };
 
-  exports.triggerDownload = function (url, filename) {
+  exports.triggerDownload = function(url, filename) {
     return catcher.watchPromise(callBackground("downloadShot", {url, filename}));
   };
 
   exports.unload = exports.remove;
 
   return exports;
 })();
 null;
--- a/browser/extensions/screenshots/webextension/selector/uicontrol.js
+++ b/browser/extensions/screenshots/webextension/selector/uicontrol.js
@@ -1,17 +1,17 @@
-/* globals console, catcher, util, ui, slides */
+/* globals log, catcher, util, ui, slides */
 /* globals window, document, location, shooter, callBackground, selectorLoader, assertIsTrusted */
 
 "use strict";
 
-this.uicontrol = (function () {
+this.uicontrol = (function() {
   let exports = {};
 
-  /**********************************************************
+  /** ********************************************************
    * selection
    */
 
   /* States:
 
   "crosshairs":
     Nothing has happened, and the crosshairs will follow the movement of the mouse
   "draggingReady":
@@ -52,18 +52,22 @@ this.uicontrol = (function () {
   const MIN_DETECT_HEIGHT = 30;
   const MIN_DETECT_WIDTH = 100;
   // An autoselection bigger than either of these will be ignored:
   const MAX_DETECT_HEIGHT = Math.max(window.innerHeight + 100, 700);
   const MAX_DETECT_WIDTH = Math.max(window.innerWidth + 100, 1000);
   // This is how close (in pixels) you can get to the edge of the window and then
   // it will scroll:
   const SCROLL_BY_EDGE = 20;
+  // This is how wide the inboard scrollbars are, generally 0 except on Mac
+  const SCROLLBAR_WIDTH = (window.navigator.platform.match(/Mac/i)) ? 17 : 0;
+
 
   const { sendEvent } = shooter;
+  const log = global.log;
 
   function round10(n) {
     return Math.floor(n / 10) * 10;
   }
 
   function eventOptionsForBox(box) {
     return {
       cd1: round10(Math.abs(box.bottom - box.top)),
@@ -84,17 +88,17 @@ this.uicontrol = (function () {
 
   function eventOptionsForMove(posStart, posEnd) {
     return {
       cd1: round10(posEnd.y - posStart.y),
       cd2: round10(posEnd.x - posStart.x)
     };
   }
 
-  /***********************************************
+  /** *********************************************
    * State and stateHandlers infrastructure
    */
 
   // This enumerates all the anchors on the selection, and what part of the
   // selection they move:
   const movements = {
     topLeft: ["x1", "y1"],
     top: [null, "y1"],
@@ -170,17 +174,17 @@ this.uicontrol = (function () {
   let stateHandlers = {};
 
   function getState() {
     return getState.state;
   }
   getState.state = "cancel";
 
   function setState(s) {
-    if (! stateHandlers[s]) {
+    if (!stateHandlers[s]) {
       throw new Error("Unknown state: " + s);
     }
     let cur = getState.state;
     let handler = stateHandlers[cur];
     if (handler.end) {
       handler.end();
     }
     getState.state = s;
@@ -300,23 +304,23 @@ this.uicontrol = (function () {
         left: this.left,
         right: this.right,
         top: this.top,
         bottom: this.bottom
       };
     }
   }
 
-  Selection.getBoundingClientRect = function (el) {
-    if (! el.getBoundingClientRect) {
+  Selection.getBoundingClientRect = function(el) {
+    if (!el.getBoundingClientRect) {
       // Typically the <html> element or somesuch
       return null;
     }
     let rect = el.getBoundingClientRect();
-    if (! rect) {
+    if (!rect) {
       return null;
     }
     return new Selection(rect.left, rect.top, rect.right, rect.bottom);
   };
 
   /** Represents a single x/y point, typically for a mouse click that doesn't have a drag: */
   class Pos {
     constructor(x, y) {
@@ -331,104 +335,111 @@ this.uicontrol = (function () {
       );
     }
 
     distanceTo(x, y) {
       return Math.sqrt(Math.pow(this.x - x, 2), Math.pow(this.y - y));
     }
   }
 
-  /***********************************************
+  /** *********************************************
    * all stateHandlers
    */
 
   stateHandlers.onboarding = {
-    start: function () {
+    start() {
       if (typeof slides == "undefined") {
         throw new Error("Attempted to set state to onboarding without loading slides");
       }
       catcher.watchPromise(slides.display({
         onEnd: this.slidesOnEnd.bind(this)
       }));
     },
 
-    slidesOnEnd: function () {
+    slidesOnEnd() {
       callBackground("hasSeenOnboarding");
       setState("crosshairs");
     },
 
-    end: function () {
+    end() {
       slides.remove();
     }
   };
 
   stateHandlers.crosshairs = {
 
     cachedEl: null,
 
-    start: function () {
+    start() {
       selectedPos = mousedownPos = null;
       this.cachedEl = null;
       watchPromise(ui.iframe.display(installHandlersOnDocument, standardOverlayCallbacks).then(() => {
         ui.iframe.usePreSelection();
         ui.Box.remove();
         const handler = watchFunction(assertIsTrusted(keyupHandler));
-        document.addEventListener("keyup", handler, false);
+        document.addEventListener("keyup", handler);
         registeredDocumentHandlers.push({name: "keyup", doc: document, handler});
       }));
     },
 
-    mousemove: function (event) {
+    mousemove(event) {
       ui.PixelDimensions.display(event.pageX, event.pageY, event.pageX, event.pageY);
       if (event.target.classList &&
-          (! event.target.classList.contains("preview-overlay"))) {
+          (!event.target.classList.contains("preview-overlay"))) {
         // User is hovering over a toolbar button or control
         autoDetectRect = null;
         ui.HoverBox.hide();
         return;
       }
       let el;
-      if (event.target.classList.contains("preview-overlay")) {
+      if (event.target.classList && event.target.classList.contains("preview-overlay")) {
         // The hover is on the overlay, so we need to figure out the real element
         el = ui.iframe.getElementFromPoint(
           event.pageX + window.scrollX - window.pageXOffset,
           event.pageY + window.scrollY - window.pageYOffset
         );
+        let xpos = Math.floor(10 * (event.pageX - window.innerWidth / 2) / window.innerWidth);
+        let ypos = Math.floor(10 * (event.pageY - window.innerHeight / 2) / window.innerHeight)
+
+        for (var i = 0; i < 2; i++) {
+          let move = `translate(${xpos}px, ${ypos}px)`;
+          event.target.getElementsByClassName('eyeball')[i].style.transform = move;
+        }
       } else {
         // The hover is on the element we care about, so we use that
         el = event.target;
       }
       if (this.cachedEl && this.cachedEl === el) {
         // Still hovering over the same element
         return;
       }
       this.cachedEl = el;
       this.setAutodetectBasedOnElement(el);
     },
 
-    setAutodetectBasedOnElement: function (el) {
+    setAutodetectBasedOnElement(el) {
       let lastRect;
       let lastNode;
       let rect;
       let attemptExtend = false;
       let node = el;
       while (node) {
         rect = Selection.getBoundingClientRect(node);
-        if (! rect) {
+        if (!rect) {
           rect = lastRect;
           break;
         }
         if (rect.width > MAX_DETECT_WIDTH || rect.height > MAX_DETECT_HEIGHT) {
           // Then the last rectangle is better
           rect = lastRect;
           attemptExtend = true;
           break;
         }
         if (rect.width >= MIN_DETECT_WIDTH && rect.height >= MIN_DETECT_HEIGHT) {
-          if (! doNotAutoselectTags[node.tagName]) {
+          if (!doNotAutoselectTags[node.tagName]) {
             break;
           }
         }
         lastRect = rect;
         lastNode = node;
         node = node.parentNode;
       }
       if (rect && node) {
@@ -441,112 +452,121 @@ this.uicontrol = (function () {
       }
       if (rect && attemptExtend) {
         let extendNode = lastNode.nextSibling;
         while (extendNode) {
           if (extendNode.nodeType === document.ELEMENT_NODE) {
             break;
           }
           extendNode = extendNode.nextSibling;
-          if (! extendNode) {
+          if (!extendNode) {
             let parent = lastNode.parentNode;
-            for (let i=0; i<parent.childNodes.length; i++) {
+            for (let i = 0; i < parent.childNodes.length; i++) {
               if (parent.childNodes[i] === lastNode) {
-                extendNode = parent.childNodes[i+1];
+                extendNode = parent.childNodes[i + 1];
               }
             }
           }
         }
         if (extendNode) {
           let extendSelection = Selection.getBoundingClientRect(extendNode);
           let extendRect = rect.union(extendSelection);
           if (extendRect.width <= MAX_DETECT_WIDTH && extendRect.height <= MAX_DETECT_HEIGHT) {
             rect = extendRect;
           }
         }
       }
 
       if (rect && (rect.width < MIN_DETECT_ABSOLUTE_WIDTH || rect.height < MIN_DETECT_ABSOLUTE_HEIGHT)) {
         rect = null;
       }
-      if (! rect) {
+      if (!rect) {
         ui.HoverBox.hide();
       } else {
         ui.HoverBox.display(rect);
       }
       autoDetectRect = rect;
     },
 
     /** When we find an element, maybe there's one that's just a little bit better... */
-    evenBetterElement: function (node, origRect) {
+    evenBetterElement(node, origRect) {
       let el = node.parentNode;
       let ELEMENT_NODE = document.ELEMENT_NODE;
       while (el && el.nodeType == ELEMENT_NODE) {
-        if (! el.getAttribute) {
+        if (!el.getAttribute) {
           return null;
         }
         let role = el.getAttribute("role");
         if (role === "article" || (el.className && typeof el.className == "string" && el.className.search("tweet ") !== -1)) {
           let rect = Selection.getBoundingClientRect(el);
-          if (! rect) {
+          if (!rect) {
             return null;
           }
           if (rect.width <= MAX_DETECT_WIDTH && rect.height <= MAX_DETECT_HEIGHT) {
             return el;
-          } else {
-            return null;
           }
+          return null;
         }
         el = el.parentNode;
       }
       return null;
     },
 
-    mousedown: function (event) {
+    mousedown(event) {
       if (ui.isHeader(event.target)) {
-        return;
+        return undefined;
       }
+      // If the pageX is greater than this, then probably it's an attempt to get
+      // to the scrollbar, or an actual scroll, and not an attempt to start the
+      // selection:
+      let maxX = window.innerWidth - SCROLLBAR_WIDTH;
+      if (event.pageX >= maxX) {
+        event.stopPropagation();
+        event.preventDefault();
+        return false;
+      }
+
       mousedownPos = new Pos(event.pageX + window.scrollX, event.pageY + window.scrollY);
       setState("draggingReady");
       event.stopPropagation();
       event.preventDefault();
       return false;
     },
 
-    end: function () {
+    end() {
       ui.HoverBox.remove();
       ui.PixelDimensions.remove();
     }
   };
 
   stateHandlers.draggingReady = {
     minMove: 40, // px
     minAutoImageWidth: 40,
     minAutoImageHeight: 40,
     maxAutoElementWidth: 800,
     maxAutoElementHeight: 600,
 
-    start: function () {
+    start() {
       ui.iframe.usePreSelection();
       ui.Box.remove();
     },
 
-    mousemove: function (event) {
+    mousemove(event) {
       if (mousedownPos.distanceTo(event.pageX, event.pageY) > this.minMove) {
         selectedPos = new Selection(
           mousedownPos.x,
           mousedownPos.y,
           event.pageX + window.scrollX,
           event.pageY + window.scrollY);
         mousedownPos = null;
         setState("dragging");
       }
     },
 
-    mouseup: function (event) {
+    mouseup(event) {
       // If we don't get into "dragging" then we attempt an autoselect
       if (mouseupNoAutoselect) {
         sendEvent("cancel-selection", "selection-background-mousedown");
         setState("crosshairs");
         return false;
       }
       if (autoDetectRect) {
         selectedPos = autoDetectRect;
@@ -560,168 +580,167 @@ this.uicontrol = (function () {
         ui.Box.display(selectedPos, standardDisplayCallbacks);
         sendEvent("make-selection", "selection-click", eventOptionsForBox(selectedPos));
         setState("selected");
         sendEvent("autoselect");
       } else {
         sendEvent("no-selection", "no-element-found");
         setState("crosshairs");
       }
+      return undefined;
     },
 
-    click: function (event) {
+    click(event) {
       this.mouseup(event);
     },
 
-    findGoodEl: function () {
+    findGoodEl() {
       let el = mousedownPos.elementFromPoint();
-      if (! el) {
+      if (!el) {
         return null;
       }
       let isGoodEl = (el) => {
         if (el.nodeType != document.ELEMENT_NODE) {
           return false;
         }
         if (el.tagName == "IMG") {
           let rect = el.getBoundingClientRect();
           return rect.width >= this.minAutoImageWidth && rect.height >= this.minAutoImageHeight;
         }
         let display = window.getComputedStyle(el).display;
         if (['block', 'inline-block', 'table'].indexOf(display) != -1) {
           return true;
           // FIXME: not sure if this is useful:
-          //let rect = el.getBoundingClientRect();
-          //return rect.width <= this.maxAutoElementWidth && rect.height <= this.maxAutoElementHeight;
+          // let rect = el.getBoundingClientRect();
+          // return rect.width <= this.maxAutoElementWidth && rect.height <= this.maxAutoElementHeight;
         }
         return false;
       };
       while (el) {
         if (isGoodEl(el)) {
           return el;
         }
         el = el.parentNode;
       }
       return null;
     },
 
-    end: function () {
+    end() {
       mouseupNoAutoselect = false;
     }
 
   };
 
   stateHandlers.dragging = {
 
-    start: function () {
+    start() {
       ui.iframe.useSelection();
       ui.Box.display(selectedPos);
     },
 
-    mousemove: function (event) {
+    mousemove(event) {
       selectedPos.x2 = util.truncateX(event.pageX);
       selectedPos.y2 = util.truncateY(event.pageY);
       scrollIfByEdge(event.pageX, event.pageY);
       ui.Box.display(selectedPos);
       ui.PixelDimensions.display(event.pageX, event.pageY, selectedPos.width, selectedPos.height);
     },
 
-    mouseup: function (event) {
+    mouseup(event) {
       selectedPos.x2 = util.truncateX(event.pageX);
       selectedPos.y2 = util.truncateY(event.pageY);
       ui.Box.display(selectedPos, standardDisplayCallbacks);
       sendEvent(
         "make-selection", "selection-drag",
         eventOptionsForBox({
           top: selectedPos.y1,
           bottom: selectedPos.y2,
           left: selectedPos.x1,
           right: selectedPos.x2
         }));
       setState("selected");
     },
 
-    end: function () {
+    end() {
       ui.PixelDimensions.remove();
     }
   };
 
   stateHandlers.selected = {
-    start: function () {
+    start() {
       ui.iframe.useSelection();
     },
 
-    mousedown: function (event) {
+    mousedown(event) {
       let target = event.target;
       if (target.tagName == "HTML") {
         // This happens when you click on the scrollbar
-        return;
+        return undefined;
       }
       let direction = ui.Box.draggerDirection(target);
       if (direction) {
         sendEvent("start-resize-selection", "handle");
         stateHandlers.resizing.startResize(event, direction);
       } else if (ui.Box.isSelection(target)) {
         sendEvent("start-move-selection", "selection");
         stateHandlers.resizing.startResize(event, "move");
-      } else if (! ui.Box.isControl(target)) {
+      } else if (!ui.Box.isControl(target)) {
         mousedownPos = new Pos(event.pageX, event.pageY);
         setState("crosshairs");
       }
       event.preventDefault();
       return false;
     }
   };
 
   stateHandlers.resizing = {
-    start: function () {
+    start() {
       ui.iframe.useSelection();
       selectedPos.sortCoords();
     },
 
-    startResize: function (event, direction) {
+    startResize(event, direction) {
       selectedPos.sortCoords();
       resizeDirection = direction;
       resizeStartPos = new Pos(event.pageX, event.pageY);
       resizeStartSelected = selectedPos.clone();
       resizeHasMoved = false;
       setState("resizing");
     },
 
-    mousemove: function (event) {
+    mousemove(event) {
       this._resize(event);
       return false;
     },
 
-    mouseup: function (event) {
+    mouseup(event) {
       this._resize(event);
       sendEvent("selection-resized");
       ui.Box.display(selectedPos, standardDisplayCallbacks);
       if (resizeHasMoved) {
         if (resizeDirection == "move") {
           let startPos = new Pos(resizeStartSelected.left, resizeStartSelected.top);
           let endPos = new Pos(selectedPos.left, selectedPos.top);
           sendEvent(
             "move-selection", "mouseup",
             eventOptionsForMove(startPos, endPos));
         } else {
           sendEvent(
             "resize-selection", "mouseup",
             eventOptionsForResize(resizeStartSelected, selectedPos));
         }
+      } else if (resizeDirection == "move") {
+        sendEvent("keep-resize-selection", "mouseup");
       } else {
-        if (resizeDirection == "move") {
-          sendEvent("keep-resize-selection", "mouseup");
-        } else {
-          sendEvent("keep-move-selection", "mouseup");
-        }
+        sendEvent("keep-move-selection", "mouseup");
       }
       setState("selected");
     },
 
-    _resize: function (event) {
+    _resize(event) {
       let diffX = event.pageX - resizeStartPos.x;
       let diffY = event.pageY - resizeStartPos.y;
       let movement = movements[resizeDirection];
       if (movement[0]) {
         let moveX = movement[0];
         moveX = moveX == "*" ? ["x1", "x2"] : [moveX];
         for (let moveDir of moveX) {
           selectedPos[moveDir] =  util.truncateX(resizeStartSelected[moveDir] + diffX);
@@ -736,24 +755,24 @@ this.uicontrol = (function () {
       }
       if (diffX || diffY) {
         resizeHasMoved = true;
       }
       scrollIfByEdge(event.pageX, event.pageY);
       ui.Box.display(selectedPos);
     },
 
-    end: function () {
+    end() {
       resizeDirection = resizeStartPos = resizeStartSelected = null;
       selectedPos.sortCoords();
     }
   };
 
   stateHandlers.cancel = {
-    start: function () {
+    start() {
       ui.iframe.hide();
       ui.Box.remove();
     }
   };
 
   let documentWidth = Math.max(
     document.body.clientWidth,
     document.documentElement.clientWidth,
@@ -777,24 +796,24 @@ this.uicontrol = (function () {
     }
     if (pageX + SCROLL_BY_EDGE >= right && right < documentWidth) {
       window.scrollBy(SCROLL_BY_EDGE, 0);
     } else if (pageX - SCROLL_BY_EDGE <= left) {
       window.scrollBy(-SCROLL_BY_EDGE, 0);
     }
   }
 
-  /***********************************************
+  /** *********************************************
    * Selection communication
    */
 
    // If the slides module is loaded then we're supposed to onboard
   let shouldOnboard = typeof slides !== "undefined";
 
-  exports.activate = function () {
+  exports.activate = function() {
     if (isFrameset()) {
       callBackground("abortFrameset");
       selectorLoader.unloadModules();
       return;
     }
     addHandlers();
     // FIXME: self.options is gone
     if (self.options && self.options.styleMyShotsButton) {
@@ -806,56 +825,57 @@ this.uicontrol = (function () {
       setState("crosshairs");
     }
   }
 
   function isFrameset() {
     return document.body.tagName == "FRAMESET";
   }
 
-  exports.deactivate = function () {
+  exports.deactivate = function() {
     try {
       setState("cancel");
       callBackground('closeSelector');
       selectorLoader.unloadModules();
     } catch (e) {
       log.error('Error in deactivate', e)
       // Sometimes this fires so late that the document isn't available
       // We don't care about the exception, so we swallow it here
     }
   };
 
-  exports.unload = function () {
+  exports.unload = function() {
     // Note that ui.unload() will be called on its own
     removeHandlers();
   };
 
-  /***********************************************
+  /** *********************************************
    * Event handlers
    */
 
   let primedDocumentHandlers = new Map();
   let registeredDocumentHandlers = []
 
   function addHandlers() {
     ["mouseup", "mousedown", "mousemove", "click"].forEach((eventName) => {
-      let fn = watchFunction((function (eventName, event) {
+      let fn = watchFunction((function(eventName, event) {
         if (typeof event.button == "number" && event.button !== 0) {
           // Not a left click
-          return;
+          return undefined;
         }
         if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
           // Modified click of key
-          return;
+          return undefined;
         }
         let state = getState();
         let handler = stateHandlers[state];
         if (handler[eventName]) {
           return handler[eventName](event);
         }
+        return undefined;
       }).bind(null, eventName));
       primedDocumentHandlers.set(eventName, fn);
     });
     primedDocumentHandlers.set("keyup", keyupHandler);
     window.addEventListener('beforeunload', beforeunloadHandler);
   }
 
   function installHandlersOnDocument(docObj) {
--- a/browser/extensions/screenshots/webextension/selector/util.js
+++ b/browser/extensions/screenshots/webextension/selector/util.js
@@ -1,49 +1,47 @@
 "use strict";
 
-this.util = (function () { // eslint-disable-line no-unused-vars
+this.util = (function() { // eslint-disable-line no-unused-vars
   let exports = {};
 
   /** Removes a node from its document, if it's a node and the node is attached to a parent */
-  exports.removeNode = function (el) {
+  exports.removeNode = function(el) {
     if (el && el.parentNode) {
-      el.parentNode.removeChild(el);
+      el.remove();
     }
   };
 
   /** Truncates the X coordinate to the document size */
-  exports.truncateX = function (x) {
+  exports.truncateX = function(x) {
     let max = Math.max(document.documentElement.clientWidth, document.body.clientWidth, document.documentElement.scrollWidth, document.body.scrollWidth);
     if (x < 0) {
       return 0;
     } else if (x > max) {
       return max;
-    } else {
-      return x;
     }
+    return x;
   };
 
   /** Truncates the Y coordinate to the document size */
-  exports.truncateY = function (y) {
+  exports.truncateY = function(y) {
     let max = Math.max(document.documentElement.clientHeight, document.body.clientHeight, document.documentElement.scrollHeight, document.body.scrollHeight);
     if (y < 0) {
       return 0;
     } else if (y > max) {
       return max;
-    } else {
-      return y;
     }
+    return y;
   };
 
   // Pixels of wiggle the captured region gets in captureSelectedText:
   var CAPTURE_WIGGLE = 10;
   const ELEMENT_NODE = document.ELEMENT_NODE;
 
-  exports.captureEnclosedText = function (box) {
+  exports.captureEnclosedText = function(box) {
     var scrollX = window.scrollX;
     var scrollY = window.scrollY;
     var text = [];
     function traverse(el) {
       var elBox = el.getBoundingClientRect();
       elBox = {
         top: elBox.top + scrollY,
         bottom: elBox.bottom + scrollY,
@@ -57,50 +55,49 @@ this.util = (function () { // eslint-dis
         // Totally outside of the box
         return;
       }
       if (elBox.bottom > box.bottom + CAPTURE_WIGGLE ||
           elBox.top < box.top - CAPTURE_WIGGLE ||
           elBox.right > box.right + CAPTURE_WIGGLE ||
           elBox.left < box.left - CAPTURE_WIGGLE) {
         // Partially outside the box
-        for (var i=0; i<el.childNodes.length; i++) {
+        for (var i = 0; i < el.childNodes.length; i++) {
           var child = el.childNodes[i];
           if (child.nodeType == ELEMENT_NODE) {
             traverse(child);
           }
         }
         return;
       }
       addText(el);
     }
     function addText(el) {
       let t;
       if (el.tagName == "IMG") {
         t = el.getAttribute("alt") || el.getAttribute("title");
       } else if (el.tagName == "A") {
         t = el.innerText;
-        if (el.getAttribute("href") && ! el.getAttribute("href").startsWith("#")) {
+        if (el.getAttribute("href") && !el.getAttribute("href").startsWith("#")) {
           t += " (" + el.href + ")";
         }
       } else {
         t = el.innerText;
       }
       if (t) {
         text.push(t);
       }
     }
     traverse(document.body);
     if (text.length) {
       let result = text.join("\n");
       result = result.replace(/^\s+/, "");
       result = result.replace(/\s+$/, "");
       result = result.replace(/[ \t]+\n/g, "\n");
       return result;
-    } else {
-      return null;
     }
+    return null;
   };
 
 
   return exports;
 })();
 null;
--- a/browser/extensions/screenshots/webextension/sitehelper.js
+++ b/browser/extensions/screenshots/webextension/sitehelper.js
@@ -1,15 +1,15 @@
 /* globals catcher, callBackground */
 /** This is a content script added to all screenshots.firefox.com pages, and allows the site to
     communicate with the add-on */
 
 "use strict";
 
-this.sitehelper = (function () {
+this.sitehelper = (function() {
 
   catcher.registerHandler((errorObj) => {
     callBackground("reportError", errorObj);
   });
 
 
   function sendCustomEvent(name, detail) {
     if (typeof detail == "object") {
@@ -26,18 +26,22 @@ this.sitehelper = (function () {
 
   document.addEventListener("request-login", catcher.watchFunction((event) => {
     let shotId = event.detail;
     catcher.watchPromise(callBackground("getAuthInfo", shotId || null).then((info) => {
       sendCustomEvent("login-successful", {deviceId: info.deviceId, isOwner: info.isOwner});
     }));
   }));
 
+  document.addEventListener("request-onboarding", catcher.watchFunction((event) => {
+    callBackground("requestOnboarding");
+  }));
+
   // Depending on the script loading order, the site might get the addon-present event,
   // but probably won't - instead the site will ask for that event after it has loaded
   document.addEventListener("request-addon-present", catcher.watchFunction(() => {
     sendCustomEvent("addon-present");
-  }), false);
+  }));
 
   sendCustomEvent("addon-present");
 
 })();
 null;
--- a/browser/locales/all-locales
+++ b/browser/locales/all-locales
@@ -51,16 +51,17 @@ ja
 ja-JP-mac
 ka
 kab
 kk
 km
 kn
 ko
 lij
+lo
 lt
 ltg
 lv
 mai
 mk
 ml
 mr
 ms
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -14,16 +14,17 @@ Cu.import("resource:///modules/E10SUtils
 Cu.import("resource://testing-common/ContentTask.jsm");
 Cu.import("resource://testing-common/BrowserTestUtils.jsm");
 
 const URL = "https://test1.example.com/extensions/mozscreenshots/browser/chrome/mozscreenshots/lib/permissionPrompts.html";
 let lastTab = null;
 
 this.PermissionPrompts = {
   init(libDir) {
+    Services.prefs.setBoolPref("browser.storageManager.enabled", true);
     Services.prefs.setBoolPref("media.navigator.permission.fake", true);
     Services.prefs.setCharPref("media.getusermedia.screensharing.allowed_domains",
                                "test1.example.com");
     Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
     Services.prefs.setBoolPref("signon.rememberSignons", true);
   },
 
   configurations: {
@@ -57,59 +58,66 @@ this.PermissionPrompts = {
 
     geo: {
       applyConfig: Task.async(function*() {
         yield closeLastTab();
         yield clickOn("#geo");
       }),
     },
 
+    persistentStorage: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#persistent-storage");
+      }),
+    },
+
     loginCapture: {
       applyConfig: Task.async(function*() {
         yield closeLastTab();
-        yield clickOn("#login-capture", URL);
+        yield clickOn("#login-capture");
       }),
     },
 
     notifications: {
       applyConfig: Task.async(function*() {
         yield closeLastTab();
-        yield clickOn("#web-notifications", URL);
+        yield clickOn("#web-notifications");
       }),
     },
 
     addons: {
       applyConfig: Task.async(function*() {
         Services.prefs.setBoolPref("xpinstall.whitelist.required", true);
 
         yield closeLastTab();
-        yield clickOn("#addons", URL);
+        yield clickOn("#addons");
       }),
     },
 
     addonsNoWhitelist: {
       applyConfig: Task.async(function*() {
         Services.prefs.setBoolPref("xpinstall.whitelist.required", false);
 
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let notification = browserWindow.document.getElementById("addon-install-confirmation-notification");
 
         yield closeLastTab();
-        yield clickOn("#addons", URL);
+        yield clickOn("#addons");
 
         // We want to skip the progress-notification, so we wait for
         // the install-confirmation screen to be "not hidden" = shown.
         yield BrowserTestUtils.waitForCondition(() => !notification.hasAttribute("hidden"),
                                                 "addon install confirmation did not show", 200);
       }),
     },
   },
 };
 
-function* closeLastTab(selector) {
+function* closeLastTab() {
   if (!lastTab) {
     return;
   }
   yield BrowserTestUtils.removeTab(lastTab);
   lastTab = null;
 }
 
 function* clickOn(selector) {
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html
@@ -1,16 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Permission Prompts</title>
 </head>
 <body>
   <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
+  <button id="persistent-storage" onclick="navigator.storage.persist()">Persistent Storage</button>
   <button id="webRTC-shareDevices" onclick="shareDevice({video: true, fake: true});">Video</button>
   <button id="webRTC-shareMicrophone" onclick="shareDevice({audio: true, fake: true});">Audio</button>
   <button id="webRTC-shareDevices2" onclick="shareDevice({audio: true, video: true, fake: true});">Audio and Video</button>
   <button id="webRTC-shareScreen" onclick="shareDevice({video: {mediaSource: 'screen'}});">Screen</button>
   <button id="web-notifications" onclick="Notification.requestPermission()">web-notifications</button>
   <a id="addons" href="borderify.xpi">Install Add-On</a>
   <form>
     <input type="email" id="email" value="email@example.com" />
--- a/devtools/client/animationinspector/test/browser_animation_refresh_on_added_animation.js
+++ b/devtools/client/animationinspector/test/browser_animation_refresh_on_added_animation.js
@@ -17,31 +17,35 @@ add_task(function* () {
 
   assertAnimationsDisplayed(panel, 0);
 
   info("Start an animation on the node");
   yield changeElementAndWait({
     selector: ".still",
     attributeName: "class",
     attributeValue: "ball animated"
-  }, panel, inspector);
+  }, panel, inspector, true);
 
   assertAnimationsDisplayed(panel, 1);
 
   info("Remove the animation class on the node");
   yield changeElementAndWait({
     selector: ".ball.animated",
     attributeName: "class",
     attributeValue: "ball still"
-  }, panel, inspector);
+  }, panel, inspector, false);
 
   assertAnimationsDisplayed(panel, 0);
 });
 
-function* changeElementAndWait(options, panel, inspector) {
+function* changeElementAndWait(options, panel, inspector, isDetailDisplayed) {
   let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
   let onInspectorUpdated = inspector.once("inspector-updated");
+  let onDetailRendered = isDetailDisplayed
+                         ? panel.animationsTimelineComponent
+                                .details.once("animation-detail-rendering-completed")
+                         : Promise.resolve();
 
   yield executeInContent("devtools:test:setAttribute", options);
 
-  yield promise.all([
-    onInspectorUpdated, onPanelUpdated, waitForAllAnimationTargets(panel)]);
+  yield promise.all([onInspectorUpdated, onPanelUpdated,
+                     waitForAllAnimationTargets(panel), onDetailRendered]);
 }
--- a/devtools/client/inspector/boxmodel/components/BoxModel.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModel.js
@@ -57,30 +57,31 @@ module.exports = createClass({
         ref: div => {
           this.boxModelContainer = div;
         },
         onKeyDown: this.onKeyDown,
       },
       BoxModelMain({
         boxModel,
         boxModelContainer: this.boxModelContainer,
-        setSelectedNode,
         ref: boxModelMain => {
           this.boxModelMain = boxModelMain;
         },
         onHideBoxModelHighlighter,
         onShowBoxModelEditor,
         onShowBoxModelHighlighter,
-        onShowBoxModelHighlighterForNode,
       }),
       BoxModelInfo({
         boxModel,
         onToggleGeometryEditor,
       }),
       showBoxModelProperties ?
         BoxModelProperties({
           boxModel,
+          setSelectedNode,
+          onHideBoxModelHighlighter,
+          onShowBoxModelHighlighterForNode,
         })
         :
         null
     );
   },
 });
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js
@@ -8,40 +8,34 @@ const { addons, createClass, createFacto
   require("devtools/client/shared/vendor/react");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const BoxModelEditable = createFactory(require("./BoxModelEditable"));
 
-// Reps
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const { Rep } = REPS;
-
 const Types = require("../types");
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
 const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
 
 module.exports = createClass({
 
   displayName: "BoxModelMain",
 
   propTypes: {
     boxModel: PropTypes.shape(Types.boxModel).isRequired,
     boxModelContainer: PropTypes.object,
-    setSelectedNode: PropTypes.func.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onShowBoxModelEditor: PropTypes.func.isRequired,
     onShowBoxModelHighlighter: PropTypes.func.isRequired,
-    onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {
       activeDescendant: null,
       focusable: false,
@@ -249,48 +243,16 @@ module.exports = createClass({
       boxModelContainer.setAttribute("activedescendant", nextLayout.className);
     }
 
     this.setState({
       activeDescendant: nextLayout.getAttribute("data-box"),
     });
   },
 
-  /**
-   * While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
-   * translate nodeFront to a grip-like object that can be used with an ElementNode rep.
-   *
-   * @param  {NodeFront} nodeFront
-   *         The NodeFront for which we want to create a grip-like object.
-   * @return {Object} a grip-like object that can be used with Reps.
-   */
-  translateNodeFrontToGrip(nodeFront) {
-    let {
-      attributes
-    } = nodeFront;
-
-    // The main difference between NodeFront and grips is that attributes are treated as
-    // a map in grips and as an array in NodeFronts.
-    let attributesMap = {};
-    for (let { name, value } of attributes) {
-      attributesMap[name] = value;
-    }
-
-    return {
-      actor: nodeFront.actorID,
-      preview: {
-        attributes: attributesMap,
-        attributesLength: attributes.length,
-        // nodeName is already lowerCased in Node grips
-        nodeName: nodeFront.nodeName.toLowerCase(),
-        nodeType: nodeFront.nodeType,
-      }
-    };
-  },
-
   onHighlightMouseOver(event) {
     let region = event.target.getAttribute("data-box");
 
     if (!region) {
       let el = event.target;
 
       do {
         el = el.parentNode;
@@ -299,22 +261,16 @@ module.exports = createClass({
           region = el.getAttribute("data-box");
           break;
         }
       } while (el.parentNode);
 
       this.props.onHideBoxModelHighlighter();
     }
 
-    if (region === "offset-parent") {
-      this.props.onHideBoxModelHighlighter();
-      this.props.onShowBoxModelHighlighterForNode(this.props.boxModel.offsetParent);
-      return;
-    }
-
     this.props.onShowBoxModelHighlighter({
       region,
       showOnly: region,
       onlyRegionArea: true,
     });
   },
 
   /**
@@ -404,25 +360,22 @@ module.exports = createClass({
     if (target && target._editable) {
       target.blur();
     }
   },
 
   render() {
     let {
       boxModel,
-      setSelectedNode,
       onShowBoxModelEditor,
     } = this.props;
-    let { layout, offsetParent } = boxModel;
-    let { height, width, position } = layout;
+    let { layout } = boxModel;
+    let { height, width } = layout;
     let { activeDescendant: level, focusable } = this.state;
 
-    let displayOffsetParent = offsetParent && layout.position === "absolute";
-
     let borderTop = this.getBorderOrPaddingValue("border-top-width");
     let borderRight = this.getBorderOrPaddingValue("border-right-width");
     let borderBottom = this.getBorderOrPaddingValue("border-bottom-width");
     let borderLeft = this.getBorderOrPaddingValue("border-left-width");
 
     let paddingTop = this.getBorderOrPaddingValue("padding-top");
     let paddingRight = this.getBorderOrPaddingValue("padding-right");
     let paddingBottom = this.getBorderOrPaddingValue("padding-bottom");
@@ -491,44 +444,16 @@ module.exports = createClass({
         ref: div => {
           this.positionLayout = div;
         },
         onClick: this.onLevelClick,
         onKeyDown: this.onKeyDown,
         onMouseOver: this.onHighlightMouseOver,
         onMouseOut: this.props.onHideBoxModelHighlighter,
       },
-      displayOffsetParent ?
-        dom.span(
-          {
-            className: "boxmodel-offset-parent",
-            "data-box": "offset-parent",
-          },
-          Rep(
-            {
-              defaultRep: offsetParent,
-              mode: MODE.TINY,
-              object: this.translateNodeFrontToGrip(offsetParent),
-              onInspectIconClick: () => setSelectedNode(offsetParent, "box-model"),
-            }
-          )
-        )
-        :
-        null,
-      displayPosition ?
-        dom.span(
-          {
-            className: "boxmodel-legend",
-            "data-box": "position",
-            title: BOXMODEL_L10N.getFormatStr("boxmodel.position", position),
-          },
-          BOXMODEL_L10N.getFormatStr("boxmodel.position", position)
-        )
-        :
-        null,
       dom.div(
         {
           className: "boxmodel-box"
         },
         dom.span(
           {
             className: "boxmodel-legend",
             "data-box": "margin",
--- a/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
@@ -17,44 +17,87 @@ const BOXMODEL_STRINGS_URI = "devtools/c
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 module.exports = createClass({
 
   displayName: "BoxModelProperties",
 
   propTypes: {
     boxModel: PropTypes.shape(Types.boxModel).isRequired,
+    setSelectedNode: PropTypes.func.isRequired,
+    onHideBoxModelHighlighter: PropTypes.func.isRequired,
+    onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {
       isOpen: true,
     };
   },
 
+  /**
+   * Various properties can display a reference element. E.g. position displays an offset
+   * parent if its value is other than fixed or static. Or z-index displays a stacking
+   * context, etc.
+   * This returns the right element if there needs to be one, and one was passed in the
+   * props.
+   *
+   * @return {Object} An object with 2 properties:
+   * - referenceElement {NodeFront}
+   * - referenceElementType {String}
+   */
+  getReferenceElement(propertyName) {
+    let value = this.props.boxModel.layout[propertyName];
+
+    if (propertyName === "position" &&
+        value !== "static" && value !== "fixed" &&
+        this.props.boxModel.offsetParent) {
+      return {
+        referenceElement: this.props.boxModel.offsetParent,
+        referenceElementType: BOXMODEL_L10N.getStr("boxmodel.offsetParent")
+      };
+    }
+
+    return {};
+  },
+
   onToggleExpander() {
     this.setState({
       isOpen: !this.state.isOpen,
     });
   },
 
   render() {
-    let { boxModel } = this.props;
+    let {
+      boxModel,
+      setSelectedNode,
+      onHideBoxModelHighlighter,
+      onShowBoxModelHighlighterForNode,
+    } = this.props;
     let { layout } = boxModel;
 
     let layoutInfo = ["box-sizing", "display", "float",
                       "line-height", "position", "z-index"];
 
-    const properties = layoutInfo.map(info => ComputedProperty({
-      name: info,
-      key: info,
-      value: layout[info],
-    }));
+    const properties = layoutInfo.map(info => {
+      let { referenceElement, referenceElementType } = this.getReferenceElement(info);
+
+      return ComputedProperty({
+        name: info,
+        key: info,
+        value: layout[info],
+        referenceElement,
+        referenceElementType,
+        setSelectedNode,
+        onHideBoxModelHighlighter,
+        onShowBoxModelHighlighterForNode,
+      });
+    });
 
     return dom.div(
       {
         className: "boxmodel-properties",
       },
       dom.div(
         {
           className: "boxmodel-properties-header",
--- a/devtools/client/inspector/boxmodel/components/ComputedProperty.js
+++ b/devtools/client/inspector/boxmodel/components/ComputedProperty.js
@@ -1,32 +1,101 @@
 /* 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 { addons, createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const { Rep } = REPS;
 
 module.exports = createClass({
 
   displayName: "ComputedProperty",
 
   propTypes: {
     name: PropTypes.string.isRequired,
     value: PropTypes.string,
+    referenceElement: PropTypes.object,
+    referenceElementType: PropTypes.string,
+    setSelectedNode: PropTypes.func.isRequired,
+    onHideBoxModelHighlighter: PropTypes.func.isRequired,
+    onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
+  /**
+   * While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
+   * translate nodeFront to a grip-like object that can be used with an ElementNode rep.
+   *
+   * @param  {NodeFront} nodeFront
+   *         The NodeFront for which we want to create a grip-like object.
+   * @return {Object} a grip-like object that can be used with Reps.
+   */
+  translateNodeFrontToGrip(nodeFront) {
+    let {
+      attributes
+    } = nodeFront;
+
+    // The main difference between NodeFront and grips is that attributes are treated as
+    // a map in grips and as an array in NodeFronts.
+    let attributesMap = {};
+    for (let { name, value } of attributes) {
+      attributesMap[name] = value;
+    }
+
+    return {
+      actor: nodeFront.actorID,
+      preview: {
+        attributes: attributesMap,
+        attributesLength: attributes.length,
+        // nodeName is already lowerCased in Node grips
+        nodeName: nodeFront.nodeName.toLowerCase(),
+        nodeType: nodeFront.nodeType,
+        isConnected: true,
+      }
+    };
+  },
+
   onFocus() {
     this.container.focus();
   },
 
+  renderReferenceElementPreview() {
+    let {
+      referenceElement,
+      referenceElementType,
+      setSelectedNode,
+      onShowBoxModelHighlighterForNode,
+      onHideBoxModelHighlighter
+    } = this.props;
+
+    if (!referenceElement) {
+      return null;
+    }
+
+    return dom.div(
+      {
+        className: "reference-element"
+      },
+      dom.span({ className: "reference-element-type" }, referenceElementType),
+      Rep({
+        defaultRep: referenceElement,
+        mode: MODE.TINY,
+        object: this.translateNodeFrontToGrip(referenceElement),
+        onInspectIconClick: () => setSelectedNode(referenceElement, "box-model"),
+        onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(referenceElement),
+        onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
+      })
+    );
+  },
+
   render() {
     const { name, value } = this.props;
 
     return dom.div(
       {
         className: "property-view",
         "data-property-name": name,
         tabIndex: "0",
@@ -55,14 +124,15 @@ module.exports = createClass({
         dom.div(
           {
             className: "property-value theme-fg-color1",
             dir: "ltr",
             tabIndex: "",
             onClick: this.onFocus,
           },
           value
-        )
+        ),
+        this.renderReferenceElementPreview()
       )
     );
   },
 
 });
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js
@@ -11,30 +11,30 @@ const TEST_URI = `
   <div id="relative_parent" style="position: relative">
     <div id="absolute_child" style="position: absolute"></div>
   </div>
   <div id="static"></div>
   <div id="no_parent" style="position: absolute"></div>
   <div id="fixed" style="position: fixed"></div>
 `;
 
-const OFFSET_PARENT_SELECTOR = ".boxmodel-offset-parent .objectBox-node";
+const OFFSET_PARENT_SELECTOR = ".property-value-container .objectBox-node";
 
 const res1 = [
   {
     selector: "#absolute_child",
     offsetParentValue: "div#relative_parent"
   },
   {
     selector: "#no_parent",
     offsetParentValue: "body"
   },
   {
     selector: "#relative_parent",
-    offsetParentValue: null
+    offsetParentValue: "body"
   },
   {
     selector: "#static",
     offsetParentValue: null
   },
   {
     selector: "#fixed",
     offsetParentValue: null
@@ -52,37 +52,37 @@ const res2 = [
   {
     selector: "#absolute_child",
     offsetParentValue: null
   },
 ];
 
 add_task(function* () {
   yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
-  let { inspector, view, testActor } = yield openBoxModelView();
+  let { inspector, boxmodel, testActor } = yield openLayoutView();
 
-  yield testInitialValues(inspector, view);
-  yield testChangingValues(inspector, view, testActor);
+  yield testInitialValues(inspector, boxmodel);
+  yield testChangingValues(inspector, boxmodel, testActor);
 });
 
-function* testInitialValues(inspector, view) {
+function* testInitialValues(inspector, boxmodel) {
   info("Test that the initial values of the box model offset parent are correct");
-  let viewdoc = view.document;
+  let viewdoc = boxmodel.document;
 
   for (let { selector, offsetParentValue } of res1) {
     yield selectNode(selector, inspector);
 
     let elt = viewdoc.querySelector(OFFSET_PARENT_SELECTOR);
     is(elt && elt.textContent, offsetParentValue, selector + " has the right value.");
   }
 }
 
-function* testChangingValues(inspector, view, testActor) {
+function* testChangingValues(inspector, boxmodel, testActor) {
   info("Test that changing the document updates the box model");
-  let viewdoc = view.document;
+  let viewdoc = boxmodel.document;
 
   for (let { selector, update } of updates) {
     let onUpdated = waitForUpdate(inspector);
     yield testActor.setAttribute(selector, "style", update);
     yield onUpdated;
   }
 
   for (let { selector, offsetParentValue } of res2) {
--- a/devtools/client/inspector/markup/views/read-only-editor.js
+++ b/devtools/client/inspector/markup/views/read-only-editor.js
@@ -18,16 +18,19 @@ function ReadOnlyEditor(container, node)
     this.tag.classList.add("theme-fg-color5");
     this.tag.textContent = node.isBeforePseudoElement ? "::before" : "::after";
   } else if (node.nodeType == nodeConstants.DOCUMENT_TYPE_NODE) {
     this.elt.classList.add("comment", "doctype");
     this.tag.textContent = node.doctypeString;
   } else {
     this.tag.textContent = node.nodeName;
   }
+
+  // Make the "tag" part of this editor focusable.
+  this.tag.setAttribute("tabindex", "-1");
 }
 
 ReadOnlyEditor.prototype = {
   buildMarkup: function () {
     let doc = this.markup.doc;
 
     this.elt = doc.createElement("span");
     this.elt.classList.add("editor");
--- a/devtools/client/locales/en-US/boxmodel.properties
+++ b/devtools/client/locales/en-US/boxmodel.properties
@@ -38,8 +38,14 @@ boxmodel.content=content
 # LOCALIZATION NOTE: (boxmodel.geometryButton.tooltip) This label is displayed as a
 # tooltip that appears when hovering over the button that allows users to edit the
 # position of an element in the page.
 boxmodel.geometryButton.tooltip=Edit position
 
 # LOCALIZATION NOTE: (boxmodel.propertiesLabel) This label is displayed as the header
 # for showing and collapsing the properties underneath the box model in the layout view
 boxmodel.propertiesLabel=Box Model Properties
+
+# LOCALIZATION NOTE: (boxmodel.offsetParent) This label is displayed inside the list of
+# properties, below the box model, in the layout view. It is displayed next to the
+# position property, when position is absolute, relative, sticky. This label tells users
+# what the DOM node previewed next to it is: an offset parent for the position element.
+boxmodel.offsetParent=offset
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -4,16 +4,18 @@
 
 /**
  * This is the stylesheet of the Box Model view implemented in the layout panel.
  */
 
 .boxmodel-container {
   overflow: auto;
   padding-bottom: 4px;
+  max-width: 600px;
+  margin: 0 auto;
 }
 
 /* Header */
 
 .boxmodel-header,
 .boxmodel-info {
   display: flex;
   align-items: center;
@@ -319,26 +321,54 @@
   padding: 2px 3px;
 }
 
 .boxmodel-properties-expander {
   vertical-align: middle;
   display: inline-block;
 }
 
+.boxmodel-properties-wrapper {
+  column-width: 250px;
+  column-gap: 20px;
+  column-rule: 1px solid var(--theme-splitter-color);
+}
+
 .boxmodel-properties-wrapper .property-view {
   padding-inline-start: 17px;
 }
 
 .boxmodel-properties-wrapper .property-name-container {
   flex: 1;
 }
 
 .boxmodel-properties-wrapper .property-value-container {
   flex: 1;
+  display: block;
+}
+
+.boxmodel-container .reference-element {
+  margin-inline-start: 14px;
+  margin-block-start: 4px;
+  display: block;
+}
+
+/* Tag displayed next to DOM Node previews (used to display reference elements) */
+
+.boxmodel-container .reference-element-type {
+  background: var(--theme-highlight-purple);
+  color: white;
+  padding: 1px 2px;
+  border-radius: 2px;
+  font-size: 9px;
+  margin-inline-end: 5px;
+}
+
+.theme-dark .boxmodel-container .reference-element-type {
+  color: black;
 }
 
 /* Box Model Main - Offset Parent */
 
 .boxmodel-offset-parent {
   position: absolute;
   top: -20px;
   right: -10px;
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -643,18 +643,19 @@ Element::GetScrollFrame(nsIFrame **aStyl
     *aStyledFrame = frame;
   }
   if (!frame) {
     return nullptr;
   }
 
   // menu frames implement GetScrollTargetFrame but we don't want
   // to use it here.  Similar for comboboxes.
-  FrameType type = frame->Type();
-  if (type != FrameType::Menu && type != FrameType::ComboboxControl) {
+  LayoutFrameType type = frame->Type();
+  if (type != LayoutFrameType::Menu &&
+      type != LayoutFrameType::ComboboxControl) {
     nsIScrollableFrame *scrollFrame = frame->GetScrollTargetFrame();
     if (scrollFrame)
       return scrollFrame;
   }
 
   nsIDocument* doc = OwnerDoc();
   bool quirksMode = doc->GetCompatibilityMode() == eCompatibility_NavQuirks;
   Element* elementWithRootScrollInfo =
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -2712,17 +2712,17 @@ nsFocusManager::DetermineElementToMoveFo
         ignoreTabIndex = true;
     }
 
     // check if the focus is currently inside a popup. Elements such as the
     // autocomplete widget use the noautofocus attribute to allow the focus to
     // remain outside the popup when it is opened.
     if (frame) {
       popupFrame =
-        nsLayoutUtils::GetClosestFrameOfType(frame, FrameType::MenuPopup);
+        nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::MenuPopup);
     }
 
     if (popupFrame && !forDocumentNavigation) {
       // Don't navigate outside of a popup, so pretend that the
       // root content is the popup itself
       rootContent = popupFrame->GetContent();
       NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
     }
@@ -2938,18 +2938,18 @@ nsFocusManager::DetermineElementToMoveFo
       }
 
       // if the frame is inside a popup, make sure to scan only within the
       // popup. This handles the situation of tabbing amongst elements
       // inside an iframe which is itself inside a popup. Otherwise,
       // navigation would move outside the popup when tabbing outside the
       // iframe.
       if (!forDocumentNavigation) {
-        popupFrame =
-          nsLayoutUtils::GetClosestFrameOfType(frame, FrameType::MenuPopup);
+        popupFrame = nsLayoutUtils::GetClosestFrameOfType(
+          frame, LayoutFrameType::MenuPopup);
         if (popupFrame) {
           rootContent = popupFrame->GetContent();
           NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
         }
       }
     }
     else {
       // There is no parent, so call the tree owner. This will tell the
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -3508,18 +3508,19 @@ GetRequiredInnerTextLineBreakCount(nsIFr
     return 1;
   }
   return 0;
 }
 
 static bool
 IsLastCellOfRow(nsIFrame* aFrame)
 {
-  FrameType type = aFrame->Type();
-  if (type != FrameType::TableCell && type != FrameType::BCTableCell) {
+  LayoutFrameType type = aFrame->Type();
+  if (type != LayoutFrameType::TableCell &&
+      type != LayoutFrameType::BCTableCell) {
     return true;
   }
   for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
     if (c->GetNextSibling()) {
       return false;
     }
   }
   return true;
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2734,20 +2734,20 @@ EventStateManager::DecideGestureEvent(Wi
     // assist is solving this.
     if (current && IsRemoteTarget(current->GetContent())) {
       panDirection = WidgetGestureNotifyEvent::ePanBoth;
       // We don't know when we reach bounds, so just disable feedback for now.
       displayPanFeedback = false;
       break;
     }
 
-    FrameType currentFrameType = current->Type();
+    LayoutFrameType currentFrameType = current->Type();
 
     // Scrollbars should always be draggable
-    if (currentFrameType == FrameType::Scrollbar) {
+    if (currentFrameType == LayoutFrameType::Scrollbar) {
       panDirection = WidgetGestureNotifyEvent::ePanNone;
       break;
     }
 
 #ifdef MOZ_XUL
     // Special check for trees
     nsTreeBodyFrame* treeFrame = do_QueryFrame(current);
     if (treeFrame) {
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -234,28 +234,28 @@ nsGenericHTMLElement::GetAccessKeyLabel(
   GetAccessKey(suffix);
   if (!suffix.IsEmpty()) {
     EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
     aLabel.Append(suffix);
   }
 }
 
 static bool
-IS_TABLE_CELL(FrameType frameType)
+IS_TABLE_CELL(LayoutFrameType frameType)
 {
-  return FrameType::TableCell == frameType ||
-         FrameType::BCTableCell == frameType;
+  return LayoutFrameType::TableCell == frameType ||
+         LayoutFrameType::BCTableCell == frameType;
 }
 
 static bool
 IsOffsetParent(nsIFrame* aFrame)
 {
-  FrameType frameType = aFrame->Type();
-
-  if (IS_TABLE_CELL(frameType) || frameType == FrameType::Table) {
+  LayoutFrameType frameType = aFrame->Type();
+
+  if (IS_TABLE_CELL(frameType) || frameType == LayoutFrameType::Table) {
     // Per the IDL for Element, only td, th, and table are acceptable offsetParents
     // apart from body or positioned elements; we need to check the content type as
     // well as the frame type so we ignore anonymous tables created by an element
     // with display: table-cell with no actual table
     nsIContent* content = aFrame->GetContent();
 
     return content->IsAnyOfHTMLElements(nsGkAtoms::table,
                                         nsGkAtoms::td,
--- a/dom/media/MediaEventSource.h
+++ b/dom/media/MediaEventSource.h
@@ -8,17 +8,16 @@
 #define MediaEventSource_h_
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/IndexSequence.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
-#include "mozilla/UniquePtr.h"
 
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
 /**
@@ -39,18 +38,22 @@ public:
   void Revoke() {
     mRevoked = true;
   }
 
   bool IsRevoked() const {
     return mRevoked;
   }
 
+protected:
+  // Virtual destructor is required since we might delete a Listener object
+  // through its base type pointer.
+  virtual ~RevocableToken() { }
+
 private:
-  ~RevocableToken() {}
   Atomic<bool> mRevoked;
 };
 
 enum class ListenerPolicy : int8_t {
   // Allow at most one listener. Move will be used when possible
   // to pass the event data to save copy.
   Exclusive,
   // Allow multiple listeners. Event data will always be copied when passed
@@ -118,181 +121,127 @@ template <typename T>
 class RawPtr {
 public:
   explicit RawPtr(T* aPtr) : mPtr(aPtr) {}
   T* get() const { return mPtr; }
 private:
   T* const mPtr;
 };
 
-/**
- * A helper class to pass event data to the listeners. Optimized to save
- * copy when Move is possible or |Function| takes no arguments.
- */
-template<typename Target, typename Function>
-class ListenerHelper {
-  // Define our custom runnable to minimize copy of the event data.
-  // NS_NewRunnableFunction will result in 2 copies of the event data.
-  // One is captured by the lambda and the other is the copy of the lambda.
+template <typename... As>
+class Listener : public RevocableToken
+{
+public:
   template <typename... Ts>
-  class R : public Runnable {
-  public:
-    template <typename... Us>
-    R(RevocableToken* aToken, const Function& aFunction, Us&&... aEvents)
-      : mToken(aToken)
-      , mFunction(aFunction)
-      , mEvents(Forward<Us>(aEvents)...) {}
-
-    template <typename... Vs, size_t... Is>
-    void Invoke(Tuple<Vs...>& aEvents, IndexSequence<Is...>) {
-      // Enable move whenever possible since mEvent won't be used anymore.
-      mFunction(Move(Get<Is>(aEvents))...);
+  void Dispatch(Ts&&... aEvents)
+  {
+    if (CanTakeArgs()) {
+      DispatchTask(NewRunnableMethod<typename Decay<Ts>::Type&&...>(
+        this, &Listener::ApplyWithArgs, Forward<Ts>(aEvents)...));
+    } else {
+      DispatchTask(NewRunnableMethod(this, &Listener::ApplyWithNoArgs));
     }
-
-    NS_IMETHOD Run() override {
-      // Don't call the listener if it is disconnected.
-      if (!mToken->IsRevoked()) {
-        Invoke(mEvents, typename IndexSequenceFor<Ts...>::Type());
-      }
-      return NS_OK;
-    }
-
-  private:
-    RefPtr<RevocableToken> mToken;
-    Function mFunction;
-
-    template <typename T>
-    using ArgType = typename RemoveCV<typename RemoveReference<T>::Type>::Type;
-    Tuple<ArgType<Ts>...> mEvents;
-  };
-
-public:
-  ListenerHelper(RevocableToken* aToken, Target* aTarget, const Function& aFunc)
-    : mToken(aToken), mTarget(aTarget), mFunction(aFunc) {}
-
-  // |F| takes one or more arguments.
-  template <typename F, typename... Ts>
-  typename EnableIf<TakeArgs<F>::value, void>::Type
-  DispatchHelper(const F& aFunc, Ts&&... aEvents) {
-    nsCOMPtr<nsIRunnable> r =
-      new R<Ts...>(mToken, aFunc, Forward<Ts>(aEvents)...);
-    EventTarget<Target>::Dispatch(mTarget.get(), r.forget());
   }
 
-  // |F| takes no arguments. Don't bother passing aEvent.
-  template <typename F, typename... Ts>
-  typename EnableIf<!TakeArgs<F>::value, void>::Type
-  DispatchHelper(const F& aFunc, Ts&&...) {
-    const RefPtr<RevocableToken>& token = mToken;
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
-      // Don't call the listener if it is disconnected.
-      if (!token->IsRevoked()) {
-        aFunc();
-      }
-    });
-    EventTarget<Target>::Dispatch(mTarget.get(), r.forget());
-  }
-
-  template <typename... Ts>
-  void Dispatch(Ts&&... aEvents) {
-    DispatchHelper(mFunction, Forward<Ts>(aEvents)...);
+protected:
+  virtual ~Listener()
+  {
+    MOZ_ASSERT(IsRevoked(), "Must disconnect the listener.");
   }
 
 private:
-  RefPtr<RevocableToken> mToken;
-  const RefPtr<Target> mTarget;
-  Function mFunction;
-};
-
-/**
- * Define whether an event data should be copied or moved to the listeners.
- *
- * @Copy Data will always be copied. Each listener gets a copy.
- * @Move Data will always be moved.
- */
-enum class EventPassMode : int8_t {
-  Copy,
-  Move
-};
+  virtual void DispatchTask(already_AddRefed<nsIRunnable> aTask) = 0;
 
-class ListenerBase {
-public:
-  ListenerBase() : mToken(new RevocableToken()) {}
-  ~ListenerBase() {
-    MOZ_ASSERT(Token()->IsRevoked(), "Must disconnect the listener.");
-  }
-  RevocableToken* Token() const {
-    return mToken;
-  }
-private:
-  const RefPtr<RevocableToken> mToken;
-};
-
-/**
- * Stored by MediaEventSource to send notifications to the listener.
- * Since virtual methods can not be templated, this class is specialized
- * to provide different Dispatch() overloads depending on EventPassMode.
- */
-template <EventPassMode Mode, typename... As>
-class Listener : public ListenerBase {
-public:
-  virtual ~Listener() {}
-  virtual void Dispatch(const As&... aEvents) = 0;
-};
-
-template <typename... As>
-class Listener<EventPassMode::Move, As...> : public ListenerBase {
-public:
-  virtual ~Listener() {}
-  virtual void Dispatch(As... aEvents) = 0;
+  // True if the underlying listener function takes non-zero arguments.
+  virtual bool CanTakeArgs() const = 0;
+  // Pass the event data to the underlying listener function. Should be called
+  // only when CanTakeArgs() returns true.
+  virtual void ApplyWithArgs(As&&... aEvents) = 0;
+  // Invoke the underlying listener function. Should be called only when
+  // CanTakeArgs() returns false.
+  virtual void ApplyWithNoArgs() = 0;
 };
 
 /**
  * Store the registered target thread and function so it knows where and to
  * whom to send the event data.
  */
-template <typename Target, typename Function, EventPassMode, typename... As>
-class ListenerImpl : public Listener<EventPassMode::Copy, As...> {
-public:
-  ListenerImpl(Target* aTarget, const Function& aFunction)
-    : mHelper(ListenerBase::Token(), aTarget, aFunction) {}
-  void Dispatch(const As&... aEvents) override {
-    mHelper.Dispatch(aEvents...);
-  }
-private:
-  ListenerHelper<Target, Function> mHelper;
-};
-
 template <typename Target, typename Function, typename... As>
-class ListenerImpl<Target, Function, EventPassMode::Move, As...>
-  : public Listener<EventPassMode::Move, As...> {
+class ListenerImpl : public Listener<As...>
+{
 public:
   ListenerImpl(Target* aTarget, const Function& aFunction)
-    : mHelper(ListenerBase::Token(), aTarget, aFunction) {}
-  void Dispatch(As... aEvents) override {
-    mHelper.Dispatch(Move(aEvents)...);
+    : mTarget(aTarget)
+    , mFunction(aFunction)
+  {
+  }
+
+private:
+  void DispatchTask(already_AddRefed<nsIRunnable> aTask) override
+  {
+    EventTarget<Target>::Dispatch(mTarget.get(), Move(aTask));
+  }
+
+  bool CanTakeArgs() const override
+  {
+    return TakeArgs<Function>::value;
   }
-private:
-  ListenerHelper<Target, Function> mHelper;
-};
+
+  // |F| takes one or more arguments.
+  template <typename F>
+  typename EnableIf<TakeArgs<F>::value, void>::Type
+  ApplyWithArgsImpl(const F& aFunc, As&&... aEvents)
+  {
+    aFunc(Move(aEvents)...);
+  }
+
+  // |F| takes no arguments.
+  template <typename F>
+  typename EnableIf<!TakeArgs<F>::value, void>::Type
+  ApplyWithArgsImpl(const F& aFunc, As&&... aEvents)
+  {
+    MOZ_CRASH("Call ApplyWithNoArgs instead.");
+  }
 
-/**
- * Select EventPassMode based on ListenerPolicy.
- *
- * @Copy Selected when ListenerPolicy is NonExclusive because each listener
- * must get a copy.
- *
- * @Move Selected when ListenerPolicy is Exclusive. All types passed to
- * MediaEventProducer::Notify() must be movable.
- */
-template <ListenerPolicy Lp>
-struct PassModePicker {
-  static const EventPassMode Value =
-    Lp == ListenerPolicy::NonExclusive ?
-    EventPassMode::Copy : EventPassMode::Move;
+  void ApplyWithArgs(As&&... aEvents) override
+  {
+    MOZ_RELEASE_ASSERT(TakeArgs<Function>::value);
+    // Don't call the listener if it is disconnected.
+    if (!RevocableToken::IsRevoked()) {
+      ApplyWithArgsImpl(mFunction, Move(aEvents)...);
+    }
+  }
+
+  // |F| takes one or more arguments.
+  template <typename F>
+  typename EnableIf<TakeArgs<F>::value, void>::Type
+  ApplyWithNoArgsImpl(const F& aFunc)
+  {
+    MOZ_CRASH("Call ApplyWithArgs instead.");
+  }
+
+  // |F| takes no arguments.
+  template <typename F>
+  typename EnableIf<!TakeArgs<F>::value, void>::Type
+  ApplyWithNoArgsImpl(const F& aFunc)
+  {
+    aFunc();
+  }
+
+  virtual void ApplyWithNoArgs() override
+  {
+    MOZ_RELEASE_ASSERT(!TakeArgs<Function>::value);
+    // Don't call the listener if it is disconnected.
+    if (!RevocableToken::IsRevoked()) {
+      ApplyWithNoArgsImpl(mFunction);
+    }
+  }
+
+  const RefPtr<Target> mTarget;
+  Function mFunction;
 };
 
 /**
  * Return true if any type is a reference type.
  */
 template <typename Head, typename... Tails>
 struct IsAnyReference {
   static const bool value = IsReference<Head>::value ||
@@ -358,46 +307,42 @@ private:
 template <ListenerPolicy Lp, typename... Es>
 class MediaEventSourceImpl {
   static_assert(!detail::IsAnyReference<Es...>::value,
                 "Ref-type not supported!");
 
   template <typename T>
   using ArgType = typename detail::EventTypeTraits<T>::ArgType;
 
-  static const detail::EventPassMode PassMode =
-    detail::PassModePicker<Lp>::Value;
-
-  typedef detail::Listener<PassMode, ArgType<Es>...> Listener;
+  typedef detail::Listener<ArgType<Es>...> Listener;
 
   template<typename Target, typename Func>
-  using ListenerImpl =
-    detail::ListenerImpl<Target, Func, PassMode, ArgType<Es>...>;
+  using ListenerImpl = detail::ListenerImpl<Target, Func, ArgType<Es>...>;
 
   template <typename Method>
   using TakeArgs = detail::TakeArgs<Method>;
 
   void PruneListeners() {
     int32_t last = static_cast<int32_t>(mListeners.Length()) - 1;
     for (int32_t i = last; i >= 0; --i) {
-      if (mListeners[i]->Token()->IsRevoked()) {
+      if (mListeners[i]->IsRevoked()) {
         mListeners.RemoveElementAt(i);
       }
     }
   }
 
   template<typename Target, typename Function>
   MediaEventListener
   ConnectInternal(Target* aTarget, const Function& aFunction) {
     MutexAutoLock lock(mMutex);
     PruneListeners();
     MOZ_ASSERT(Lp == ListenerPolicy::NonExclusive || mListeners.IsEmpty());
     auto l = mListeners.AppendElement();
-    l->reset(new ListenerImpl<Target, Function>(aTarget, aFunction));
-    return MediaEventListener((*l)->Token());
+    *l = new ListenerImpl<Target, Function>(aTarget, aFunction);
+    return MediaEventListener(*l);
   }
 
   // |Method| takes one or more arguments.
   template <typename Target, typename This, typename Method>
   typename EnableIf<TakeArgs<Method>::value, MediaEventListener>::Type
   ConnectInternal(Target* aTarget, This* aThis, Method aMethod) {
     detail::RawPtr<This> thiz(aThis);
     auto f = [=] (ArgType<Es>&&... aEvents) {
@@ -467,27 +412,27 @@ protected:
   template <typename... Ts>
   void NotifyInternal(Ts&&... aEvents) {
     MutexAutoLock lock(mMutex);
     int32_t last = static_cast<int32_t>(mListeners.Length()) - 1;
     for (int32_t i = last; i >= 0; --i) {
       auto&& l = mListeners[i];
       // Remove disconnected listeners.
       // It is not optimal but is simple and works well.
-      if (l->Token()->IsRevoked()) {
+      if (l->IsRevoked()) {
         mListeners.RemoveElementAt(i);
         continue;
       }
       l->Dispatch(Forward<Ts>(aEvents)...);
     }
   }
 
 private:
   Mutex mMutex;
-  nsTArray<UniquePtr<Listener>> mListeners;
+  nsTArray<RefPtr<Listener>> mListeners;
 };
 
 template <typename... Es>
 using MediaEventSource =
   MediaEventSourceImpl<ListenerPolicy::NonExclusive, Es...>;
 
 template <typename... Es>
 using MediaEventSourceExc =
@@ -498,17 +443,18 @@ using MediaEventSourceExc =
  * and event publisher. Mostly used as a member variable to publish events
  * to the listeners.
  */
 template <typename... Es>
 class MediaEventProducer : public MediaEventSource<Es...> {
 public:
   template <typename... Ts>
   void Notify(Ts&&... aEvents) {
-    this->NotifyInternal(Forward<Ts>(aEvents)...);
+    // Pass lvalues to prevent move in NonExclusive mode.
+    this->NotifyInternal(aEvents...);
   }
 };
 
 /**
  * Specialization for void type. A dummy bool is passed to NotifyInternal
  * since there is no way to pass a void value.
  */
 template <>
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -3018,17 +3018,17 @@ SourceMediaStream::AddDirectTrackListene
       DirectMediaStreamTrackListener::InstallationResult::TRACK_NOT_FOUND_AT_SOURCE);
     return;
   }
   if (!isAudio && !isVideo) {
     LOG(
       LogLevel::Warning,
       ("Source track for direct track listener %p is unknown", listener.get()));
     // It is not a video or audio track.
-    MOZ_ASSERT(true);
+    MOZ_ASSERT(false);
     return;
   }
   LOG(
     LogLevel::Debug,
     ("Added direct track listener %p. ended=%d", listener.get(), !updateData));
   listener->NotifyDirectListenerInstalled(
     DirectMediaStreamTrackListener::InstallationResult::SUCCESS);
   if (!updateData) {
--- a/dom/media/VideoSegment.cpp
+++ b/dom/media/VideoSegment.cpp
@@ -45,17 +45,16 @@ VideoFrame::TakeFrom(VideoFrame* aFrame)
 
 /* static */ already_AddRefed<Image>
 VideoFrame::CreateBlackImage(const gfx::IntSize& aSize)
 {
   RefPtr<ImageContainer> container =
     LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
   RefPtr<PlanarYCbCrImage> image = container->CreatePlanarYCbCrImage();
   if (!image) {
-    MOZ_ASSERT(false);
     return nullptr;
   }
 
   int len = ((aSize.width * aSize.height) * 3 / 2);
 
   // Generate a black image.
   auto frame = MakeUnique<uint8_t[]>(len);
   int y = aSize.width * aSize.height;
@@ -77,17 +76,16 @@ VideoFrame::CreateBlackImage(const gfx::
   data.mCbCrSize = gfx::IntSize(aSize.width / 2, aSize.height / 2);
   data.mPicX = 0;
   data.mPicY = 0;
   data.mPicSize = gfx::IntSize(aSize.width, aSize.height);
   data.mStereoMode = StereoMode::MONO;
 
   // Copies data, so we can free data.
   if (!image->CopyData(data)) {
-    MOZ_ASSERT(false);
     return nullptr;
   }
 
   return image.forget();
 }
 
 VideoChunk::VideoChunk()
 {}
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -342,17 +342,21 @@ static bool isYUV444(const PlanarYCbCrIm
 }
 
 nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk)
 {
   RefPtr<Image> img;
   if (aChunk.mFrame.GetForceBlack() || aChunk.IsNull()) {
     if (!mMuteFrame) {
       mMuteFrame = VideoFrame::CreateBlackImage(gfx::IntSize(mFrameWidth, mFrameHeight));
-      MOZ_ASSERT(mMuteFrame);
+    }
+    if (!mMuteFrame) {
+      VP8LOG(LogLevel::Warning, "Failed to allocate black image of size %dx%d",
+             mFrameWidth, mFrameHeight);
+      return NS_OK;
     }
     img = mMuteFrame;
   } else {
     img = aChunk.mFrame.GetImage();
   }
 
   if (img->GetSize() != IntSize(mFrameWidth, mFrameHeight)) {
     VP8LOG(LogLevel::Info,
--- a/dom/media/gtest/TestMediaEventSource.cpp
+++ b/dom/media/gtest/TestMediaEventSource.cpp
@@ -330,8 +330,47 @@ TEST(MediaEventSource, MoveOnly)
   // It is an error to pass an lvalue which is move-only.
   // UniquePtr<int> event(new int(30));
   // source.Notify(event);
 
   queue->BeginShutdown();
   queue->AwaitShutdownAndIdle();
   listener.Disconnect();
 }
+
+struct RefCounter
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCounter)
+  explicit RefCounter(int aVal) : mVal(aVal) { }
+  int mVal;
+private:
+  ~RefCounter() { }
+};
+
+/*
+ * Test we should copy instead of move in NonExclusive mode
+ * for each listener must get a copy.
+ */
+TEST(MediaEventSource, NoMove)
+{
+  RefPtr<TaskQueue> queue = new TaskQueue(
+    GetMediaThreadPool(MediaThreadType::PLAYBACK));
+
+  MediaEventProducer<RefPtr<RefCounter>> source;
+
+  auto func1 = [] (RefPtr<RefCounter>&& aEvent) {
+    EXPECT_EQ(aEvent->mVal, 20);
+  };
+  auto func2 = [] (RefPtr<RefCounter>&& aEvent) {
+    EXPECT_EQ(aEvent->mVal, 20);
+  };
+  MediaEventListener listener1 = source.Connect(queue, func1);
+  MediaEventListener listener2 = source.Connect(queue, func2);
+
+  // We should copy this rvalue instead of move it in NonExclusive mode.
+  RefPtr<RefCounter> val = new RefCounter(20);
+  source.Notify(Move(val));
+
+  queue->BeginShutdown();
+  queue->AwaitShutdownAndIdle();
+  listener1.Disconnect();
+  listener2.Disconnect();
+}
--- a/dom/media/imagecapture/CaptureTask.cpp
+++ b/dom/media/imagecapture/CaptureTask.cpp
@@ -123,34 +123,34 @@ CaptureTask::SetCurrentFrames(const Vide
       mTask = nullptr;
       return NS_OK;
     }
 
   protected:
     RefPtr<CaptureTask> mTask;
   };
 
-  VideoSegment::ConstChunkIterator iter(aSegment);
-
-
-
-  while (!iter.IsEnded()) {
+  for (VideoSegment::ConstChunkIterator iter(aSegment);
+       !iter.IsEnded(); iter.Next()) {
     VideoChunk chunk = *iter;
 
     // Extract the first valid video frame.
     VideoFrame frame;
     if (!chunk.IsNull()) {
       RefPtr<layers::Image> image;
       if (chunk.mFrame.GetForceBlack()) {
         // Create a black image.
         image = VideoFrame::CreateBlackImage(chunk.mFrame.GetIntrinsicSize());
       } else {
         image = chunk.mFrame.GetImage();
       }
-      MOZ_ASSERT(image);
+      if (!image) {
+        MOZ_ASSERT(image);
+        continue;
+      }
       mImageGrabbedOrTrackEnd = true;
 
       // Encode image.
       nsresult rv;
       nsAutoString type(NS_LITERAL_STRING("image/jpeg"));
       nsAutoString options;
       rv = dom::ImageEncoder::ExtractDataFromLayersImageAsync(
                                 type,
@@ -158,17 +158,16 @@ CaptureTask::SetCurrentFrames(const Vide
                                 false,
                                 image,
                                 new EncodeComplete(this));
       if (NS_FAILED(rv)) {
         PostTrackEndEvent();
       }
       return;
     }
-    iter.Next();
   }
 }
 
 void
 CaptureTask::PostTrackEndEvent()
 {
   mImageGrabbedOrTrackEnd = true;
 
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/crashtests/1348381.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+  <title>Bug 1348381: Crash when recording extremely large canvas' captureStream</title>
+</head>
+</body>
+<canvas id="c" height="0.6"></canvas>
+<img id="img" src="data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA="></img>
+<script type="application/javascript">
+const c = document.getElementById("c");
+const ctx = c.getContext('2d');
+const s = c.captureStream(0);
+const mr = new MediaRecorder(s);
+const t = s.getVideoTracks()[0];
+mr.start();
+const img = document.getElementById('img');
+t.enabled = false;
+ctx.drawImage(img, 16, 18014398509481984);
+setTimeout(() => document.documentElement.removeAttribute("class"), 100);
+</script>
+</body>
+</html>
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -9,8 +9,9 @@ load 802982.html
 load 812785.html
 load 834100.html
 load 836349.html
 load 837324.html
 load 855796.html
 load 860143.html
 load 861958.html
 load 863929.html
+load 1348381.html
--- a/dom/svg/SVGTextContentElement.cpp
+++ b/dom/svg/SVGTextContentElement.cpp
@@ -33,26 +33,26 @@ nsSVGElement::LengthInfo SVGTextContentE
   { &nsGkAtoms::textLength, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::XY }
 };
 
 SVGTextFrame*
 SVGTextContentElement::GetSVGTextFrame()
 {
   nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
   nsIFrame* textFrame =
-    nsLayoutUtils::GetClosestFrameOfType(frame, FrameType::SVGText);
+    nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText);
   return static_cast<SVGTextFrame*>(textFrame);
 }
 
 SVGTextFrame*
 SVGTextContentElement::GetSVGTextFrameForNonLayoutDependentQuery()
 {
   nsIFrame* frame = GetPrimaryFrame(FlushType::Frames);
   nsIFrame* textFrame =
-    nsLayoutUtils::GetClosestFrameOfType(frame, FrameType::SVGText);
+    nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText);
   return static_cast<SVGTextFrame*>(textFrame);
 }
 
 already_AddRefed<SVGAnimatedLength>
 SVGTextContentElement::TextLength()
 {
   return LengthAttributes()[TEXTLENGTH].ToDOMAnimatedLength(this);
 }
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -295,18 +295,20 @@ void
 WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
 {
   mImageKeys.push_back(key);
 }
 
 void
 WebRenderLayerManager::DiscardImages()
 {
-  for (auto key : mImageKeys) {
+  if (!WrBridge()->IsDestroyed()) {
+    for (auto key : mImageKeys) {
       WrBridge()->SendDeleteImage(key);
+    }
   }
   mImageKeys.clear();
 }
 
 void
 WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
   mDiscardedCompositorAnimationsIds.push_back(aId);
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -1260,17 +1260,17 @@ AccessibleCaretManager::DragCaretInterna
   }
 
   ClearMaintainedSelection();
 
   nsIFrame* anchorFrame = nullptr;
   selection->GetPrimaryFrameForAnchorNode(&anchorFrame);
 
   nsIFrame* scrollable =
-    nsLayoutUtils::GetClosestFrameOfType(anchorFrame, FrameType::Scroll);
+    nsLayoutUtils::GetClosestFrameOfType(anchorFrame, LayoutFrameType::Scroll);
   AutoWeakFrame weakScrollable = scrollable;
   fs->HandleClick(offsets.content, offsets.StartOffset(), offsets.EndOffset(),
                   GetCaretMode() == CaretMode::Selection, false,
                   offsets.associate);
   if (!weakScrollable.IsAlive()) {
     return NS_OK;
   }
 
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -856,26 +856,26 @@ GetPrevContinuationWithSameStyle(nsIFram
     prevContinuation = nullptr;
   }
   return prevContinuation;
 }
 
 nsresult
 GeckoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
-  FrameType frameType = aFrame->Type();
-  if (frameType == FrameType::Placeholder) {
+  LayoutFrameType frameType = aFrame->Type();
+  if (frameType == LayoutFrameType::Placeholder) {
     // Also reparent the out-of-flow and all its continuations.
     nsIFrame* outOfFlow =
       nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
     NS_ASSERTION(outOfFlow, "no out-of-flow frame");
     do {
       ReparentStyleContext(outOfFlow);
     } while ((outOfFlow = outOfFlow->GetNextContinuation()));
-  } else if (frameType == FrameType::Backdrop) {
+  } else if (frameType == LayoutFrameType::Backdrop) {
     // Style context of backdrop frame has no parent style context, and
     // thus we do not need to reparent it.
     return NS_OK;
   }
 
   // DO NOT verify the style tree before reparenting.  The frame
   // tree has already been changed, so this check would just fail.
   nsStyleContext* oldContext = aFrame->StyleContext();
@@ -1655,18 +1655,18 @@ ElementRestyler::MoveStyleContextsForCon
       // FIXME: It is probably safe to just continue here instead of bailing out.
       if (child->IsPlaceholderFrame()) {
         return false;
       }
       nsStyleContext* sc = child->StyleContext();
       if (sc->GetParent() != aOldContext) {
         return false;
       }
-      FrameType type = child->Type();
-      if (type == FrameType::Letter || type == FrameType::Line) {
+      LayoutFrameType type = child->Type();
+      if (type == LayoutFrameType::Letter || type == LayoutFrameType::Line) {
         return false;
       }
       if (sc->HasChildThatUsesGrandancestorStyle()) {
         // XXX Not sure if we need this?
         return false;
       }
       nsIAtom* pseudoTag = sc->GetPseudo();
       if (pseudoTag && !nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
@@ -2063,26 +2063,26 @@ ElementRestyler::ComputeRestyleResultFro
     return;
   }
 
   // Style changes might have moved children between the two nsLetterFrames
   // (the one matching ::first-letter and the one containing the rest of the
   // content).  Continue restyling to the children of the nsLetterFrame so
   // that they get the correct style context parent.  Similarly for
   // nsLineFrames.
-  FrameType type = aSelf->Type();
-
-  if (type == FrameType::Letter) {
+  LayoutFrameType type = aSelf->Type();
+
+  if (type == LayoutFrameType::Letter) {
     LOG_RESTYLE_CONTINUE("frame is a letter frame");
     aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
-  if (type == FrameType::Line) {
+  if (type == LayoutFrameType::Line) {
     LOG_RESTYLE_CONTINUE("frame is a line frame");
     aRestyleResult = RestyleResult::eContinue;
     aCanStopWithStyleChange = false;
     return;
   }
 
   // Some style computations depend not on the parent's style, but a grandparent
   // or one the grandparent's ancestors.  An example is an explicit 'inherit'
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3533,18 +3533,18 @@ PresShell::DoScrollContentIntoView()
     // The reflow flush before this scroll got interrupted, and this frame's
     // coords and size are all zero, and it has no content showing anyway.
     // Don't bother scrolling to it.  We'll try again when we finish up layout.
     return;
   }
 
   // Make sure we skip 'frame' ... if it's scrollable, we should use its
   // scrollable ancestor as the container.
-  nsIFrame* container =
-    nsLayoutUtils::GetClosestFrameOfType(frame->GetParent(), FrameType::Scroll);
+  nsIFrame* container = nsLayoutUtils::GetClosestFrameOfType(
+    frame->GetParent(), LayoutFrameType::Scroll);
   if (!container) {
     // nothing can be scrolled
     return;
   }
 
   ScrollIntoViewData* data = static_cast<ScrollIntoViewData*>(
     mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
   if (MOZ_UNLIKELY(!data)) {
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1073,18 +1073,18 @@ DoApplyRenderingChangeToTree(nsIFrame* a
         nsSVGUtils::ScheduleReflowSVG(aFrame);
       }
     }
     if (aChange & nsChangeHint_UpdateTextPath) {
       if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
         // Invalidate and reflow the entire SVGTextFrame:
         NS_ASSERTION(aFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
                      "expected frame for a <textPath> element");
-        nsIFrame* text =
-          nsLayoutUtils::GetClosestFrameOfType(aFrame, FrameType::SVGText);
+        nsIFrame* text = nsLayoutUtils::GetClosestFrameOfType(
+          aFrame, LayoutFrameType::SVGText);
         NS_ASSERTION(text, "expected to find an ancestor SVGTextFrame");
         static_cast<SVGTextFrame*>(text)->NotifyGlyphMetricsChange();
       } else {
         MOZ_ASSERT(false, "unexpected frame got nsChangeHint_UpdateTextPath");
       }
     }
     if (aChange & nsChangeHint_UpdateOpacityLayer) {
       // FIXME/bug 796697: we can get away with empty transactions for
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -500,20 +500,20 @@ DumpBidiLine(BidiLineData* aData, bool a
 
 /* Some helper methods for Resolve() */
 
 // Should this frame be split between text runs?
 static bool
 IsBidiSplittable(nsIFrame* aFrame)
 {
   // Bidi inline containers should be split, unless they're line frames.
-  FrameType frameType = aFrame->Type();
+  LayoutFrameType frameType = aFrame->Type();
   return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) &&
-          frameType != FrameType::Line) ||
-         frameType == FrameType::Text;
+          frameType != LayoutFrameType::Line) ||
+         frameType == LayoutFrameType::Text;
 }
 
 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
 static bool
 IsBidiLeaf(nsIFrame* aFrame)
 {
   nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
   return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer);
@@ -1120,18 +1120,18 @@ nsBidiPresUtils::TraverseFrames(nsBlockI
        * and add its index to the mContentToFrameIndex hashtable. This
        * will be used in RemoveBidiContinuation() to identify the last
        * frame in the array with a given content.
        */
       nsIContent* content = frame->GetContent();
       aBpd->AppendFrame(frame, aLineIter, content);
 
       // Append the content of the frame to the paragraph buffer
-      FrameType frameType = frame->Type();
-      if (FrameType::Text == frameType) {
+      LayoutFrameType frameType = frame->Type();
+      if (LayoutFrameType::Text == frameType) {
         if (content != aBpd->mPrevContent) {
           aBpd->mPrevContent = content;
           if (!frame->StyleText()->NewlineIsSignificant(
                 static_cast<nsTextFrame*>(frame))) {
             content->AppendTextTo(aBpd->mBuffer);
           } else {
             /*
              * For preformatted text we have to do bidi resolution on each line
@@ -1236,17 +1236,17 @@ nsBidiPresUtils::TraverseFrames(nsBlockI
                */
               if (frame && frame == nextSibling) {
                 nextSibling = frame->GetNextSibling();
               }
 
             } while (next);
           }
         }
-      } else if (FrameType::Br == frameType) {
+      } else if (LayoutFrameType::Br == frameType) {
         // break frame -- append line separator
         aBpd->AppendUnichar(kLineSeparator);
         ResolveParagraphWithinBlock(aBpd);
       } else { 
         // other frame type -- see the Unicode Bidi Algorithm:
         // "...inline objects (such as graphics) are treated as if they are ...
         // U+FFFC"
         // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
@@ -1559,24 +1559,24 @@ nsBidiPresUtils::RepositionRubyContentFr
 
 /* static */ nscoord
 nsBidiPresUtils::RepositionRubyFrame(
   nsIFrame* aFrame,
   const nsContinuationStates* aContinuationStates,
   const WritingMode aContainerWM,
   const LogicalMargin& aBorderPadding)
 {
-  FrameType frameType = aFrame->Type();
+  LayoutFrameType frameType = aFrame->Type();
   MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
 
   nscoord icoord = 0;
   WritingMode frameWM = aFrame->GetWritingMode();
   bool isLTR = frameWM.IsBidiLTR();
   nsSize frameSize = aFrame->GetSize();
-  if (frameType == FrameType::Ruby) {
+  if (frameType == LayoutFrameType::Ruby) {
     icoord += aBorderPadding.IStart(frameWM);
     // Reposition ruby segments in a ruby container
     for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame));
           !e.AtEnd(); e.Next()) {
       nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
       AutoRubyTextContainerArray textContainers(rbc);
 
       nscoord segmentISize = RepositionFrame(rbc, isLTR, icoord,
@@ -1585,17 +1585,17 @@ nsBidiPresUtils::RepositionRubyFrame(
       for (nsRubyTextContainerFrame* rtc : textContainers) {
         nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
                                         frameWM, false, frameSize);
         segmentISize = std::max(segmentISize, isize);
       }
       icoord += segmentISize;
     }
     icoord += aBorderPadding.IEnd(frameWM);
-  } else if (frameType == FrameType::RubyBaseContainer) {
+  } else if (frameType == LayoutFrameType::RubyBaseContainer) {
     // Reposition ruby columns in a ruby segment
     auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
     AutoRubyTextContainerArray textContainers(rbc);
 
     for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
       RubyColumn column;
       e.GetColumn(column);
 
@@ -1605,17 +1605,18 @@ nsBidiPresUtils::RepositionRubyFrame(
       for (nsRubyTextFrame* rt : column.mTextFrames) {
         nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
                                         frameWM, false, frameSize);
         columnISize = std::max(columnISize, isize);
       }
       icoord += columnISize;
     }
   } else {
-    if (frameType == FrameType::RubyBase || frameType == FrameType::RubyText) {
+    if (frameType == LayoutFrameType::RubyBase ||
+        frameType == LayoutFrameType::RubyText) {
       RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
     }
     // Note that, ruby text container is not present in all conditions
     // above. It is intended, because the children of rtc are reordered
     // with the children of ruby base container simultaneously. We only
     // need to return its isize here, as it should not be changed.
     icoord += aFrame->ISize(aContainerWM);
   }
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -338,18 +338,19 @@ IsAnonymousFlexOrGridItem(const nsIFrame
   return pseudoType == nsCSSAnonBoxes::anonymousFlexItem ||
          pseudoType == nsCSSAnonBoxes::anonymousGridItem;
 }
 
 // Returns true if aFrame is a flex/grid container.
 static inline bool
 IsFlexOrGridContainer(const nsIFrame* aFrame)
 {
-  const FrameType t = aFrame->Type();
-  return t == FrameType::FlexContainer || t == FrameType::GridContainer;
+  const LayoutFrameType t = aFrame->Type();
+  return t == LayoutFrameType::FlexContainer ||
+         t == LayoutFrameType::GridContainer;
 }
 
 // Returns true IFF the given nsIFrame is a nsFlexContainerFrame and
 // represents a -webkit-{inline-}box container.
 static inline bool
 IsFlexContainerForLegacyBox(const nsIFrame* aFrame)
 {
   return aFrame->IsFlexContainerFrame() &&
@@ -1966,37 +1967,37 @@ IsRubyPseudo(nsIFrame* aFrame)
 static bool
 IsTableOrRubyPseudo(nsIFrame* aFrame)
 {
   return IsTablePseudo(aFrame) || IsRubyPseudo(aFrame);
 }
 
 /* static */
 nsCSSFrameConstructor::ParentType
-nsCSSFrameConstructor::GetParentType(FrameType aFrameType)
-{
-  if (aFrameType == FrameType::Table) {
+nsCSSFrameConstructor::GetParentType(LayoutFrameType aFrameType)
+{
+  if (aFrameType == LayoutFrameType::Table) {
     return eTypeTable;
   }
-  if (aFrameType == FrameType::TableRowGroup) {
+  if (aFrameType == LayoutFrameType::TableRowGroup) {
     return eTypeRowGroup;
   }
-  if (aFrameType == FrameType::TableRow) {
+  if (aFrameType == LayoutFrameType::TableRow) {
     return eTypeRow;
   }
-  if (aFrameType == FrameType::TableColGroup) {
+  if (aFrameType == LayoutFrameType::TableColGroup) {
     return eTypeColGroup;
   }
-  if (aFrameType == FrameType::RubyBaseContainer) {
+  if (aFrameType == LayoutFrameType::RubyBaseContainer) {
     return eTypeRubyBaseContainer;
   }
-  if (aFrameType == FrameType::RubyTextContainer) {
+  if (aFrameType == LayoutFrameType::RubyTextContainer) {
     return eTypeRubyTextContainer;
   }
-  if (aFrameType == FrameType::Ruby) {
+  if (aFrameType == LayoutFrameType::Ruby) {
     return eTypeRuby;
   }
 
   return eTypeBlock;
 }
 
 static nsContainerFrame*
 AdjustCaptionParentFrame(nsContainerFrame* aParentFrame)
@@ -4230,34 +4231,34 @@ nsCSSFrameConstructor::GetAnonymousConte
   uint32_t count = aContent.Length();
   for (uint32_t i=0; i < count; i++) {
     // get our child's content and set its parent to our content
     nsIContent* content = aContent[i].mContent;
     NS_ASSERTION(content, "null anonymous content?");
 
     ConnectAnonymousTreeDescendants(content, aContent[i].mChildren);
 
-    FrameType parentFrameType = aParentFrame->Type();
-    if (parentFrameType == FrameType::SVGUse) {
+    LayoutFrameType parentFrameType = aParentFrame->Type();
+    if (parentFrameType == LayoutFrameType::SVGUse) {
       // least-surprise CSS binding until we do the SVG specified
       // cascading rules for <svg:use> - bug 265894
       content->SetFlags(NODE_IS_ANONYMOUS_ROOT);
     } else {
       content->SetIsNativeAnonymousRoot();
       // Don't mark descendants of the custom content container
       // as native anonymous.  When canvas custom content is initially
       // created and appended to the custom content container, in
       // nsIDocument::InsertAnonymousContent, it is not considered native
       // anonymous content.  But if we end up reframing the root element,
       // we will re-create the nsCanvasFrame, and we would end up in here,
       // marking it as NAC.  Existing uses of canvas custom content would
       // break if it becomes NAC (since each element starts inheriting
       // styles from its closest non-NAC ancestor, rather than from its
       // parent).
-      if (!(parentFrameType == FrameType::Canvas &&
+      if (!(parentFrameType == LayoutFrameType::Canvas &&
             content == static_cast<nsCanvasFrame*>(aParentFrame)
                          ->GetCustomContentContainer())) {
         SetNativeAnonymousBitOnDescendants(content);
       }
     }
 
     bool anonContentIsEditable = content->HasFlag(NODE_IS_EDITABLE);
 
@@ -6287,18 +6288,18 @@ nsCSSFrameConstructor::GetAbsoluteContai
       // If it's mathml, bail out -- no absolute positioning out from inside
       // mathml frames.  Note that we don't make this part of the loop
       // condition because of the stuff at the end of this method...
       return nullptr;
     }
 
     // Look for the ICB.
     if (aType == FIXED_POS) {
-      FrameType t = frame->Type();
-      if (t == FrameType::Viewport || t == FrameType::PageContent) {
+      LayoutFrameType t = frame->Type();
+      if (t == LayoutFrameType::Viewport || t == LayoutFrameType::PageContent) {
         return static_cast<nsContainerFrame*>(frame);
       }
     }
 
     // If the frame is positioned, we will probably return it as the containing
     // block (see the exceptions below).  Otherwise, we'll start looking at the
     // parent frame, unless we're dealing with a scrollframe.
     // Scrollframes are special since they're not positioned, but their
@@ -6307,41 +6308,41 @@ nsCSSFrameConstructor::GetAbsoluteContai
     // If we're looking for a fixed-pos containing block and the frame is
     // not transformed, skip it.
     if (!frame->IsAbsPosContainingBlock() ||
         (aType == FIXED_POS &&
          !frame->IsFixedPosContainingBlock())) {
       continue;
     }
     nsIFrame* absPosCBCandidate = frame;
-    FrameType type = absPosCBCandidate->Type();
-    if (type == FrameType::FieldSet) {
+    LayoutFrameType type = absPosCBCandidate->Type();
+    if (type == LayoutFrameType::FieldSet) {
       absPosCBCandidate = static_cast<nsFieldSetFrame*>(absPosCBCandidate)->GetInner();
       if (!absPosCBCandidate) {
         continue;
       }
       type = absPosCBCandidate->Type();
     }
-    if (type == FrameType::Scroll) {
+    if (type == LayoutFrameType::Scroll) {
       nsIScrollableFrame* scrollFrame = do_QueryFrame(absPosCBCandidate);
       absPosCBCandidate = scrollFrame->GetScrolledFrame();
       if (!absPosCBCandidate) {
         continue;
       }
       type = absPosCBCandidate->Type();
     }
     // Only first continuations can be containing blocks.
     absPosCBCandidate = absPosCBCandidate->FirstContinuation();
     // Is the frame really an absolute container?
     if (!absPosCBCandidate->IsAbsoluteContainer()) {
       continue;
     }
 
     // For tables, skip the inner frame and consider the table wrapper frame.
-    if (type == FrameType::Table) {
+    if (type == LayoutFrameType::Table) {
       continue;
     }
     // For table wrapper frames, we can just return absPosCBCandidate.
     MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(absPosCBCandidate),
                "abs.pos. containing block must be nsContainerFrame sub-class");
     return static_cast<nsContainerFrame*>(absPosCBCandidate);
   }
 
@@ -6622,26 +6623,26 @@ nsCSSFrameConstructor::AppendFramesToPar
 // inside fieldsets, (5) popups and other kids of the menu are siblings from a
 // content perspective, they are not considered siblings in the frame tree.
 bool
 nsCSSFrameConstructor::IsValidSibling(nsIFrame*              aSibling,
                                       nsIContent*            aContent,
                                       StyleDisplay&          aDisplay)
 {
   nsIFrame* parentFrame = aSibling->GetParent();
-  FrameType parentType = parentFrame->Type();
+  LayoutFrameType parentType = parentFrame->Type();
 
   StyleDisplay siblingDisplay = aSibling->GetDisplay();
   if (StyleDisplay::TableColumnGroup == siblingDisplay ||
       StyleDisplay::TableColumn      == siblingDisplay ||
       StyleDisplay::TableCaption     == siblingDisplay ||
       StyleDisplay::TableHeaderGroup == siblingDisplay ||
       StyleDisplay::TableRowGroup    == siblingDisplay ||
       StyleDisplay::TableFooterGroup == siblingDisplay ||
-      FrameType::Menu == parentType) {
+      LayoutFrameType::Menu == parentType) {
     // if we haven't already, construct a style context to find the display type of aContent
     if (UNSET_DISPLAY == aDisplay) {
       nsIFrame* styleParent;
       aSibling->GetParentStyleContext(&styleParent);
       if (!styleParent) {
         styleParent = aSibling->GetParent();
       }
       if (!styleParent) {
@@ -6656,17 +6657,17 @@ nsCSSFrameConstructor::IsValidSibling(ns
       }
       // XXXbz when this code is killed, the state argument to
       // ResolveStyleContext can be made non-optional.
       RefPtr<nsStyleContext> styleContext =
         ResolveStyleContext(styleParent, aContent, nullptr);
       const nsStyleDisplay* display = styleContext->StyleDisplay();
       aDisplay = display->mDisplay;
     }
-    if (FrameType::Menu == parentType) {
+    if (LayoutFrameType::Menu == parentType) {
       return
         (StyleDisplay::MozPopup == aDisplay) ==
         (StyleDisplay::MozPopup == siblingDisplay);
     }
     // To have decent performance we want to return false in cases in which
     // reordering the two siblings has no effect on display.  To ensure
     // correctness, we MUST return false in cases where the two siblings have
     // the same desired parent type and live on different display lists.
@@ -6697,21 +6698,21 @@ nsCSSFrameConstructor::IsValidSibling(ns
     // below.
   }
 
   if (IsFrameForFieldSet(parentFrame)) {
     // Legends can be sibling of legends but not of other content in the fieldset
     if (nsContainerFrame* cif = aSibling->GetContentInsertionFrame()) {
       aSibling = cif;
     }
-    FrameType sibType = aSibling->Type();
+    LayoutFrameType sibType = aSibling->Type();
     bool legendContent = aContent->IsHTMLElement(nsGkAtoms::legend);
 
-    if ((legendContent && (FrameType::Legend != sibType)) ||
-        (!legendContent && (FrameType::Legend == sibType)))
+    if ((legendContent && (LayoutFrameType::Legend != sibType)) ||
+        (!legendContent && (LayoutFrameType::Legend == sibType)))
       return false;
   }
 
   return true;
 }
 
 nsIFrame*
 nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
@@ -7642,17 +7643,17 @@ nsCSSFrameConstructor::ContentAppended(n
                                containingBlock->StyleContext());
   }
 
   if (haveFirstLetterStyle) {
     // Before we get going, remove the current letter frames
     RemoveLetterFrames(state.mPresShell, containingBlock);
   }
 
-  FrameType frameType = parentFrame->Type();
+  LayoutFrameType frameType = parentFrame->Type();
 
   FlattenedChildIterator iter(aContainer);
   bool haveNoXBLChildren = (!iter.XBLInvolved() || !iter.GetNextChild());
   FrameConstructionItemList items;
   if (aFirstNewContent->GetPreviousSibling() &&
       GetParentType(frameType) == eTypeBlock &&
       haveNoXBLChildren) {
     // If there's a text node in the normal content list just before the new
@@ -7717,17 +7718,17 @@ nsCSSFrameConstructor::ContentAppended(n
     // GetChildAt calls instead and do this during construction of our
     // FrameConstructionItemList?
     InvalidateCanvasIfNeeded(mPresShell, child);
   }
 
   // If the container is a table and a caption was appended, it needs to be put
   // in the table wrapper frame's additional child list.
   nsFrameItems captionItems;
-  if (FrameType::Table == frameType) {
+  if (LayoutFrameType::Table == frameType) {
     // Pull out the captions.  Note that we don't want to do that as we go,
     // because processing a single caption can add a whole bunch of things to
     // the frame items due to pseudoframe processing.  So we'd have to pull
     // captions from a list anyway; might as well do that here.
     // XXXbz this is no longer true; we could pull captions directly out of the
     // FrameConstructionItemList now.
     PullOutCaptionFrames(frameItems, captionItems);
   }
@@ -7738,17 +7739,17 @@ nsCSSFrameConstructor::ContentAppended(n
     AppendFirstLineFrames(state, containingBlock->GetContent(),
                           containingBlock, frameItems);
   }
 
   // Notify the parent frame passing it the list of new frames
   // Append the flowed frames to the principal child list; captions
   // need special treatment
   if (captionItems.NotEmpty()) { // append the caption to the table wrapper
-    NS_ASSERTION(FrameType::Table == frameType, "how did that happen?");
+    NS_ASSERTION(LayoutFrameType::Table == frameType, "how did that happen?");
     nsContainerFrame* outerTable = parentFrame->GetParent();
     AppendFrames(outerTable, nsIFrame::kCaptionList, captionItems);
   }
 
   if (frameItems.NotEmpty()) { // append the in-flow kids
     AppendFramesToParent(state, parentFrame, frameItems, prevSibling);
   }
 
@@ -8065,27 +8066,27 @@ nsCSSFrameConstructor::ContentRangeInser
     IssueSingleInsertNofications(aContainer, aStartChild, aEndChild,
                                  aAllowLazyConstruction, aForReconstruction);
     LAYOUT_PHASE_TEMP_REENTER();
     return;
   }
 
   nsIContent* container = insertion.mParentFrame->GetContent();
 
-  FrameType frameType = insertion.mParentFrame->Type();
+  LayoutFrameType frameType = insertion.mParentFrame->Type();
   LAYOUT_PHASE_TEMP_EXIT();
   if (MaybeRecreateForFrameset(insertion.mParentFrame, aStartChild, aEndChild)) {
     LAYOUT_PHASE_TEMP_REENTER();
     return;
   }
   LAYOUT_PHASE_TEMP_REENTER();
 
   // We should only get here with fieldsets when doing a single insert, because
   // fieldsets have multiple insertion points.
-  NS_ASSERTION(isSingleInsert || frameType != FrameType::FieldSet,
+  NS_ASSERTION(isSingleInsert || frameType != LayoutFrameType::FieldSet,
                "Unexpected parent");
   if (IsFrameForFieldSet(insertion.mParentFrame) &&
       aStartChild->NodeInfo()->NameAtom() == nsGkAtoms::legend) {
     // Just reframe the parent, since figuring out whether this
     // should be the new legend and then handling it is too complex.
     // We could do a little better here --- check if the fieldset already
     // has a legend which occurs earlier in its child list than this node,
     // and if so, proceed. But we'd have to extend nsFieldSetFrame
@@ -8094,18 +8095,18 @@ nsCSSFrameConstructor::ContentRangeInser
     RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
                              REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
     return;
   }
 
   // We should only get here with details when doing a single insertion because
   // we treat details frame as if it has multiple insertion points.
-  MOZ_ASSERT(isSingleInsert || frameType != FrameType::Details);
-  if (frameType == FrameType::Details) {
+  MOZ_ASSERT(isSingleInsert || frameType != LayoutFrameType::Details);
+  if (frameType == LayoutFrameType::Details) {
     // When inserting an element into <details>, just reframe the details frame
     // and let it figure out where the element should be laid out. It might seem
     // expensive to recreate the entire details frame, but it's the simplest way
     // to handle the insertion.
     LAYOUT_PHASE_TEMP_EXIT();
     RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
                              REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
@@ -8286,17 +8287,18 @@ nsCSSFrameConstructor::ContentRangeInser
 
   if (frameItems.NotEmpty()) {
     for (nsIContent* child = aStartChild;
          child != aEndChild;
          child = child->GetNextSibling()){
       InvalidateCanvasIfNeeded(mPresShell, child);
     }
 
-    if (FrameType::Table == frameType || FrameType::TableWrapper == frameType) {
+    if (LayoutFrameType::Table == frameType ||
+        LayoutFrameType::TableWrapper == frameType) {
       PullOutCaptionFrames(frameItems, captionItems);
     }
   }
 
   // If the parent of our current prevSibling is different from the frame we'll
   // actually use as the parent, then the calculated insertion point is now
   // invalid and as it is unknown where to insert correctly we append instead
   // (bug 341858).
@@ -8355,18 +8357,18 @@ nsCSSFrameConstructor::ContentRangeInser
       InsertFirstLineFrames(state, container, containingBlock, &insertion.mParentFrame,
                             prevSibling, frameItems);
     }
   }
 
   // We might have captions; put them into the caption list of the
   // table wrapper frame.
   if (captionItems.NotEmpty()) {
-    NS_ASSERTION(FrameType::Table == frameType ||
-                 FrameType::TableWrapper == frameType,
+    NS_ASSERTION(LayoutFrameType::Table == frameType ||
+                 LayoutFrameType::TableWrapper == frameType,
                  "parent for caption is not table?");
     // We need to determine where to put the caption items; start with the
     // the parent frame that has already been determined and get the insertion
     // prevsibling of the first caption item.
     bool captionIsAppend;
     nsIFrame* captionPrevSibling = nullptr;
 
     // aIsRangeInsertSafe is ignored on purpose because it is irrelevant here.
@@ -8620,33 +8622,35 @@ nsCSSFrameConstructor::ContentRemoved(ns
         *aDestroyedFramesFor = container;
       }
       return;
     }
     LAYOUT_PHASE_TEMP_REENTER();
 
     // Get the childFrame's parent frame
     nsIFrame* parentFrame = childFrame->GetParent();
-    FrameType parentType = parentFrame->Type();
-
-    if (parentType == FrameType::FrameSet && IsSpecialFramesetChild(aChild)) {
+    LayoutFrameType parentType = parentFrame->Type();
+
+    if (parentType == LayoutFrameType::FrameSet &&
+        IsSpecialFramesetChild(aChild)) {
       // Just reframe the parent, since framesets are weird like that.
       *aDidReconstruct = true;
       LAYOUT_PHASE_TEMP_EXIT();
       RecreateFramesForContent(parentFrame->GetContent(), false,
                                aFlags, aDestroyedFramesFor);
       LAYOUT_PHASE_TEMP_REENTER();
       return;
     }
 
     // If we're a child of MathML, then we should reframe the MathML content.
     // If we're non-MathML, then we would be wrapped in a block so we need to
     // check our grandparent in that case.
-    nsIFrame* possibleMathMLAncestor =
-      parentType == FrameType::Block ? parentFrame->GetParent() : parentFrame;
+    nsIFrame* possibleMathMLAncestor = parentType == LayoutFrameType::Block
+                                         ? parentFrame->GetParent()
+                                         : parentFrame;
     if (possibleMathMLAncestor->IsFrameOfType(nsIFrame::eMathML)) {
       *aDidReconstruct = true;
       LAYOUT_PHASE_TEMP_EXIT();
       RecreateFramesForContent(possibleMathMLAncestor->GetContent(),
                                false, aFlags, aDestroyedFramesFor);
       LAYOUT_PHASE_TEMP_REENTER();
       return;
     }
@@ -9132,63 +9136,63 @@ nsCSSFrameConstructor::CreateContinuingF
 {
   nsIPresShell*              shell = aPresContext->PresShell();
   nsStyleContext*            styleContext = aFrame->StyleContext();
   nsIFrame*                  newFrame = nullptr;
   nsIFrame*                  nextContinuation = aFrame->GetNextContinuation();
   nsIFrame*                  nextInFlow = aFrame->GetNextInFlow();
 
   // Use the frame type to determine what type of frame to create
-  FrameType frameType = aFrame->Type();
+  LayoutFrameType frameType = aFrame->Type();
   nsIContent* content = aFrame->GetContent();
 
   NS_ASSERTION(aFrame->GetSplittableType() != NS_FRAME_NOT_SPLITTABLE,
                "why CreateContinuingFrame for a non-splittable frame?");
 
-  if (FrameType::Text == frameType) {
+  if (LayoutFrameType::Text == frameType) {
     newFrame = NS_NewContinuingTextFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Inline == frameType) {
+  } else if (LayoutFrameType::Inline == frameType) {
     newFrame = NS_NewInlineFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Block == frameType) {
+  } else if (LayoutFrameType::Block == frameType) {
     MOZ_ASSERT(!aFrame->IsTableCaption(),
                "no support for fragmenting table captions yet");
     newFrame = NS_NewBlockFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
 #ifdef MOZ_XUL
-  } else if (FrameType::XULLabel == frameType) {
+  } else if (LayoutFrameType::XULLabel == frameType) {
     newFrame = NS_NewXULLabelFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
 #endif
-  } else if (FrameType::ColumnSet == frameType) {
+  } else if (LayoutFrameType::ColumnSet == frameType) {
     MOZ_ASSERT(!aFrame->IsTableCaption(),
                "no support for fragmenting table captions yet");
     newFrame = NS_NewColumnSetFrame(shell, styleContext, nsFrameState(0));
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Page == frameType) {
+  } else if (LayoutFrameType::Page == frameType) {
     nsContainerFrame* canvasFrame;
     newFrame = ConstructPageFrame(shell, aParentFrame, aFrame, canvasFrame);
-  } else if (FrameType::TableWrapper == frameType) {
+  } else if (LayoutFrameType::TableWrapper == frameType) {
     newFrame =
       CreateContinuingOuterTableFrame(shell, aPresContext, aFrame, aParentFrame,
                                       content, styleContext);
 
-  } else if (FrameType::Table == frameType) {
+  } else if (LayoutFrameType::Table == frameType) {
     newFrame =
       CreateContinuingTableFrame(shell, aFrame, aParentFrame,
                                  content, styleContext);
 
-  } else if (FrameType::TableRowGroup == frameType) {
+  } else if (LayoutFrameType::TableRowGroup == frameType) {
     newFrame = NS_NewTableRowGroupFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
     if (newFrame->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) {
       nsTableFrame::RegisterPositionedTablePart(newFrame);
     }
-  } else if (FrameType::TableRow == frameType) {
+  } else if (LayoutFrameType::TableRow == frameType) {
     nsTableRowFrame* rowFrame = NS_NewTableRowFrame(shell, styleContext);
 
     rowFrame->Init(content, aParentFrame, aFrame);
     if (rowFrame->GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) {
       nsTableFrame::RegisterPositionedTablePart(rowFrame);
     }
 
     // Create a continuing frame for each table cell frame
@@ -9224,38 +9228,38 @@ nsCSSFrameConstructor::CreateContinuingF
     // Create a continuing area frame
     nsIFrame* blockFrame = aFrame->PrincipalChildList().FirstChild();
     nsIFrame* continuingBlockFrame =
       CreateContinuingFrame(aPresContext, blockFrame,
                             static_cast<nsContainerFrame*>(cellFrame));
 
     SetInitialSingleChild(cellFrame, continuingBlockFrame);
     newFrame = cellFrame;
-  } else if (FrameType::Line == frameType) {
+  } else if (LayoutFrameType::Line == frameType) {
     newFrame = NS_NewFirstLineFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Letter == frameType) {
+  } else if (LayoutFrameType::Letter == frameType) {
     newFrame = NS_NewFirstLetterFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Image == frameType) {
+  } else if (LayoutFrameType::Image == frameType) {
     newFrame = NS_NewImageFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::ImageControl == frameType) {
+  } else if (LayoutFrameType::ImageControl == frameType) {
     newFrame = NS_NewImageControlFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Placeholder == frameType) {
+  } else if (LayoutFrameType::Placeholder == frameType) {
     // create a continuing out of flow frame
     nsIFrame* oofFrame = nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
     nsIFrame* oofContFrame =
       CreateContinuingFrame(aPresContext, oofFrame, aParentFrame);
     newFrame =
       CreatePlaceholderFrameFor(shell, content, oofContFrame,
                                 aParentFrame, aFrame,
                                 aFrame->GetStateBits() & PLACEHOLDER_TYPE_MASK);
-  } else if (FrameType::FieldSet == frameType) {
+  } else if (LayoutFrameType::FieldSet == frameType) {
     nsContainerFrame* fieldset = NS_NewFieldSetFrame(shell, styleContext);
 
     fieldset->Init(content, aParentFrame, aFrame);
 
     // Create a continuing area frame
     // XXXbz we really shouldn't have to do this by hand!
     nsContainerFrame* blockFrame = GetFieldSetBlockFrame(aFrame);
     if (blockFrame) {
@@ -9263,35 +9267,35 @@ nsCSSFrameConstructor::CreateContinuingF
         CreateContinuingFrame(aPresContext, blockFrame, fieldset);
       // Set the fieldset's initial child list
       SetInitialSingleChild(fieldset, continuingBlockFrame);
     } else {
       MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER,
                  "FieldSet block may only be null for overflow containers");
     }
     newFrame = fieldset;
-  } else if (FrameType::Legend == frameType) {
+  } else if (LayoutFrameType::Legend == frameType) {
     newFrame = NS_NewLegendFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::FlexContainer == frameType) {
+  } else if (LayoutFrameType::FlexContainer == frameType) {
     newFrame = NS_NewFlexContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::GridContainer == frameType) {
+  } else if (LayoutFrameType::GridContainer == frameType) {
     newFrame = NS_NewGridContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Ruby == frameType) {
+  } else if (LayoutFrameType::Ruby == frameType) {
     newFrame = NS_NewRubyFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::RubyBaseContainer == frameType) {
+  } else if (LayoutFrameType::RubyBaseContainer == frameType) {
     newFrame = NS_NewRubyBaseContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::RubyTextContainer == frameType) {
+  } else if (LayoutFrameType::RubyTextContainer == frameType) {
     newFrame = NS_NewRubyTextContainerFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
-  } else if (FrameType::Details == frameType) {
+  } else if (LayoutFrameType::Details == frameType) {
     newFrame = NS_NewDetailsFrame(shell, styleContext);
     newFrame->Init(content, aParentFrame, aFrame);
   } else {
     MOZ_CRASH("unexpected frame type");
   }
 
   // Init() set newFrame to be a fluid continuation of aFrame.
   // If we want a non-fluid continuation, we need to call SetPrevContinuation()
@@ -9697,18 +9701,18 @@ nsCSSFrameConstructor::MaybeRecreateCont
       // aFrame's parent is a pseudo, that'll be the right content node.
       RecreateFramesForContent(parent->GetContent(), true, aFlags,
                                aDestroyedFramesFor);
       return true;
     }
   }
 
   // Check ruby containers
-  FrameType parentType = parent->Type();
-  if (parentType == FrameType::Ruby ||
+  LayoutFrameType parentType = parent->Type();
+  if (parentType == LayoutFrameType::Ruby ||
       RubyUtils::IsRubyContainerBox(parentType)) {
     // In ruby containers, pseudo frames may be created from
     // whitespaces or even nothing. There are two cases we actually
     // need to handle here, but hard to check exactly:
     // 1. Status of spaces beside the frame may vary, and related
     //    frames may be constructed or destroyed accordingly.
     // 2. The type of the first child of a ruby frame determines
     //    whether a pseudo ruby base container should exist.
@@ -10124,19 +10128,19 @@ void
 nsCSSFrameConstructor::CreateNeededAnonFlexOrGridItems(
   nsFrameConstructorState& aState,
   FrameConstructionItemList& aItems,
   nsIFrame* aParentFrame)
 {
   if (aItems.IsEmpty()) {
     return;
   }
-  const FrameType parentType = aParentFrame->Type();
-  if (parentType != FrameType::FlexContainer &&
-      parentType != FrameType::GridContainer) {
+  const LayoutFrameType parentType = aParentFrame->Type();
+  if (parentType != LayoutFrameType::FlexContainer &&
+      parentType != LayoutFrameType::GridContainer) {
     return;
   }
 
   const bool isWebkitBox = IsFlexContainerForLegacyBox(aParentFrame);
   FCItemIterator iter(aItems);
   do {
     // Advance iter past children that don't want to be wrapped
     if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState, isWebkitBox)) {
@@ -10800,46 +10804,46 @@ void nsCSSFrameConstructor::CreateNeeded
  * purely for sanity-checking the children of these container types.
  * NOTE: See also NeedsAnonFlexOrGridItem(), for the non-debug version of this
  * logic (which operates a bit earlier, on FCData instead of frames).
  */
 static bool
 FrameWantsToBeInAnonymousItem(const nsIFrame* aContainerFrame,
                               const nsIFrame* aFrame)
 {
-  FrameType containerType = aContainerFrame->Type();
-  MOZ_ASSERT(containerType == FrameType::FlexContainer ||
-             containerType == FrameType::GridContainer);
+  LayoutFrameType containerType = aContainerFrame->Type();
+  MOZ_ASSERT(containerType == LayoutFrameType::FlexContainer ||
+             containerType == LayoutFrameType::GridContainer);
 
   // Any line-participant frames (e.g. text) definitely want to be wrapped in
   // an anonymous flex/grid item.
   if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
     return true;
   }
 
   // If the container is a -webkit-box/-webkit-inline-box, then placeholders
   // also need to be wrapped, for compatibility.
-  if (containerType == FrameType::FlexContainer &&
+  if (containerType == LayoutFrameType::FlexContainer &&
       aContainerFrame->HasAnyStateBits(NS_STATE_FLEX_IS_LEGACY_WEBKIT_BOX) &&
       aFrame->IsPlaceholderFrame()) {
     return true;
   }
 
   return false;
 }
 #endif
 
 static void
 VerifyGridFlexContainerChildren(nsIFrame* aParentFrame,
                                 const nsFrameList& aChildren)
 {
 #ifdef DEBUG
   auto parentType = aParentFrame->Type();
-  if (parentType != FrameType::FlexContainer &&
-      parentType != FrameType::GridContainer) {
+  if (parentType != LayoutFrameType::FlexContainer &&
+      parentType != LayoutFrameType::GridContainer) {
     return;
   }
 
   bool prevChildWasAnonItem = false;
   for (const nsIFrame* child : aChildren) {
     MOZ_ASSERT(!FrameWantsToBeInAnonymousItem(aParentFrame, child),
                "frame wants to be inside an anonymous item, but it isn't");
     if (IsAnonymousFlexOrGridItem(child)) {
@@ -11753,33 +11757,33 @@ nsCSSFrameConstructor::WrapFramesInFirst
   bool*                    aStopLooking)
 {
   nsIFrame* prevFrame = nullptr;
   nsIFrame* frame = aParentFrameList;
 
   while (frame) {
     nsIFrame* nextFrame = frame->GetNextSibling();
 
-    FrameType frameType = frame->Type();
-    if (FrameType::Text == frameType) {
+    LayoutFrameType frameType = frame->Type();
+    if (LayoutFrameType::Text == frameType) {
       // Wrap up first-letter content in a letter frame
       nsIContent* textContent = frame->GetContent();
       if (IsFirstLetterContent(textContent)) {
         // Create letter frame to wrap up the text
         CreateLetterFrame(aBlockFrame, aBlockContinuation, textContent,
                           aParentFrame, aLetterFrames);
 
         // Provide adjustment information for parent
         *aModifiedParent = aParentFrame;
         *aTextFrame = frame;
         *aPrevFrame = prevFrame;
         *aStopLooking = true;
         return;
       }
-    } else if (IsInlineFrame(frame) && frameType != FrameType::Br) {
+    } else if (IsInlineFrame(frame) && frameType != LayoutFrameType::Br) {
       nsIFrame* kids = frame->PrincipalChildList().FirstChild();
       WrapFramesInFirstLetterFrame(aBlockFrame, aBlockContinuation,
                                    static_cast<nsContainerFrame*>(frame),
                                    kids, aModifiedParent, aTextFrame,
                                    aPrevFrame, aLetterFrames, aStopLooking);
       if (*aStopLooking) {
         return;
       }
@@ -12527,19 +12531,19 @@ nsCSSFrameConstructor::WipeContainingBlo
     return true;
   }
 
   nsIFrame* nextSibling = ::GetInsertNextSibling(aFrame, aPrevSibling);
 
   // Situation #2 is a flex or grid container frame into which we're inserting
   // new inline non-replaced children, adjacent to an existing anonymous
   // flex or grid item.
-  FrameType frameType = aFrame->Type();
-  if (frameType == FrameType::FlexContainer ||
-      frameType == FrameType::GridContainer) {
+  LayoutFrameType frameType = aFrame->Type();
+  if (frameType == LayoutFrameType::FlexContainer ||
+      frameType == LayoutFrameType::GridContainer) {
     FCItemIterator iter(aItems);
 
     // Check if we're adding to-be-wrapped content right *after* an existing
     // anonymous flex or grid item (which would need to absorb this content).
     const bool isWebkitBox = IsFlexContainerForLegacyBox(aFrame);
     if (aPrevSibling && IsAnonymousFlexOrGridItem(aPrevSibling) &&
         iter.item().NeedsAnonFlexOrGridItem(aState, isWebkitBox)) {
       RecreateFramesForContent(aFrame->GetContent(), true,
@@ -12598,17 +12602,17 @@ nsCSSFrameConstructor::WipeContainingBlo
   // spaces. It containes these two special cases apart from tables:
   // 1) There are effectively three types of white spaces in ruby frames
   //    we handle differently: leading/tailing/inter-level space,
   //    inter-base/inter-annotation space, and inter-segment space.
   //    These three types of spaces can be converted to each other when
   //    their sibling changes.
   // 2) The first effective child of a ruby frame must always be a ruby
   //    base container. It should be created or destroyed accordingly.
-  if (IsRubyPseudo(aFrame) || frameType == FrameType::Ruby ||
+  if (IsRubyPseudo(aFrame) || frameType == LayoutFrameType::Ruby ||
       RubyUtils::IsRubyContainerBox(frameType)) {
     // We want to optimize it better, and avoid reframing as much as
     // possible. But given the cases above, and the fact that a ruby
     // usually won't be very large, it should be fine to reframe it.
     RecreateFramesForContent(aFrame->GetContent(), true,
                              REMOVE_FOR_RECONSTRUCTION, nullptr);
     return true;
   }
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -609,18 +609,18 @@ private:
 #define FCDATA_DESIRED_PARENT_TYPE_TO_BITS(_type)     \
   (((uint32_t)(_type)) << FCDATA_PARENT_TYPE_OFFSET)
 
   /* Get the parent type that aParentFrame has. */
   static ParentType GetParentType(nsIFrame* aParentFrame) {
     return GetParentType(aParentFrame->Type());
   }
 
-  /* Get the parent type for the given FrameType */
-  static ParentType GetParentType(mozilla::FrameType aFrameType);
+  /* Get the parent type for the given LayoutFrameType */
+  static ParentType GetParentType(mozilla::LayoutFrameType aFrameType);
 
   static bool IsRubyParentType(ParentType aParentType) {
     return (aParentType == eTypeRuby ||
             aParentType == eTypeRubyBase ||
             aParentType == eTypeRubyBaseContainer ||
             aParentType == eTypeRubyText ||
             aParentType == eTypeRubyTextContainer);
   }
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -370,17 +370,17 @@ nsCaret::GetGeometryForFrame(nsIFrame* a
   }
 
   rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) :
                                      nsSize(caretMetrics.mCaretWidth, height));
 
   // Clamp the inline-position to be within our scroll frame. If we don't, then
   // it clips us, and we don't appear at all. See bug 335560.
   nsIFrame* scrollFrame =
-    nsLayoutUtils::GetClosestFrameOfType(aFrame, FrameType::Scroll);
+    nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
   if (scrollFrame) {
     // First, use the scrollFrame to get at the scrollable view that we're in.
     nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
     nsIFrame *scrolled = sf->GetScrolledFrame();
     nsRect caretInScroll = rect + aFrame->GetOffsetTo(scrolled);
 
     // Now see if the caret extends beyond the view's bounds. If it does,
     // then snap it back, put it as close to the edge as it can.
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2575,17 +2575,17 @@ nsDocumentViewer::FindContainerView()
     return nullptr;
   }
 
   // subdocFrame might not be a subdocument frame; the frame
   // constructor can treat a <frame> as an inline in some XBL
   // cases. Treat that as display:none, the document is not
   // displayed.
   if (!subdocFrame->IsSubDocumentFrame()) {
-    NS_WARNING_ASSERTION(subdocFrame->Type() == FrameType::None,
+    NS_WARNING_ASSERTION(subdocFrame->Type() == LayoutFrameType::None,
                          "Subdocument container has non-subdocument frame");
     return nullptr;
   }
 
   NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views");
   return static_cast<nsSubDocumentFrame*>(subdocFrame)->EnsureInnerView();
 }
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1527,35 +1527,35 @@ nsLayoutUtils::GetChildListNameFor(nsIFr
       id = nsIFrame::kPopupList;
 #endif // MOZ_XUL
     } else {
       NS_ASSERTION(aChildFrame->IsFloating(), "not a floated frame");
       id = nsIFrame::kFloatList;
     }
 
   } else {
-    FrameType childType = aChildFrame->Type();
-    if (FrameType::MenuPopup == childType) {
+    LayoutFrameType childType = aChildFrame->Type();
+    if (LayoutFrameType::MenuPopup == childType) {
       nsIFrame* parent = aChildFrame->GetParent();
       MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame");
       if (parent) {
         if (parent->IsPopupSetFrame()) {
           id = nsIFrame::kPopupList;
         } else {
           nsIFrame* firstPopup = parent->GetChildList(nsIFrame::kPopupList).FirstChild();
           MOZ_ASSERT(!firstPopup || !firstPopup->GetNextSibling(),
                      "We assume popupList only has one child, but it has more.");
           id = firstPopup == aChildFrame
                  ? nsIFrame::kPopupList
                  : nsIFrame::kPrincipalList;
         }
       } else {
         id = nsIFrame::kPrincipalList;
       }
-    } else if (FrameType::TableColGroup == childType) {
+    } else if (LayoutFrameType::TableColGroup == childType) {
       id = nsIFrame::kColGroupList;
     } else if (aChildFrame->IsTableCaption()) {
       id = nsIFrame::kCaptionList;
     } else {
       id = nsIFrame::kPrincipalList;
     }
   }
 
@@ -1617,34 +1617,34 @@ nsLayoutUtils::GetAfterFrame(const nsICo
 {
   Element* pseudo = GetAfterPseudo(aContent);
   return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
 }
 
 // static
 nsIFrame*
 nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
-                                     FrameType aFrameType,
+                                     LayoutFrameType aFrameType,
                                      nsIFrame* aStopAt)
 {
   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
     if (frame->Type() == aFrameType) {
       return frame;
     }
     if (frame == aStopAt) {
       break;
     }
   }
   return nullptr;
 }
 
 /* static */ nsIFrame*
 nsLayoutUtils::GetPageFrame(nsIFrame* aFrame)
 {
-  return GetClosestFrameOfType(aFrame, FrameType::Page);
+  return GetClosestFrameOfType(aFrame, LayoutFrameType::Page);
 }
 
 // static
 nsIFrame*
 nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame)
 {
   if (aFrame->IsTableWrapperFrame()) {
     nsIFrame* inner = aFrame->PrincipalChildList().FirstChild();
@@ -2892,17 +2892,17 @@ nsLayoutUtils::ContainsPoint(const nsRec
   rect.Inflate(aInflateSize);
   return rect.Contains(aPoint);
 }
 
 nsRect
 nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame, const nsRect& aRect)
 {
   nsIFrame* closestScrollFrame =
-    nsLayoutUtils::GetClosestFrameOfType(aFrame, FrameType::Scroll);
+    nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
 
   nsRect resultRect = aRect;
 
   while (closestScrollFrame) {
     nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
 
     nsRect scrollPortRect = sf->GetScrollPortRect();
     nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect);
@@ -2910,19 +2910,18 @@ nsLayoutUtils::ClampRectToScrollFrames(n
     resultRect = resultRect.Intersect(scrollPortRect);
 
     // Check whether aRect is visible in the scroll frame or not.
     if (resultRect.IsEmpty()) {
       break;
     }
 
     // Get next ancestor scroll frame.
-    closestScrollFrame =
-      nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(),
-                                           FrameType::Scroll);
+    closestScrollFrame = nsLayoutUtils::GetClosestFrameOfType(
+      closestScrollFrame->GetParent(), LayoutFrameType::Scroll);
   }
 
   return resultRect;
 }
 
 bool
 nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame,
                                          Matrix4x4* aTransform)
@@ -3008,19 +3007,18 @@ TransformGfxRectToAncestor(nsIFrame *aFr
 
 static SVGTextFrame*
 GetContainingSVGTextFrame(nsIFrame* aFrame)
 {
   if (!nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
     return nullptr;
   }
 
-  return static_cast<SVGTextFrame*>
-    (nsLayoutUtils::GetClosestFrameOfType(aFrame->GetParent(),
-                                          FrameType::SVGText));
+  return static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
+    aFrame->GetParent(), LayoutFrameType::SVGText));
 }
 
 nsPoint
 nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame,
                                              const nsPoint& aPoint,
                                              nsIFrame* aAncestor)
 {
     SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
@@ -3591,28 +3589,28 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
         }
       }
 
       nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
 
       aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list);
     }
 
-    FrameType frameType = aFrame->Type();
+    LayoutFrameType frameType = aFrame->Type();
 
     // For the viewport frame in print preview/page layout we want to paint
     // the grey background behind the page, not the canvas color.
-    if (frameType == FrameType::Viewport &&
+    if (frameType == LayoutFrameType::Viewport &&
         nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) {
       nsRect bounds = nsRect(builder.ToReferenceFrame(aFrame),
                              aFrame->GetSize());
       nsDisplayListBuilder::AutoBuildingDisplayList
         buildingDisplayList(&builder, aFrame, bounds, false);
       presShell->AddPrintPreviewBackgroundItem(builder, list, aFrame, bounds);
-    } else if (frameType != FrameType::Page) {
+    } else if (frameType != LayoutFrameType::Page) {
       // For printing, this function is first called on an nsPageFrame, which
       // creates a display list with a PageContent item. The PageContent item's
       // paint function calls this function on the nsPageFrame's child which is
       // an nsPageContentFrame. We only want to add the canvas background color
       // item once, for the nsPageContentFrame.
 
       // Add the canvas background color to the bottom of the list. This
       // happens after we've built the list so that AddCanvasBackgroundColorItem
@@ -4652,19 +4650,20 @@ GetPercentBSize(const nsStyleCoord& aSty
   const nsStylePosition *pos = f->StylePosition();
   const nsStyleCoord& bSizeCoord = pos->BSize(wm);
   nscoord h;
   if (!GetAbsoluteCoord(bSizeCoord, h) &&
       !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
     NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto ||
                  bSizeCoord.HasPercent(),
                  "unknown block-size unit");
-    FrameType fType = f->Type();
-    if (fType != FrameType::Viewport && fType != FrameType::Canvas &&
-        fType != FrameType::PageContent) {
+    LayoutFrameType fType = f->Type();
+    if (fType != LayoutFrameType::Viewport &&
+        fType != LayoutFrameType::Canvas &&
+        fType != LayoutFrameType::PageContent) {
       // There's no basis for the percentage height, so it acts like auto.
       // Should we consider a max-height < min-height pair a basis for
       // percentage heights?  The spec is somewhat unclear, and not doing
       // so is simpler and avoids troubling discontinuities in behavior,
       // so I'll choose not to. -LDB
       return false;
     }
 
@@ -4830,31 +4829,31 @@ static int32_t gNoiseIndent = 0;
 inline static bool
 FormControlShrinksForPercentISize(nsIFrame* aFrame)
 {
   if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
     // Quick test to reject most frames.
     return false;
   }
 
-  FrameType fType = aFrame->Type();
-  if (fType == FrameType::Meter || fType == FrameType::Progress) {
+  LayoutFrameType fType = aFrame->Type();
+  if (fType == LayoutFrameType::Meter || fType == LayoutFrameType::Progress) {
     // progress and meter do have this shrinking behavior
     // FIXME: Maybe these should be nsIFormControlFrame?
     return true;
   }
 
   if (!static_cast<nsIFormControlFrame*>(do_QueryFrame(aFrame))) {
     // Not a form control.  This includes fieldsets, which do not
     // shrink.
     return false;
   }
 
-  if (fType == FrameType::GfxButtonControl ||
-      fType == FrameType::HTMLButtonControl) {
+  if (fType == LayoutFrameType::GfxButtonControl ||
+      fType == LayoutFrameType::HTMLButtonControl) {
     // Buttons don't have this shrinking behavior.  (Note that color
     // inputs do, even though they inherit from button, so we can't use
     // do_QueryFrame here.)
     return false;
   }
 
   return true;
 }
@@ -5962,25 +5961,25 @@ nsLayoutUtils::GetFirstLineBaseline(Writ
 nsLayoutUtils::GetFirstLinePosition(WritingMode aWM,
                                     const nsIFrame* aFrame,
                                     LinePosition* aResult)
 {
   const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast<nsIFrame*>(aFrame));
   if (!block) {
     // For the first-line baseline we also have to check for a table, and if
     // so, use the baseline of its first row.
-    FrameType fType = aFrame->Type();
-    if (fType == FrameType::TableWrapper  ||
-        fType == FrameType::FlexContainer ||
-        fType == FrameType::GridContainer) {
-      if ((fType == FrameType::GridContainer &&
+    LayoutFrameType fType = aFrame->Type();
+    if (fType == LayoutFrameType::TableWrapper ||
+        fType == LayoutFrameType::FlexContainer ||
+        fType == LayoutFrameType::GridContainer) {
+      if ((fType == LayoutFrameType::GridContainer &&
            aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) ||
-          (fType == FrameType::FlexContainer &&
+          (fType == LayoutFrameType::FlexContainer &&
            aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) ||
-          (fType == FrameType::TableWrapper &&
+          (fType == LayoutFrameType::TableWrapper &&
            static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() == 0)) {
         // empty grid/flex/table container
         aResult->mBStart = 0;
         aResult->mBaseline = aFrame->SynthesizeBaselineBOffsetFromBorderBox(aWM,
                                        BaselineSharingGroup::eFirst);
         aResult->mBEnd = aFrame->BSize(aWM);
         return true;
       }
@@ -5988,17 +5987,17 @@ nsLayoutUtils::GetFirstLinePosition(Writ
       aResult->mBaseline = aFrame->GetLogicalBaseline(aWM);
       // This is what we want for the list bullet caller; not sure if
       // other future callers will want the same.
       aResult->mBEnd = aFrame->BSize(aWM);
       return true;
     }
 
     // For first-line baselines, we have to consider scroll frames.
-    if (fType == FrameType::Scroll) {
+    if (fType == LayoutFrameType::Scroll) {
       nsIScrollableFrame *sFrame = do_QueryFrame(const_cast<nsIFrame*>(aFrame));
       if (!sFrame) {
         NS_NOTREACHED("not scroll frame");
       }
       LinePosition kidPosition;
       if (GetFirstLinePosition(aWM,
                                sFrame->GetScrolledFrame(), &kidPosition)) {
         // Consider only the border and padding that contributes to the
@@ -6006,17 +6005,17 @@ nsLayoutUtils::GetFirstLinePosition(Writ
         // position.
         *aResult = kidPosition +
           aFrame->GetLogicalUsedBorderAndPadding(aWM).BStart(aWM);
         return true;
       }
       return false;
     }
 
-    if (fType == FrameType::FieldSet) {
+    if (fType == LayoutFrameType::FieldSet) {
       LinePosition kidPosition;
       nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
       // kid might be a legend frame here, but that's ok.
       if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
         *aResult = kidPosition +
           kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
         return true;
       }
@@ -6937,24 +6936,24 @@ nsLayoutUtils::GetFrameTransparency(nsIF
       bg->BottomLayer().mClip != StyleGeometryBox::BorderBox)
     return eTransparencyTransparent;
   return eTransparencyOpaque;
 }
 
 static bool IsPopupFrame(nsIFrame* aFrame)
 {
   // aFrame is a popup it's the list control frame dropdown for a combobox.
-  FrameType frameType = aFrame->Type();
-  if (frameType == FrameType::ListControl) {
+  LayoutFrameType frameType = aFrame->Type();
+  if (frameType == LayoutFrameType::ListControl) {
     nsListControlFrame* lcf = static_cast<nsListControlFrame*>(aFrame);
     return lcf->IsInDropDownMode();
   }
 
   // ... or if it's a XUL menupopup frame.
-  return frameType == FrameType::MenuPopup;
+  return frameType == LayoutFrameType::MenuPopup;
 }
 
 /* static */ bool
 nsLayoutUtils::IsPopup(nsIFrame* aFrame)
 {
   // Optimization: the frame can't possibly be a popup if it has no view.
   if (!aFrame->HasView()) {
     NS_ASSERTION(!IsPopupFrame(aFrame), "popup frame must have a view");
@@ -7121,19 +7120,19 @@ nsLayoutUtils::GetDeviceContextForScreen
 nsLayoutUtils::IsReallyFixedPos(nsIFrame* aFrame)
 {
   NS_PRECONDITION(aFrame->GetParent(),
                   "IsReallyFixedPos called on frame not in tree");
   NS_PRECONDITION(aFrame->StyleDisplay()->mPosition ==
                     NS_STYLE_POSITION_FIXED,
                   "IsReallyFixedPos called on non-'position:fixed' frame");
 
-  FrameType parentType = aFrame->GetParent()->Type();
-  return parentType == FrameType::Viewport ||
-         parentType == FrameType::PageContent;
+  LayoutFrameType parentType = aFrame->GetParent()->Type();
+  return parentType == LayoutFrameType::Viewport ||
+         parentType == LayoutFrameType::PageContent;
 }
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas,
                                           uint32_t aSurfaceFlags,
                                           RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
@@ -7932,29 +7931,29 @@ nsLayoutUtils::FontSizeInflationInner(co
 
   // If between this current frame and its font inflation container there is a
   // non-inline element with fixed width or height, then we should not inflate
   // fonts for this frame.
   for (const nsIFrame* f = aFrame;
        f && !f->IsContainerForFontSizeInflation();
        f = f->GetParent()) {
     nsIContent* content = f->GetContent();
-    FrameType fType = f->Type();
+    LayoutFrameType fType = f->Type();
     nsIFrame* parent = f->GetParent();
     // Also, if there is more than one frame corresponding to a single
     // content node, we want the outermost one.
     if (!(parent && parent->GetContent() == content) &&
         // ignore width/height on inlines since they don't apply
-        fType != FrameType::Inline &&
+        fType != LayoutFrameType::Inline &&
         // ignore width on radios and checkboxes since we enlarge them and
         // they have width/height in ua.css
-        fType != FrameType::FormControl) {
+        fType != LayoutFrameType::FormControl) {
       // ruby annotations should have the same inflation as its
       // grandparent, which is the ruby frame contains the annotation.
-      if (fType == FrameType::RubyText) {
+      if (fType == LayoutFrameType::RubyText) {
         MOZ_ASSERT(parent && parent->IsRubyTextContainerFrame());
         nsIFrame* grandparent = parent->GetParent();
         MOZ_ASSERT(grandparent && grandparent->IsRubyFrame());
         return FontSizeInflationFor(grandparent);
       }
       nsStyleCoord stylePosWidth = f->StylePosition()->mWidth;
       nsStyleCoord stylePosHeight = f->StylePosition()->mHeight;
       if (stylePosWidth.GetUnit() != eStyleUnit_Auto ||
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -61,17 +61,17 @@ class nsIDocument;
 struct gfxPoint;
 struct nsStyleFont;
 struct nsStyleImageOrientation;
 struct nsOverflowAreas;
 
 namespace mozilla {
 enum class CSSPseudoElementType : uint8_t;
 class EventListenerManager;
-enum class FrameType : uint8_t;
+enum class LayoutFrameType : uint8_t;
 struct IntrinsicSize;
 struct ContainerLayerParameters;
 class WritingMode;
 namespace dom {
 class CanvasRenderingContext2D;
 class DOMRectList;