Merge inbound to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 19 Aug 2017 15:29:10 -0700
changeset 375811 5ca5691372cb432ec1fa4693ca608a30858226de
parent 375771 c40774ced6619efd0359fb94a3dbcb144e9d6fc9 (current diff)
parent 375810 531b8905ad4dc655105f59b1d5b9a3e336554a9f (diff)
child 375812 858be85c2563492d33bc4a01677ff0212a7de301
child 375826 e373771f250e8307261b5086a3590f7a802d8489
child 375848 71f4f2c0ec56937235b27ebbc3089af63991d1d0
push id32362
push userphilringnalda@gmail.com
push dateSat, 19 Aug 2017 22:29:29 +0000
treeherdermozilla-central@5ca5691372cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
5ca5691372cb / 57.0a1 / 20170820100343 / files
nightly linux64
5ca5691372cb / 57.0a1 / 20170820100343 / files
nightly mac
5ca5691372cb / 57.0a1 / 20170820100343 / files
nightly win32
5ca5691372cb / 57.0a1 / 20170820100343 / files
nightly win64
5ca5691372cb / 57.0a1 / 20170820100343 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c, a=merge MozReview-Commit-ID: LCCoXUsCtmv
modules/libpref/init/all.js
security/sandbox/linux/moz.build
testing/web-platform/meta/MANIFEST.json
--- a/Makefile.in
+++ b/Makefile.in
@@ -300,16 +300,20 @@ ifdef SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE
 	$(PYTHON) -u $(topsrcdir)/toolkit/crashreporter/tools/upload_symbols.py '$(DIST)/$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip'
 endif
 endif
 
 .PHONY: update-packaging
 update-packaging:
 	$(MAKE) -C tools/update-packaging
 
+.PHONY: package-generated-sources
+package-generated-sources:
+	$(call py_action,package_generated_sources,'$(DIST)/$(PKG_PATH)$(GENERATED_SOURCE_FILE_PACKAGE)')
+
 #XXX: this is a hack, since we don't want to clobber for MSVC
 # PGO support, but we can't do this test in client.mk
 ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
 # No point in clobbering if PGO has been explicitly disabled.
 ifndef NO_PROFILE_GUIDED_OPTIMIZE
 maybe_clobber_profiledbuild: clean
 else
 maybe_clobber_profiledbuild:
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -321,16 +321,20 @@ toolbarpaletteitem > toolbaritem[sdkstyl
 toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
   display: -moz-box;
 }
 
 toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
   display: -moz-box;
 }
 
+.webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+  width: 16px;
+}
+
 @media not all and (min-resolution: 1.1dppx) {
   .webextension-browser-action {
     list-style-image: var(--webextension-toolbar-image, inherit);
   }
 
   .webextension-browser-action:-moz-lwtheme-brighttext {
     list-style-image: var(--webextension-toolbar-image-light, inherit);
   }
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -172,17 +172,17 @@ this.browserAction = class extends Exten
         node.classList.add("badged-button");
         node.classList.add("webextension-browser-action");
         node.setAttribute("constrain-size", "true");
 
         node.onmousedown = event => this.handleEvent(event);
         node.onmouseover = event => this.handleEvent(event);
         node.onmouseout = event => this.handleEvent(event);
 
-        this.updateButton(node, this.defaults);
+        this.updateButton(node, this.defaults, true);
       },
 
       onViewShowing: async event => {
         TelemetryStopwatch.start(POPUP_OPEN_MS_HISTOGRAM, this);
         let document = event.target.ownerDocument;
         let tabbrowser = document.defaultView.gBrowser;
 
         let tab = tabbrowser.selectedTab;
@@ -424,20 +424,19 @@ this.browserAction = class extends Exten
     if (this.pendingPopupTimeout) {
       clearTimeout(this.pendingPopupTimeout);
       this.pendingPopupTimeout = null;
     }
   }
 
   // Update the toolbar button |node| with the tab context data
   // in |tabData|.
-  updateButton(node, tabData) {
+  updateButton(node, tabData, sync = false) {
     let title = tabData.title || this.extension.name;
-
-    node.ownerGlobal.requestAnimationFrame(() => {
+    let callback = () => {
       node.setAttribute("tooltiptext", title);
       node.setAttribute("label", title);
 
       if (tabData.badgeText) {
         node.setAttribute("badge", tabData.badgeText);
       } else {
         node.removeAttribute("badge");
       }
@@ -462,17 +461,22 @@ this.browserAction = class extends Exten
       const LEGACY_CLASS = "toolbarbutton-legacy-addon";
       if (legacy) {
         node.classList.add(LEGACY_CLASS);
       } else {
         node.classList.remove(LEGACY_CLASS);
       }
 
       node.setAttribute("style", style);
-    });
+    };
+    if (sync) {
+      callback();
+    } else {
+      node.ownerGlobal.requestAnimationFrame(callback);
+    }
   }
 
   getIconData(icons) {
     let baseSize = 16;
     let {icon, size} = IconDetails.getPreferredIcon(icons, this.extension, baseSize);
 
     let legacy = false;
 
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -146,28 +146,32 @@
             "name": "details",
             "type": "object",
             "properties": {
               "imageData": {
                 "choices": [
                   { "$ref": "ImageDataType" },
                   {
                     "type": "object",
-                    "additionalProperties": {"$ref": "ImageDataType"}
+                    "patternProperties": {
+                      "^[1-9]\\d*$": {"$ref": "ImageDataType"}
+                    }
                   }
                 ],
                 "optional": true,
                 "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
               },
               "path": {
                 "choices": [
                   { "type": "string" },
                   {
                     "type": "object",
-                    "additionalProperties": {"type": "string"}
+                    "patternProperties": {
+                      "^[1-9]\\d*$": { "type": "string" }
+                    }
                   }
                 ],
                 "optional": true,
                 "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -138,28 +138,32 @@
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
               "imageData": {
                 "choices": [
                   { "$ref": "ImageDataType" },
                   {
                     "type": "object",
-                    "additionalProperties": {"$ref": "ImageDataType"}
+                    "patternProperties": {
+                      "^[1-9]\\d*$": {"$ref": "ImageDataType"}
+                    }
                   }
                 ],
                 "optional": true,
                 "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
               },
               "path": {
                 "choices": [
                   { "type": "string" },
                   {
                     "type": "object",
-                    "additionalProperties": {"type": "string"}
+                    "patternProperties": {
+                      "^[1-9]\\d*$": { "type": "string" }
+                    }
                   }
                 ],
                 "optional": true,
                 "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
               }
             }
           },
           {
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
@@ -14,21 +14,20 @@ add_task(async function testInvalidIconS
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         let tabId = tabs[0].id;
 
         let promises = [];
         for (let api of ["pageAction", "browserAction"]) {
           // helper function to run setIcon and check if it fails
           let assertSetIconThrows = function(detail, error, message) {
             detail.tabId = tabId;
-            promises.push(
-              browser.test.assertRejects(
-                browser[api].setIcon(detail),
-                /must be an integer/,
-                "setIcon with invalid icon size"));
+            browser.test.assertThrows(
+              () => browser[api].setIcon(detail),
+              /an unexpected .* property/,
+              "setIcon with invalid icon size");
           };
 
           let imageData = new ImageData(1, 1);
 
           // test invalid icon size inputs
           for (let type of ["path", "imageData"]) {
             let img = type == "imageData" ? imageData : "test.png";
 
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -133,24 +133,31 @@ function needHomepageOverride(prefb) {
  * updated.
  * @param  defaultOverridePage
  *         The default override page.
  * @return The override page.
  */
 function getPostUpdateOverridePage(defaultOverridePage) {
   var um = Components.classes["@mozilla.org/updates/update-manager;1"]
                      .getService(Components.interfaces.nsIUpdateManager);
-  try {
-    // If the updates.xml file is deleted then getUpdateAt will throw.
-    var update = um.getUpdateAt(0)
+  // The active update should be present when this code is called. If for
+  // whatever reason it isn't fallback to the latest update in the update
+  // history.
+  if (um.activeUpdate) {
+    var update = um.activeUpdate
                    .QueryInterface(Components.interfaces.nsIPropertyBag);
-  } catch (e) {
-    // This should never happen.
-    Components.utils.reportError("Unable to find update: " + e);
-    return defaultOverridePage;
+  } else {
+    // If the updates.xml file is deleted then getUpdateAt will throw.
+    try {
+      update = um.getUpdateAt(0)
+                 .QueryInterface(Components.interfaces.nsIPropertyBag);
+    } catch (e) {
+      Components.utils.reportError("Unable to find update: " + e);
+      return defaultOverridePage;
+    }
   }
 
   let actions = update.getProperty("actions");
   // When the update doesn't specify actions fallback to the original behavior
   // of displaying the default override page.
   if (!actions)
     return defaultOverridePage;
 
--- a/browser/config/mozconfigs/linux32/artifact
+++ b/browser/config/mozconfigs/linux32/artifact
@@ -1,8 +1,7 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
--- a/browser/config/mozconfigs/linux32/debug-artifact
+++ b/browser/config/mozconfigs/linux32/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/build/unix/mozconfig.linux32"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
 
 ac_add_options --enable-debug
--- a/browser/config/mozconfigs/linux64/artifact
+++ b/browser/config/mozconfigs/linux64/artifact
@@ -1,8 +1,7 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
--- a/browser/config/mozconfigs/linux64/debug-artifact
+++ b/browser/config/mozconfigs/linux64/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/build/unix/mozconfig.linux"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
 
 ac_add_options --enable-debug
--- a/browser/config/mozconfigs/macosx64/artifact
+++ b/browser/config/mozconfigs/macosx64/artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 # Needed to set SourceRepository in application.ini (used by Talos)
 export MOZILLA_OFFICIAL=1
 
 . "$topsrcdir/build/macosx/mozconfig.common"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
--- a/browser/config/mozconfigs/macosx64/debug-artifact
+++ b/browser/config/mozconfigs/macosx64/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/build/macosx/mozconfig.common"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
 
 ac_add_options --enable-debug
--- a/browser/config/mozconfigs/win32/artifact
+++ b/browser/config/mozconfigs/win32/artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 # Needed to set SourceRepository in application.ini (used by Talos)
 export MOZILLA_OFFICIAL=1
 
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win32/mozconfig.vs-latest"
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/debug-artifact
+++ b/browser/config/mozconfigs/win32/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win32/mozconfig.vs-latest"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
 ac_add_options --enable-artifact-build-symbols
--- a/browser/config/mozconfigs/win64/artifact
+++ b/browser/config/mozconfigs/win64/artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 # Needed to set SourceRepository in application.ini (used by Talos)
 export MOZILLA_OFFICIAL=1
 
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win64/mozconfig.vs-latest"
--- a/browser/config/mozconfigs/win64/debug-artifact
+++ b/browser/config/mozconfigs/win64/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win64/mozconfig.vs-latest"
 . "$topsrcdir/build/mozconfig.common.override"
 
 . "$topsrcdir/build/mozconfig.artifact"
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -15,16 +15,17 @@ const ABOUT_HOME_URL = "about:home";
 const ABOUT_NEWTAB_URL = "about:newtab";
 const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
 const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
 const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
 const BRAND_SHORT_NAME = Services.strings
                      .createBundle("chrome://branding/locale/brand.properties")
                      .GetStringFromName("brandShortName");
 const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
+const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
 
 /**
  * Add any number of tours, key is the tourId, value should follow the format below
  * "tourId": { // The short tour id which could be saved in pref
  *   // The unique tour id
  *   id: "onboarding-tour-addons",
  *   // The string id of tour name which would be displayed on the navigation bar
  *   tourNameId: "onboarding.tour-addon",
@@ -363,16 +364,17 @@ class Onboarding {
     }
     this.uiInitialized = true;
     this._tourItems = [];
     this._tourPages = [];
 
     let { body } = this._window.document;
     this._overlayIcon = this._renderOverlayButton();
     this._overlayIcon.addEventListener("click", this);
+    this._overlayIcon.addEventListener("keypress", this);
     body.insertBefore(this._overlayIcon, body.firstChild);
 
     this._overlay = this._renderOverlay();
     this._overlay.addEventListener("click", this);
     this._overlay.addEventListener("keypress", this);
     body.appendChild(this._overlay);
 
     this._loadJS(TOUR_AGENT_JS_URI);
@@ -431,34 +433,42 @@ class Onboarding {
     if (this._prefsObserved) {
       for (let [name, callback] of this._prefsObserved) {
         Services.prefs.removeObserver(name, callback);
       }
       this._prefsObserved = null;
     }
   }
 
+  /**
+   * Find a tour that should be selected. It is either a first tour that was not
+   * yet complete or the first one in the tab list.
+   */
+  get selectedTour() {
+    return this._tours.find(tour => !this.isTourCompleted(tour.id)) ||
+           this._tours[0];
+  }
+
   handleClick(target) {
     let { id, classList } = target;
     // Only containers receive pointer events in onboarding tour tab list,
     // actual semantic tab is their first child.
     if (classList.contains("onboarding-tour-item-container")) {
       ({ id, classList } = target.firstChild);
     }
 
     switch (id) {
       case "onboarding-overlay-button":
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         this.toggleOverlay();
-        let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0];
-        this.gotoPage(selectedTour.id);
+        this.gotoPage(this.selectedTour.id);
         break;
       case "onboarding-notification-close-btn":
         this.hideNotification();
         this._removeTourFromNotificationQueue(this._notificationBar.dataset.targetTourId);
         break;
       case "onboarding-notification-action-btn":
         let tourId = this._notificationBar.dataset.targetTourId;
         this.toggleOverlay();
@@ -472,18 +482,56 @@ class Onboarding {
       // navigation.
       target.focus();
     } else if (classList.contains("onboarding-tour-action-button")) {
       let activeItem = this._tourItems.find(item => item.classList.contains("onboarding-active"));
       this.setToursCompleted([ activeItem.id ]);
     }
   }
 
+  /**
+   * Wrap keyboard focus within the dialog and focus on first element after last
+   * when moving forward or last element after first when moving backwards. Do
+   * nothing if focus is moving in the middle of the list of dialog's focusable
+   * elements.
+   *
+   * @param  {DOMNode} current  currently focused element
+   * @param  {Boolean} back     direction
+   * @return {DOMNode}          newly focused element if any
+   */
+  wrapMoveFocus(current, back) {
+    let elms = [...this._dialog.querySelectorAll(
+      `button, input[type="checkbox"], input[type="email"], [tabindex="0"]`)];
+    let next;
+    if (back) {
+      if (elms.indexOf(current) === 0) {
+        next = elms[elms.length - 1];
+        next.focus();
+      }
+    } else if (elms.indexOf(current) === elms.length - 1) {
+      next = elms[0];
+      next.focus();
+    }
+    return next;
+  }
+
   handleKeypress(event) {
-    let { target, key } = event;
+    let { target, key, shiftKey } = event;
+
+    if (target === this._overlayIcon) {
+      if ([" ", "Enter"].includes(key)) {
+        // Remember that the dialog was opened with a keyboard.
+        this._overlayIcon.dataset.keyboardFocus = true;
+        this.handleClick(target);
+        event.preventDefault();
+      }
+
+      return;
+    }
+
     // Current focused item can be tab container if previous navigation was done
     // via mouse.
     if (target.classList.contains("onboarding-tour-item-container")) {
       target = target.firstChild;
     }
     let targetIndex;
     switch (key) {
       case " ":
@@ -510,16 +558,26 @@ class Onboarding {
         targetIndex = this._tourItems.indexOf(target);
         if (targetIndex > -1 && targetIndex < this._tourItems.length - 1) {
           let next = this._tourItems[targetIndex + 1];
           this.handleClick(next);
           next.focus();
         }
         event.preventDefault();
         break;
+      case "Escape":
+        this.toggleOverlay();
+        break;
+      case "Tab":
+        let next = this.wrapMoveFocus(target, shiftKey);
+        // If focus was wrapped, prevent Tab key default action.
+        if (next) {
+          event.preventDefault();
+        }
+        break;
       default:
         break;
     }
     event.stopPropagation();
   }
 
   handleEvent(evt) {
     switch (evt.type) {
@@ -559,23 +617,59 @@ class Onboarding {
   toggleOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
       this._loadTours(this._tours);
     }
 
     this.hideNotification();
     this._overlay.classList.toggle("onboarding-opened");
+    this.toggleModal(this._overlay.classList.contains("onboarding-opened"));
 
     let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
     if (hiddenCheckbox.checked) {
       this.hide();
     }
   }
 
+  /**
+   * Set modal dialog state and properties for accessibility purposes.
+   * @param  {Boolean} opened  whether the dialog is opened or closed.
+   */
+  toggleModal(opened) {
+    let { document: doc } = this._window;
+    if (opened) {
+      // Set aria-hidden to true for the rest of the document.
+      [...doc.body.children].forEach(
+        child => child.id !== "onboarding-overlay" &&
+                 child.setAttribute("aria-hidden", true));
+      // When dialog is opened with the keyboard, focus on the selected or
+      // first tour item.
+      if (this._overlayIcon.dataset.keyboardFocus) {
+        doc.getElementById(this.selectedTour.id).focus();
+      } else {
+        // When dialog is opened with mouse, focus on the dialog itself to avoid
+        // visible keyboard focus styling.
+        this._dialog.focus();
+      }
+    } else {
+      // Remove all set aria-hidden attributes.
+      [...doc.body.children].forEach(
+        child => child.removeAttribute("aria-hidden"));
+      // If dialog was opened with a keyboard, set the focus back on the overlay
+      // button.
+      if (this._overlayIcon.dataset.keyboardFocus) {
+        delete this._overlayIcon.dataset.keyboardFocus;
+        this._overlayIcon.focus();
+      } else {
+        this._window.document.activeElement.blur();
+      }
+    }
+  }
+
   gotoPage(tourId) {
     let targetPageId = `${tourId}-page`;
     for (let page of this._tourPages) {
       if (page.id === targetPageId) {
         page.style.display = "";
         page.dispatchEvent(new this._window.CustomEvent("beforeshow"));
       } else {
         page.style.display = "none";
@@ -863,28 +957,31 @@ class Onboarding {
   }
 
   _renderOverlay() {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-overlay";
     // We use `innerHTML` for more friendly reading.
     // The security should be fine because this is not from an external input.
     div.innerHTML = `
-      <div id="onboarding-overlay-dialog">
+      <div role="dialog" tabindex="-1" aria-labelledby="onboarding-header">
         <header id="onboarding-header"></header>
         <nav>
           <ul id="onboarding-tour-list" role="tablist"></ul>
         </nav>
         <footer id="onboarding-footer">
           <input type="checkbox" id="onboarding-tour-hidden-checkbox" /><label for="onboarding-tour-hidden-checkbox"></label>
         </footer>
         <button id="onboarding-overlay-close-btn" class="onboarding-close-btn"></button>
       </div>
     `;
 
+    this._dialog = div.querySelector(`[role="dialog"]`);
+    this._dialog.id = ONBOARDING_DIALOG_ID;
+
     div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
       this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
     div.querySelector("#onboarding-header").textContent =
       this._bundle.GetStringFromName("onboarding.overlay-title2");
     let closeBtn = div.querySelector("#onboarding-overlay-close-btn");
     closeBtn.setAttribute("title",
       this._bundle.GetStringFromName("onboarding.overlay-close-button-tooltip"));
     return div;
@@ -893,17 +990,17 @@ class Onboarding {
   _renderOverlayButton() {
     let button = this._window.document.createElement("button");
     let tooltipStringId = this._tourType === "new" ?
       "onboarding.overlay-icon-tooltip2" : "onboarding.overlay-icon-tooltip-updated2";
     let tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
     button.setAttribute("aria-label", tooltip);
     button.id = "onboarding-overlay-button";
     button.setAttribute("aria-haspopup", true);
-    button.setAttribute("aria-controls", "onboarding-overlay-dialog");
+    button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
     let img = this._window.document.createElement("img");
     img.id = "onboarding-overlay-button-icon";
     img.setAttribute("role", "presentation");
     img.src = "resource://onboarding/img/overlay-icon.svg";
     button.appendChild(img);
     return button;
   }
 
@@ -954,21 +1051,20 @@ class Onboarding {
       pagesFrag.appendChild(div);
       // Cache elements in arrays for later use to avoid cost of querying elements
       this._tourItems.push(tab);
       this._tourPages.push(div);
 
       this.markTourCompletionState(tour.id);
     }
 
-    let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
     let ul = this._window.document.getElementById("onboarding-tour-list");
     ul.appendChild(itemsFrag);
     let footer = this._window.document.getElementById("onboarding-footer");
-    dialog.insertBefore(pagesFrag, footer);
+    this._dialog.insertBefore(pagesFrag, footer);
   }
 
   _loadCSS() {
     // Returning a Promise so we can inform caller of loading complete
     // by resolving it.
     return new Promise(resolve => {
       let doc = this._window.document;
       let link = doc.createElement("link");
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js
@@ -58,8 +58,35 @@ add_task(async function test_onboarding_
       "onboarding-notification-body"
     ].forEach(id =>
       is(doc.getElementById(id).getAttribute("role"), "presentation",
         "Element is only used for presentation"));
   });
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+add_task(async function test_onboarding_overlay_dialog() {
+  resetOnboardingDefaultState();
+
+  info("Wait for onboarding overlay loaded");
+  let tab = await openTab(ABOUT_HOME_URL);
+  let browser = tab.linkedBrowser;
+  await promiseOnboardingOverlayLoaded(browser);
+
+  info("Test accessibility and semantics of the dialog overlay");
+  await assertModalDialog(browser, { visible: false });
+
+  info("Click on overlay button and check modal dialog state");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button",
+                                                 {}, browser);
+  await promiseOnboardingOverlayOpened(browser);
+  await assertModalDialog(browser,
+    { visible: true, focusedId: "onboarding-overlay-dialog" });
+
+  info("Close the dialog and check modal dialog state");
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn",
+                                                 {}, browser);
+  await promiseOnboardingOverlayClosed(browser);
+  await assertModalDialog(browser, { visible: false });
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js
@@ -1,61 +1,137 @@
 /* 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";
 
-function assertTourList(browser, args) {
-  return ContentTask.spawn(browser, args, ({ tourId, focusedId }) => {
-    let doc = content.document;
-    let items = [...doc.querySelectorAll(".onboarding-tour-item")];
-    items.forEach(item => is(item.getAttribute("aria-selected"),
-      item.id === tourId ? "true" : "false",
-      "Active item should have aria-selected set to true and inactive to false"));
-    let focused = doc.getElementById(focusedId);
-    is(focused, doc.activeElement, `Focus should be set on ${focusedId}`);
+function assertOverlayState(browser, args) {
+  return ContentTask.spawn(browser, args, ({ tourId, focusedId, visible }) => {
+    let { document: doc, window} = content;
+    if (tourId) {
+      let items = [...doc.querySelectorAll(".onboarding-tour-item")];
+      items.forEach(item => is(item.getAttribute("aria-selected"),
+        item.id === tourId ? "true" : "false",
+        "Active item should have aria-selected set to true and inactive to false"));
+    }
+    if (focusedId) {
+      let focused = doc.getElementById(focusedId);
+      is(focused, doc.activeElement, `Focus should be set on ${focusedId}`);
+    }
+    if (visible !== undefined) {
+      let overlay = doc.getElementById("onboarding-overlay");
+      is(window.getComputedStyle(overlay).getPropertyValue("display"),
+        visible ? "block" : "none",
+        `Onboarding overlay should be ${visible ? "visible" : "invisible"}`);
+    }
   });
 }
 
-const TEST_DATA = [
+const TOUR_LIST_TEST_DATA = [
   { key: "VK_DOWN", expected: { tourId: TOUR_IDs[1], focusedId: TOUR_IDs[1] }},
   { key: "VK_DOWN", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }},
   { key: "VK_DOWN", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }},
   { key: "VK_DOWN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }},
   { key: "VK_UP", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }},
   { key: "VK_UP", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }},
   { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[3] }},
   { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[4] }},
   { key: "VK_RETURN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }},
   { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[3] }},
   { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[2] }},
   // VK_SPACE does not work well with EventUtils#synthesizeKey use " " instead
   { key: " ", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}
 ];
 
+const BUTTONS_TEST_DATA = [
+  { key: " ", expected: { focusedId: TOUR_IDs[0], visible: true }},
+  { key: "VK_ESCAPE", expected: { focusedId: "onboarding-overlay-button", visible: false }},
+  { key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
+  { key: " ", expected: { focusedId: "onboarding-overlay-button", visible: false }},
+  { key: "VK_RETURN", expected: { focusedId: TOUR_IDs[1], visible: true }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: TOUR_IDs[0], visible: true }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
+  { key: "VK_TAB", expected: { focusedId: TOUR_IDs[0], visible: true }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { focusedId: "onboarding-overlay-close-btn", visible: true }},
+  { key: "VK_RETURN", expected: { focusedId: "onboarding-overlay-button", visible: false }}
+];
+
 add_task(async function test_tour_list_keyboard_navigation() {
   resetOnboardingDefaultState();
 
   info("Display onboarding overlay on the home page");
   let tab = await openTab(ABOUT_HOME_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button",
                                                  {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
   info("Checking overall overlay tablist semantics");
   await assertOverlaySemantics(tab.linkedBrowser);
 
   info("Set initial focus on the currently active tab");
   await ContentTask.spawn(tab.linkedBrowser, {}, () =>
     content.document.querySelector(".onboarding-active").focus());
-  await assertTourList(tab.linkedBrowser,
+  await assertOverlayState(tab.linkedBrowser,
                        { tourId: TOUR_IDs[0], focusedId: TOUR_IDs[0] });
 
-  for (let { key, options = {}, expected } of TEST_DATA) {
+  for (let { key, options = {}, expected } of TOUR_LIST_TEST_DATA) {
     info(`Pressing ${key} to select ${expected.tourId} and have focus on ${expected.focusedId}`);
     await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser);
-    await assertTourList(tab.linkedBrowser, expected);
+    await assertOverlayState(tab.linkedBrowser, expected);
   }
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+add_task(async function test_buttons_keyboard_navigation() {
+  resetOnboardingDefaultState();
+
+  info("Wait for onboarding overlay loaded");
+  let tab = await openTab(ABOUT_HOME_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+
+  info("Set keyboard focus on the onboarding overlay button");
+  await ContentTask.spawn(tab.linkedBrowser, {}, () =>
+    content.document.getElementById("onboarding-overlay-button").focus());
+  await assertOverlayState(tab.linkedBrowser,
+    { focusedId: "onboarding-overlay-button", visible: false });
+
+  for (let { key, options = {}, expected } of BUTTONS_TEST_DATA) {
+    info(`Pressing ${key} to have ${expected.visible ? "visible" : "invisible"} overlay and have focus on ${expected.focusedId}`);
+    await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser);
+    await assertOverlayState(tab.linkedBrowser, expected);
+  }
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_overlay_dialog_keyboard_navigation() {
+  resetOnboardingDefaultState();
+
+  info("Wait for onboarding overlay loaded");
+  let tab = await openTab(ABOUT_HOME_URL);
+  let browser = tab.linkedBrowser;
+  await promiseOnboardingOverlayLoaded(browser);
+
+  info("Test accessibility and semantics of the dialog overlay");
+  await assertModalDialog(browser, { visible: false });
+
+  info("Set keyboard focus on the onboarding overlay button");
+  await ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById("onboarding-overlay-button").focus());
+  info("Open dialog with keyboard and check the dialog state");
+  await BrowserTestUtils.synthesizeKey(" ", {}, browser);
+  await promiseOnboardingOverlayOpened(browser);
+  await assertModalDialog(browser,
+    { visible: true, keyboardFocus: true, focusedId: TOUR_IDs[0] });
+
+  info("Close the dialog and check modal dialog state");
+  await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, browser);
+  await promiseOnboardingOverlayClosed(browser);
+  await assertModalDialog(browser,
+    { visible: false, keyboardFocus: true, focusedId: "onboarding-overlay-button" });
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -82,31 +82,32 @@ function promiseOnboardingOverlayLoaded(
       });
       observer.observe(doc.body, { childList: true });
     });
   }
   return ContentTask.spawn(browser, {}, isLoaded);
 }
 
 function promiseOnboardingOverlayOpened(browser) {
-  let condition = () => {
-    return ContentTask.spawn(browser, {}, function() {
-      return new Promise(resolve => {
-        let overlay = content.document.querySelector("#onboarding-overlay");
-        if (overlay.classList.contains("onboarding-opened")) {
-          resolve(true);
-          return;
-        }
-        resolve(false);
-      });
-    })
-  };
-  return BrowserTestUtils.waitForCondition(
-    condition,
-    "Should open onboarding overlay",
+  return BrowserTestUtils.waitForCondition(() =>
+    ContentTask.spawn(browser, {}, () =>
+      content.document.querySelector("#onboarding-overlay").classList.contains(
+        "onboarding-opened")),
+    "Should close onboarding overlay",
+    100,
+    30
+  );
+}
+
+function promiseOnboardingOverlayClosed(browser) {
+  return BrowserTestUtils.waitForCondition(() =>
+    ContentTask.spawn(browser, {}, () =>
+      !content.document.querySelector("#onboarding-overlay").classList.contains(
+        "onboarding-opened")),
+    "Should close onboarding overlay",
     100,
     30
   );
 }
 
 function promisePrefUpdated(name, expectedValue) {
   return new Promise(resolve => {
     let onUpdate = actualValue => {
@@ -204,33 +205,69 @@ function waitUntilWindowIdle(browser) {
 function skipMuteNotificationOnFirstSession() {
   Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 0);
 }
 
 function assertOverlaySemantics(browser) {
   return ContentTask.spawn(browser, {}, function() {
     let doc = content.document;
 
+    info("Checking dialog");
+    let dialog = doc.getElementById("onboarding-overlay-dialog");
+    is(dialog.getAttribute("role"), "dialog",
+      "Dialog should have a dialog role attribute set");
+    is(dialog.tabIndex, "-1", "Dialog should be focusable but not in tab order");
+    is(dialog.getAttribute("aria-labelledby"), "onboarding-header",
+      "Dialog should be labaled by its header");
+
     info("Checking the tablist container");
     is(doc.getElementById("onboarding-tour-list").getAttribute("role"), "tablist",
-      "Tour list should have a tablist role argument set");
+      "Tour list should have a tablist role attribute set");
 
     info("Checking each tour item that represents the tab");
     let items = [...doc.querySelectorAll(".onboarding-tour-item")];
     items.forEach(item => {
       is(item.parentNode.getAttribute("role"), "presentation",
         "Parent should have no semantic value");
       is(item.getAttribute("aria-selected"),
          item.classList.contains("onboarding-active") ? "true" : "false",
          "Active item should have aria-selected set to true and inactive to false");
       is(item.tabIndex, "0", "Item tab index must be set for keyboard accessibility");
-      is(item.getAttribute("role"), "tab", "Item should have a tab role argument set");
+      is(item.getAttribute("role"), "tab", "Item should have a tab role attribute set");
       let tourPanelId = `${item.id}-page`;
       is(item.getAttribute("aria-controls"), tourPanelId,
         "Item should have aria-controls attribute point to its tabpanel");
       let panel = doc.getElementById(tourPanelId);
       is(panel.getAttribute("role"), "tabpanel",
-        "Tour panel should have a tabpanel role argument set");
+        "Tour panel should have a tabpanel role attribute set");
       is(panel.getAttribute("aria-labelledby"), item.id,
         "Tour panel should have aria-labelledby attribute point to its tab");
     });
   });
 }
+
+function assertModalDialog(browser, args) {
+  return ContentTask.spawn(browser, args, ({ keyboardFocus, visible, focusedId }) => {
+    let doc = content.document;
+    let overlayButton = doc.getElementById("onboarding-overlay-button");
+    if (visible) {
+      [...doc.body.children].forEach(child =>
+        child.id !== "onboarding-overlay" &&
+        is(child.getAttribute("aria-hidden"), "true",
+          "Content should not be visible to screen reader"));
+      is(focusedId ? doc.getElementById(focusedId) : doc.body,
+        doc.activeElement, `Focus should be on ${focusedId || "body"}`);
+      is(keyboardFocus ? "true" : undefined,
+        overlayButton.dataset.keyboardFocus,
+        "Overlay button focus state is saved correctly");
+    } else {
+      [...doc.body.children].forEach(
+        child => ok(!child.hasAttribute("aria-hidden"),
+          "Content should be visible to screen reader"));
+      if (keyboardFocus) {
+        is(overlayButton, doc.activeElement,
+          "Focus should be set on overlay button");
+      }
+      ok(!overlayButton.dataset.keyboardFocus,
+        "Overlay button focus state should be cleared");
+    }
+  });
+}
--- a/build/moz-automation.mk
+++ b/build/moz-automation.mk
@@ -22,30 +22,32 @@ endif
 # Helper variables to convert from MOZ_AUTOMATION_* variables to the
 # corresponding the make target
 tier_MOZ_AUTOMATION_BUILD_SYMBOLS = buildsymbols
 tier_MOZ_AUTOMATION_L10N_CHECK = l10n-check
 tier_MOZ_AUTOMATION_INSTALLER = installer
 tier_MOZ_AUTOMATION_PACKAGE = package
 tier_MOZ_AUTOMATION_PACKAGE_TESTS = package-tests
 tier_MOZ_AUTOMATION_UPDATE_PACKAGING = update-packaging
+tier_MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES = package-generated-sources
 tier_MOZ_AUTOMATION_UPLOAD_SYMBOLS = uploadsymbols
 tier_MOZ_AUTOMATION_UPLOAD = upload
 
 # Automation build steps. Everything in MOZ_AUTOMATION_TIERS also gets used in
 # TIERS for mach display. As such, the MOZ_AUTOMATION_TIERS are roughly sorted
 # here in the order that they will be executed (since mach doesn't know of the
 # dependencies between them).
 moz_automation_symbols = \
   MOZ_AUTOMATION_PACKAGE_TESTS \
   MOZ_AUTOMATION_BUILD_SYMBOLS \
   MOZ_AUTOMATION_UPLOAD_SYMBOLS \
   MOZ_AUTOMATION_PACKAGE \
   MOZ_AUTOMATION_INSTALLER \
   MOZ_AUTOMATION_UPDATE_PACKAGING \
+  MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES \
   MOZ_AUTOMATION_L10N_CHECK \
   MOZ_AUTOMATION_UPLOAD \
   $(NULL)
 MOZ_AUTOMATION_TIERS := $(foreach sym,$(moz_automation_symbols),$(if $(filter 1,$($(sym))),$(tier_$(sym))))
 
 # Dependencies between automation build steps
 automation/uploadsymbols: automation/buildsymbols
 
@@ -55,16 +57,17 @@ automation/update-packaging: automation/
 automation/l10n-check: automation/package
 automation/l10n-check: automation/installer
 
 automation/upload: automation/installer
 automation/upload: automation/package
 automation/upload: automation/package-tests
 automation/upload: automation/buildsymbols
 automation/upload: automation/update-packaging
+automation/upload: automation/package-generated-sources
 
 # The installer and packager all run stage-package, and may conflict
 # with each other.
 automation/installer: automation/package
 
 automation/build: $(addprefix automation/,$(MOZ_AUTOMATION_TIERS))
 	@echo Automation steps completed.
 
--- a/build/moz.configure/warnings.configure
+++ b/build/moz.configure/warnings.configure
@@ -37,16 +37,17 @@ add_gcc_warning('-Wpointer-arith')
 # catches comparing signed/unsigned ints
 add_gcc_warning('-Wsign-compare')
 
 # catches overflow bugs, few false positives
 add_gcc_warning('-Wtype-limits')
 
 # catches some dead code
 add_gcc_warning('-Wunreachable-code')
+check_and_add_gcc_warning('-Wunreachable-code-return')
 
 # catches treating string literals as non-const
 add_gcc_warning('-Wwrite-strings', cxx_compiler)
 
 # turned on by -Wall, but we use offsetof on non-POD types frequently
 add_gcc_warning('-Wno-invalid-offsetof', cxx_compiler)
 
 # catches objects passed by value to variadic functions.
new file mode 100644
--- /dev/null
+++ b/build/mozconfig.artifact.automation
@@ -0,0 +1,6 @@
+# Common options for artifact builds to set automation steps.
+# This gets included before mozconfig.automation.
+
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
--- a/build/mozconfig.automation
+++ b/build/mozconfig.automation
@@ -10,12 +10,13 @@
 # overridden by setting them earlier in the appropriate mozconfig.
 
 mk_add_options "export MOZ_AUTOMATION_BUILD_SYMBOLS=${MOZ_AUTOMATION_BUILD_SYMBOLS-1}"
 mk_add_options "export MOZ_AUTOMATION_L10N_CHECK=${MOZ_AUTOMATION_L10N_CHECK-1}"
 mk_add_options "export MOZ_AUTOMATION_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}"
 mk_add_options "export MOZ_AUTOMATION_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}"
 mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
 mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}"
+mk_add_options "export MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=${MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES-1}"
 mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}"
 mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}"
 
 export MOZ_AUTOMATION_MOZCONFIG=1
new file mode 100644
--- /dev/null
+++ b/build/upload_generated_sources.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env/python
+# 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/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import argparse
+from concurrent.futures import ThreadPoolExecutor
+from contextlib import contextmanager
+import gzip
+import io
+import logging
+from mozbuild.base import MozbuildObject
+from mozbuild.generated_sources import (
+    get_filename_with_digest,
+    get_s3_region_and_bucket,
+)
+import os
+from Queue import Queue
+import requests
+import sys
+import tarfile
+from threading import Event, Thread
+import time
+
+# Arbitrary, should probably measure this.
+NUM_WORKER_THREADS = 10
+log = logging.getLogger('upload-generated-sources')
+log.setLevel(logging.INFO)
+
+
+@contextmanager
+def timed():
+    '''
+    Yield a function that provides the elapsed time in seconds since this
+    function was called.
+    '''
+    start = time.time()
+    def elapsed():
+        return time.time() - start
+    yield elapsed
+
+
+def gzip_compress(data):
+    '''
+    Apply gzip compression to `data` and return the result as a `BytesIO`.
+    '''
+    b = io.BytesIO()
+    with gzip.GzipFile(fileobj=b, mode='w') as f:
+        f.write(data)
+    b.flush()
+    b.seek(0)
+    return b
+
+
+def upload_worker(queue, event, bucket, session_args):
+    '''
+    Get `(name, contents)` entries from `queue` and upload `contents`
+    to S3 with gzip compression using `name` as the key, prefixed with
+    the SHA-512 digest of `contents` as a hex string. If an exception occurs,
+    set `event`.
+    '''
+    try:
+        import boto3
+        session = boto3.session.Session(**session_args)
+        s3 = session.client('s3')
+        while True:
+            if event.is_set():
+                # Some other thread hit an exception.
+                return
+            (name, contents) = queue.get()
+            pathname = get_filename_with_digest(name, contents)
+            compressed = gzip_compress(contents)
+            extra_args = {
+                'ContentEncoding': 'gzip',
+                'ContentType': 'text/plain',
+            }
+            log.info('Uploading "{}" ({} bytes)'.format(pathname, len(compressed.getvalue())))
+            with timed() as elapsed:
+                s3.upload_fileobj(compressed, bucket, pathname, ExtraArgs=extra_args)
+                log.info('Finished uploading "{}" in {:0.3f}s'.format(pathname, elapsed()))
+            queue.task_done()
+    except Exception:
+        log.exception('Thread encountered exception:')
+        event.set()
+
+
+def do_work(artifact, region, bucket):
+    session_args = {'region_name': region}
+    session = requests.Session()
+    if 'TASK_ID' in os.environ:
+        level = os.environ.get('MOZ_SCM_LEVEL', '1')
+        secrets_url = 'http://taskcluster/secrets/v1/secret/project/releng/gecko/build/level-{}/gecko-generated-sources-upload'.format(level)
+        log.info('Using AWS credentials from the secrets service: "{}"'.format(secrets_url))
+        res = session.get(secrets_url)
+        res.raise_for_status()
+        secret = res.json()
+        session_args.update(
+            aws_access_key_id=secret['secret']['AWS_ACCESS_KEY_ID'],
+            aws_secret_access_key=secret['secret']['AWS_SECRET_ACCESS_KEY'],
+        )
+    else:
+        log.info('Trying to use your AWS credentials..')
+
+
+    # First, fetch the artifact containing the sources.
+    log.info('Fetching generated sources artifact: "{}"'.format(artifact))
+    with timed() as elapsed:
+        res = session.get(artifact)
+        log.info('Fetch HTTP status: {}, {} bytes downloaded in {:0.3f}s'.format(res.status_code, len(res.content), elapsed()))
+    res.raise_for_status()
+    # Create a queue and worker threads for uploading.
+    q = Queue()
+    event = Event()
+    log.info('Creating {} worker threads'.format(NUM_WORKER_THREADS))
+    for i in range(NUM_WORKER_THREADS):
+        t = Thread(target=upload_worker, args=(q, event, bucket, session_args))
+        t.daemon = True
+        t.start()
+    with tarfile.open(fileobj=io.BytesIO(res.content), mode='r|gz') as tar:
+        # Next, process each file.
+        for entry in tar:
+            if event.is_set():
+                break
+            log.info('Queueing "{}"'.format(entry.name))
+            q.put((entry.name, tar.extractfile(entry).read()))
+    # Wait until all uploads are finished.
+    # We don't use q.join() here because we want to also monitor event.
+    while q.unfinished_tasks:
+        if event.wait(0.1):
+            log.error('Worker thread encountered exception, exiting...')
+            break
+
+
+def main(argv):
+    logging.basicConfig(format='%(levelname)s - %(threadName)s - %(message)s')
+    parser = argparse.ArgumentParser(
+    description='Upload generated source files in ARTIFACT to BUCKET in S3.')
+    parser.add_argument('artifact',
+                        help='generated-sources artifact from build task')
+    args = parser.parse_args(argv)
+    region, bucket = get_s3_region_and_bucket()
+
+    config = MozbuildObject.from_environment()
+    config._activate_virtualenv()
+    config.virtualenv_manager.install_pip_package('boto3==1.4.4')
+
+    with timed() as elapsed:
+        do_work(region=region, bucket=bucket, artifact=args.artifact)
+        log.info('Finished in {:.03f}s'.format(elapsed()))
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -999,22 +999,17 @@ CssRuleView.prototype = {
     header.insertBefore(twisty, header.firstChild);
     this.element.appendChild(header);
 
     let container = this.styleDocument.createElementNS(HTML_NS, "div");
     container.classList.add("ruleview-expandable-container");
     container.hidden = false;
     this.element.appendChild(container);
 
-    header.addEventListener("dblclick", () => {
-      this._toggleContainerVisibility(twisty, container, isPseudo,
-        !this.showPseudoElements);
-    });
-
-    twisty.addEventListener("click", () => {
+    header.addEventListener("click", () => {
       this._toggleContainerVisibility(twisty, container, isPseudo,
         !this.showPseudoElements);
     });
 
     if (isPseudo) {
       this._toggleContainerVisibility(twisty, container, isPseudo,
         this.showPseudoElements);
     }
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -855,17 +855,17 @@ static const uint32_t JSCLASS_FOREGROUND
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
 static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
-    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
+    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 36;
 
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                              \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -900,18 +900,24 @@ static bool
 obj_watch(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
-    if (!GlobalObject::warnOnceAboutWatch(cx, obj))
-        return false;
+    if (!cx->compartment()->warnedAboutObjectWatch) {
+        if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                               JSMSG_OBJECT_WATCH_DEPRECATED))
+        {
+            return false;
+        }
+        cx->compartment()->warnedAboutObjectWatch = true;
+    }
 
     if (args.length() <= 1) {
         ReportMissingArg(cx, args.calleev(), 1);
         return false;
     }
 
     RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
     if (!callable)
@@ -932,18 +938,24 @@ static bool
 obj_unwatch(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
-    if (!GlobalObject::warnOnceAboutWatch(cx, obj))
-        return false;
+    if (!cx->compartment()->warnedAboutObjectWatch) {
+        if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                               JSMSG_OBJECT_WATCH_DEPRECATED))
+        {
+            return false;
+        }
+        cx->compartment()->warnedAboutObjectWatch = true;
+    }
 
     RootedId id(cx);
     if (args.length() != 0) {
         if (!ValueToId<CanGC>(cx, args[0], &id))
             return false;
     } else {
         id = JSID_VOID;
     }
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -127,20 +127,18 @@ js::gc::AllocateWholeCellSet(Arena* aren
 {
     Zone* zone = arena->zone;
     if (!zone->group()->nursery().isEnabled())
         return nullptr;
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     Nursery& nursery = zone->group()->nursery();
     void* data = nursery.allocateBuffer(zone, sizeof(ArenaCellSet));
-    if (!data) {
+    if (!data)
         oomUnsafe.crash("Failed to allocate WholeCellSet");
-        return nullptr;
-    }
 
     if (nursery.freeSpace() < ArenaCellSet::NurseryFreeThresholdBytes)
         zone->group()->storeBuffer().setAboutToOverflow(JS::gcreason::FULL_WHOLE_CELL_BUFFER);
 
     auto cells = static_cast<ArenaCellSet*>(data);
     new (cells) ArenaCellSet(arena);
     arena->bufferedCells() = cells;
     zone->group()->storeBuffer().addToWholeCellBuffer(cells);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1612,18 +1612,17 @@ IonBuilder::inlineConstantStringSplitStr
     if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles)
         return InliningStatus_NotInlined;
 
     MOZ_TRY(jsop_newarray(templateObject, initLength));
 
     MDefinition* array = current->peek(-1);
 
     if (!initLength) {
-        if (!array->isResumePoint())
-            MOZ_TRY(resumeAfter(array->toNewArray()));
+        MOZ_TRY(resumeAfter(array->toNewArray()));
         return InliningStatus_Inlined;
     }
 
     JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
 
     // Store all values, no need to initialize the length after each as
     // jsop_initelem_array is doing because we do not expect to bailout
     // because the memory is supposed to be allocated by now.
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4127,34 +4127,34 @@ MResumePoint::Copy(TempAllocator& alloc,
 
     // Copy the operands.
     for (size_t i = 0; i < resume->numOperands(); i++)
         resume->initOperand(i, src->getOperand(i));
     return resume;
 }
 
 MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode)
-  : MNode(block),
+  : MNode(block, Kind::ResumePoint),
     pc_(pc),
     instruction_(nullptr),
     mode_(mode)
 {
     block->addResumePoint(this);
 }
 
 bool
 MResumePoint::init(TempAllocator& alloc)
 {
     return operands_.init(alloc, block()->stackDepth());
 }
 
 MResumePoint*
 MResumePoint::caller() const
 {
-    return block_->callerResumePoint();
+    return block()->callerResumePoint();
 }
 
 void
 MResumePoint::inherit(MBasicBlock* block)
 {
     // FixedList doesn't initialize its elements, so do unchecked inits.
     for (size_t i = 0; i < stackDepth(); i++)
         initOperand(i, block->getSlot(i));
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -296,47 +296,71 @@ typedef InlineList<MUse>::iterator MUseI
 //   MResumePoint: a list of instructions that correspond to the state of the
 //                 interpreter/Baseline stack.
 //
 // Nodes can hold references to MDefinitions. Each MDefinition has a list of
 // nodes holding such a reference (its use chain).
 class MNode : public TempObject
 {
   protected:
-    MBasicBlock* block_;    // Containing basic block.
-
-  public:
-    enum Kind {
-        Definition,
+    enum class Kind {
+        Definition = 0,
         ResumePoint
     };
 
-    MNode()
-      : block_(nullptr)
+  private:
+    static const uintptr_t KindMask = 0x1;
+    uintptr_t blockAndKind_;
+
+    Kind kind() const {
+        return Kind(blockAndKind_ & KindMask);
+    }
+
+  protected:
+    explicit MNode(const MNode& other)
+      : blockAndKind_(other.blockAndKind_)
     { }
 
-    explicit MNode(MBasicBlock* block)
-      : block_(block)
-    { }
-
-    virtual Kind kind() const = 0;
-
+    MNode(MBasicBlock* block, Kind kind)
+    {
+        setBlockAndKind(block, kind);
+    }
+
+    void setBlockAndKind(MBasicBlock* block, Kind kind) {
+        blockAndKind_ = uintptr_t(block) | uintptr_t(kind);
+        MOZ_ASSERT(this->block() == block);
+    }
+
+    MBasicBlock* definitionBlock() const {
+        MOZ_ASSERT(isDefinition());
+        static_assert(unsigned(Kind::Definition) == 0, "Code below relies on low bit being 0");
+        return reinterpret_cast<MBasicBlock*>(blockAndKind_);
+    }
+    MBasicBlock* resumePointBlock() const {
+        MOZ_ASSERT(isResumePoint());
+        static_assert(unsigned(Kind::ResumePoint) == 1, "Code below relies on low bit being 1");
+        // Use a subtraction: if the caller does block()->foo, the compiler
+        // will be able to fold it with the load.
+        return reinterpret_cast<MBasicBlock*>(blockAndKind_ - 1);
+    }
+
+  public:
     // Returns the definition at a given operand.
     virtual MDefinition* getOperand(size_t index) const = 0;
     virtual size_t numOperands() const = 0;
     virtual size_t indexOf(const MUse* u) const = 0;
 
     bool isDefinition() const {
-        return kind() == Definition;
+        return kind() == Kind::Definition;
     }
     bool isResumePoint() const {
-        return kind() == ResumePoint;
+        return kind() == Kind::ResumePoint;
     }
     MBasicBlock* block() const {
-        return block_;
+        return reinterpret_cast<MBasicBlock*>(blockAndKind_ & ~KindMask);
     }
     MBasicBlock* caller() const;
 
     // Sets an already set operand, updating use information. If you're looking
     // for setOperand, this is probably what you want.
     virtual void replaceOperand(size_t index, MDefinition* operand) = 0;
 
     // Resets the operand to an uninitialized state, breaking the link
@@ -510,37 +534,43 @@ class MDefinition : public MNode
     }
     void removeFlags(uint32_t flags) {
         flags_ &= ~flags;
     }
     void setFlags(uint32_t flags) {
         flags_ |= flags;
     }
 
+    // Calling isDefinition or isResumePoint on MDefinition is unnecessary.
+    bool isDefinition() const = delete;
+    bool isResumePoint() const = delete;
+
   protected:
     void setBlock(MBasicBlock* block) {
-        block_ = block;
+        setBlockAndKind(block, Kind::Definition);
     }
 
     static HashNumber addU32ToHash(HashNumber hash, uint32_t data);
 
   public:
     MDefinition()
-      : id_(0),
+      : MNode(nullptr, Kind::Definition),
+        id_(0),
         flags_(0),
         range_(nullptr),
         resultType_(MIRType::None),
         resultTypeSet_(nullptr),
         loadDependency_(nullptr),
         trackedSite_(nullptr)
     { }
 
     // Copying a definition leaves the list of uses and the block empty.
     explicit MDefinition(const MDefinition& other)
-      : id_(0),
+      : MNode(other),
+        id_(0),
         flags_(other.flags_),
         range_(other.range_),
         resultType_(other.resultType_),
         resultTypeSet_(other.resultTypeSet_),
         loadDependency_(other.loadDependency_),
         trackedSite_(other.trackedSite_)
     { }
 
@@ -560,16 +590,20 @@ class MDefinition : public MNode
     virtual bool neverHoist() const { return false; }
 
     // Also for LICM. Test whether this definition is likely to be a call, which
     // would clobber all or many of the floating-point registers, such that
     // hoisting floating-point constants out of containing loops isn't likely to
     // be worthwhile.
     virtual bool possiblyCalls() const { return false; }
 
+    MBasicBlock* block() const {
+        return definitionBlock();
+    }
+
     void setTrackedSite(const BytecodeSite* site) {
         MOZ_ASSERT(site);
         trackedSite_ = site;
     }
     const BytecodeSite* trackedSite() const {
         return trackedSite_;
     }
     jsbytecode* trackedPc() const {
@@ -695,22 +729,18 @@ class MDefinition : public MNode
     // Compute an absolute or symbolic range for the value of this node.
     virtual void computeRange(TempAllocator& alloc) {
     }
 
     // Collect information from the pre-truncated ranges.
     virtual void collectRangeInfoPreTrunc() {
     }
 
-    MNode::Kind kind() const override {
-        return MNode::Definition;
-    }
-
     uint32_t id() const {
-        MOZ_ASSERT(block_);
+        MOZ_ASSERT(block());
         return id_;
     }
     void setId(uint32_t id) {
         id_ = id;
     }
 
 #define FLAG_ACCESSOR(flag) \
     bool is##flag() const {\
@@ -13325,16 +13355,24 @@ class MResumePoint final :
 
     jsbytecode* pc_;
     MInstruction* instruction_;
     Mode mode_;
 
     MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode);
     void inherit(MBasicBlock* state);
 
+    void setBlock(MBasicBlock* block) {
+        setBlockAndKind(block, Kind::ResumePoint);
+    }
+
+    // Calling isDefinition or isResumePoint on MResumePoint is unnecessary.
+    bool isDefinition() const = delete;
+    bool isResumePoint() const = delete;
+
   protected:
     // Initializes operands_ to an empty array of a fixed length.
     // The array may then be filled in by inherit().
     MOZ_MUST_USE bool init(TempAllocator& alloc);
 
     void clearOperand(size_t index) {
         // FixedList doesn't initialize its elements, so do an unchecked init.
         operands_[index].initUncheckedWithoutProducer(this);
@@ -13349,19 +13387,20 @@ class MResumePoint final :
 
   public:
     static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc,
                              Mode mode);
     static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, MResumePoint* model,
                              const MDefinitionVector& operands);
     static MResumePoint* Copy(TempAllocator& alloc, MResumePoint* src);
 
-    MNode::Kind kind() const override {
-        return MNode::ResumePoint;
-    }
+    MBasicBlock* block() const {
+        return resumePointBlock();
+    }
+
     size_t numAllocatedOperands() const {
         return operands_.length();
     }
     uint32_t stackDepth() const {
         return numAllocatedOperands();
     }
     size_t numOperands() const override {
         return numAllocatedOperands();
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -320,17 +320,17 @@ MBasicBlock*
 MBasicBlock::NewWithResumePoint(MIRGraph& graph, const CompileInfo& info,
                                 MBasicBlock* pred, BytecodeSite* site,
                                 MResumePoint* resumePoint)
 {
     MBasicBlock* block = new(graph.alloc()) MBasicBlock(graph, info, site, NORMAL);
 
     MOZ_ASSERT(!resumePoint->instruction());
     resumePoint->block()->discardResumePoint(resumePoint, RefType_None);
-    resumePoint->block_ = block;
+    resumePoint->setBlock(block);
     block->addResumePoint(resumePoint);
     block->entryResumePoint_ = resumePoint;
 
     if (!block->init())
         return nullptr;
 
     if (!block->inheritResumePoint(pred))
         return nullptr;
@@ -922,19 +922,19 @@ MBasicBlock::discardIgnoreOperands(MInst
     prepareForDiscard(ins, RefType_IgnoreOperands);
     instructions_.remove(ins);
 }
 
 void
 MBasicBlock::discardDef(MDefinition* at)
 {
     if (at->isPhi())
-        at->block_->discardPhi(at->toPhi());
+        at->block()->discardPhi(at->toPhi());
     else
-        at->block_->discard(at->toInstruction());
+        at->block()->discard(at->toInstruction());
 }
 
 void
 MBasicBlock::discardAllInstructions()
 {
     MInstructionIterator iter = begin();
     discardAllInstructionsStartingAt(iter);
 }
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -55,16 +55,17 @@ JSCompartment::JSCompartment(Zone* zone,
     isSystem_(false),
     isAtomsCompartment_(false),
     isSelfHosting(false),
     marked(true),
     warnedAboutDateToLocaleFormat(false),
     warnedAboutExprClosure(false),
     warnedAboutForEach(false),
     warnedAboutLegacyGenerator(false),
+    warnedAboutObjectWatch(false),
     warnedAboutStringGenericsMethods(0),
 #ifdef DEBUG
     firedOnNewGlobalObject(false),
 #endif
     global_(nullptr),
     enterCompartmentDepth(0),
     performanceMonitoring(runtime_),
     data(nullptr),
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -620,16 +620,17 @@ struct JSCompartment
 
   public:
     bool                         isSelfHosting;
     bool                         marked;
     bool                         warnedAboutDateToLocaleFormat : 1;
     bool                         warnedAboutExprClosure : 1;
     bool                         warnedAboutForEach : 1;
     bool                         warnedAboutLegacyGenerator : 1;
+    bool                         warnedAboutObjectWatch : 1;
     uint32_t                     warnedAboutStringGenericsMethods;
 
 #ifdef DEBUG
     bool                         firedOnNewGlobalObject;
 #endif
 
     void mark() { marked = true; }
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_2/Objects/watch-deprecated.js
@@ -0,0 +1,23 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+enableLastWarning();
+
+var g = newGlobal();
+g.eval("({}).watch('x', function(){})");
+
+var warning = getLastWarning();
+assertEq(warning.name, "Warning");
+assertEq(warning.message.includes("watch"), true, "warning should mention watch");
+
+clearLastWarning();
+
+g = newGlobal();
+g.eval("({}).unwatch('x')");
+
+warning = getLastWarning();
+assertEq(warning.name, "Warning");
+assertEq(warning.message.includes("watch"), true, "warning should mention watch");
+
+clearLastWarning();
+
+reportCompare(0, 0);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -538,38 +538,16 @@ GlobalObject::isRuntimeCodeGenEnabled(JS
          */
         JSCSPEvalChecker allows = cx->runtime()->securityCallbacks->contentSecurityPolicyAllows;
         Value boolValue = BooleanValue(!allows || allows(cx));
         v.set(global, HeapSlot::Slot, RUNTIME_CODEGEN_ENABLED, boolValue);
     }
     return !v.isFalse();
 }
 
-/* static */ bool
-GlobalObject::warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag,
-                            unsigned errorNumber)
-{
-    Rooted<GlobalObject*> global(cx, &obj->global());
-    HeapSlot& v = global->getSlotRef(WARNED_ONCE_FLAGS);
-    MOZ_ASSERT_IF(!v.isUndefined(), v.toInt32());
-    int32_t flags = v.isUndefined() ? 0 : v.toInt32();
-    if (!(flags & flag)) {
-        if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
-                                               errorNumber))
-        {
-            return false;
-        }
-        if (v.isUndefined())
-            v.init(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
-        else
-            v.set(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
-    }
-    return true;
-}
-
 /* static */ JSFunction*
 GlobalObject::createConstructor(JSContext* cx, Native ctor, JSAtom* nameArg, unsigned length,
                                 gc::AllocKind kind, const JSJitInfo* jitInfo)
 {
     RootedAtom name(cx, nameArg);
     JSFunction* fun = NewNativeConstructor(cx, ctor, length, name, kind);
     if (!fun)
         return nullptr;
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -95,17 +95,16 @@ class GlobalObject : public NativeObject
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REGEXP_STATICS,
-        WARNED_ONCE_FLAGS,
         RUNTIME_CODEGEN_ENABLED,
         DEBUGGERS,
         INTRINSICS,
         FOR_OF_PIC_CHAIN,
         MODULE_RESOLVE_HOOK,
         WINDOW_PROXY,
 
         /* Total reserved-slot count for global objects. */
@@ -115,28 +114,16 @@ class GlobalObject : public NativeObject
     /*
      * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and
      * we won't expose GlobalObject, so just assert that the two values are
      * synchronized.
      */
     static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                   "global object slot counts are inconsistent");
 
-    enum WarnOnceFlag : int32_t {
-        WARN_WATCH_DEPRECATED                   = 1 << 0,
-    };
-
-    // Emit the specified warning if the given slot in |obj|'s global isn't
-    // true, then set the slot to true.  Thus calling this method warns once
-    // for each global object it's called on, and every other call does
-    // nothing.
-    static bool
-    warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag, unsigned errorNumber);
-
-
   public:
     LexicalEnvironmentObject& lexicalEnvironment() const;
     GlobalScope& emptyGlobalScope() const;
 
     void setThrowTypeError(JSFunction* fun) {
         MOZ_ASSERT(getSlotRef(THROWTYPEERROR).isUndefined());
         setSlot(THROWTYPEERROR, ObjectValue(*fun));
     }
@@ -746,25 +733,16 @@ class GlobalObject : public NativeObject
         const Value v = getReservedSlot(THROWTYPEERROR);
         MOZ_ASSERT(v.isObject(),
                    "attempting to access [[ThrowTypeError]] too early");
         return &v.toObject();
     }
 
     static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global);
 
-    // Warn about use of the deprecated watch/unwatch functions in the global
-    // in which |obj| was created, if no prior warning was given.
-    static bool warnOnceAboutWatch(JSContext* cx, HandleObject obj) {
-        // Temporarily disabled until we've provided a watch/unwatch workaround for
-        // debuggers like Firebug (bug 934669).
-        //return warnOnceAbout(cx, obj, WARN_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED);
-        return true;
-    }
-
     static bool getOrCreateEval(JSContext* cx, Handle<GlobalObject*> global,
                                 MutableHandleObject eval);
 
     // Infallibly test whether the given value is the eval function for this global.
     bool valueIsEval(const Value& val);
 
     // Implemented in jsiter.cpp.
     static bool initIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
--- a/mobile/android/config/mozconfigs/android-api-15-frontend/nightly
+++ b/mobile/android/config/mozconfigs/android-api-15-frontend/nightly
@@ -2,16 +2,17 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_INSTALLER=0
 MOZ_AUTOMATION_L10N_CHECK=0
 MOZ_AUTOMATION_PACKAGE=0
 MOZ_AUTOMATION_PACKAGE_TESTS=0
 MOZ_AUTOMATION_UPDATE_PACKAGING=0
 MOZ_AUTOMATION_UPLOAD=0
 MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 ac_add_options --with-gradle="$topsrcdir/gradle-dist/bin/gradle"
 export GRADLE_MAVEN_REPOSITORIES="file://$topsrcdir/jcenter","file://$topsrcdir/google"
--- a/mobile/android/config/mozconfigs/android-api-15-gradle-dependencies/nightly
+++ b/mobile/android/config/mozconfigs/android-api-15-gradle-dependencies/nightly
@@ -2,16 +2,17 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_INSTALLER=0
 MOZ_AUTOMATION_L10N_CHECK=0
 MOZ_AUTOMATION_PACKAGE=0
 MOZ_AUTOMATION_PACKAGE_TESTS=0
 MOZ_AUTOMATION_UPDATE_PACKAGING=0
 MOZ_AUTOMATION_UPLOAD=0
 MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # We want to download Gradle.
 ac_add_options --with-gradle
--- a/mobile/android/config/mozconfigs/android-api-15-gradle/nightly-artifact
+++ b/mobile/android/config/mozconfigs/android-api-15-gradle/nightly-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 . "$topsrcdir/mobile/android/config/mozconfigs/android-api-15-gradle/nightly"
 
--- a/mobile/android/config/mozconfigs/android-api-15/debug-artifact
+++ b/mobile/android/config/mozconfigs/android-api-15/debug-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Global options
 ac_add_options --enable-debug
--- a/mobile/android/config/mozconfigs/android-api-15/nightly-artifact
+++ b/mobile/android/config/mozconfigs/android-api-15/nightly-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 . "$topsrcdir/mobile/android/config/mozconfigs/android-api-15/nightly"
 
--- a/mobile/android/config/mozconfigs/android-x86/nightly-artifact
+++ b/mobile/android/config/mozconfigs/android-x86/nightly-artifact
@@ -1,10 +1,9 @@
-MOZ_AUTOMATION_BUILD_SYMBOLS=0
-MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/build/mozconfig.artifact.automation"
 
 NO_CACHE=1
 NO_NDK=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 . "$topsrcdir/mobile/android/config/mozconfigs/android-x86/nightly"
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -894,16 +894,17 @@ pref("gfx.webrender.enabled", false);
 #endif
 #ifdef XP_WIN
 pref("gfx.webrender.force-angle", true);
 #endif
 
 pref("gfx.webrender.highlight-painted-layers", false);
 pref("gfx.webrender.layers-free", false);
 pref("gfx.webrender.profiler.enabled", false);
+pref("gfx.webrender.blob-images", false);
 
 // Whether webrender should be used as much as possible.
 pref("gfx.webrendest.enabled", false);
 
 pref("accessibility.browsewithcaret", false);
 pref("accessibility.warn_on_browsewithcaret", true);
 
 pref("accessibility.browsewithcaret_shortcut.enabled", true);
--- a/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
@@ -80,18 +80,16 @@ MapGIOResult(gint code)
   G_IO_ERROR_BUSY,
   G_IO_ERROR_WOULD_MERGE,
   G_IO_ERROR_TOO_MANY_OPEN_FILES
 */
     // Make GCC happy
     default:
       return NS_ERROR_FAILURE;
   }
-
-  return NS_ERROR_FAILURE;
 }
 
 static nsresult
 MapGIOResult(GError *result)
 {
   if (!result)
     return NS_OK;
   return MapGIOResult(result->code);
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/action/package_generated_sources.py
@@ -0,0 +1,30 @@
+# 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/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import argparse
+import json
+import os.path
+import sys
+
+import buildconfig
+from mozpack.archive import create_tar_gz_from_files
+from mozbuild.generated_sources import get_generated_sources
+
+
+def main(argv):
+    parser = argparse.ArgumentParser(
+        description='Produce archive of generated sources')
+    parser.add_argument('outputfile', help='File to write output to')
+    args = parser.parse_args(argv)
+
+
+    files = dict(get_generated_sources())
+    with open(args.outputfile, 'wb') as fh:
+        create_tar_gz_from_files(fh, files, compresslevel=5)
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/generated_sources.py
@@ -0,0 +1,73 @@
+# 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/.
+
+import hashlib
+import json
+import os
+
+from mozpack.files import FileFinder
+import mozpack.path as mozpath
+
+
+def sha512_digest(data):
+    '''
+    Generate the SHA-512 digest of `data` and return it as a hex string.
+    '''
+    return hashlib.sha512(data).hexdigest()
+
+
+def get_filename_with_digest(name, contents):
+    '''
+    Return the filename that will be used to store the generated file
+    in the S3 bucket, consisting of the SHA-512 digest of `contents`
+    joined with the relative path `name`.
+    '''
+    digest = sha512_digest(contents)
+    return mozpath.join(digest, name)
+
+
+def get_generated_sources():
+    '''
+    Yield tuples of `(objdir-rel-path, file)` for generated source files
+    in this objdir, where `file` is either an absolute path to the file or
+    a `mozpack.File` instance.
+    '''
+    import buildconfig
+
+    # First, get the list of generated sources produced by the build backend.
+    gen_sources = os.path.join(buildconfig.topobjdir, 'generated-sources.json')
+    with open(gen_sources, 'rb') as f:
+        data = json.load(f)
+    for f in data['sources']:
+        yield f, mozpath.join(buildconfig.topobjdir, f)
+    # Next, return all the files in $objdir/ipc/ipdl/_ipdlheaders.
+    base = 'ipc/ipdl/_ipdlheaders'
+    finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
+    for p, f in finder.find('**/*.h'):
+        yield mozpath.join(base, p), f
+    # Next, return any Rust source files that were generated into the Rust
+    # object directory.
+    rust_build_kind = 'debug' if buildconfig.substs.get('MOZ_DEBUG_RUST') else 'release'
+    base = mozpath.join('toolkit/library',
+                        buildconfig.substs['RUST_TARGET'],
+                        rust_build_kind,
+                        'build')
+    finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
+    for p, f in finder.find('**/*.rs'):
+        yield mozpath.join(base, p), f
+
+
+def get_s3_region_and_bucket():
+    '''
+    Return a tuple of (region, bucket) giving the AWS region and S3
+    bucket to which generated sources should be uploaded.
+    '''
+    region = 'us-west-2'
+    level = os.environ.get('MOZ_SCM_LEVEL', '1')
+    bucket = {
+        '1': 'gecko-generated-sources-l1',
+        '2': 'gecko-generated-sources-l2',
+        '3': 'gecko-generated-sources',
+    }[level]
+    return (region, bucket)
--- a/security/sandbox/linux/moz.build
+++ b/security/sandbox/linux/moz.build
@@ -77,19 +77,20 @@ if CONFIG['MOZ_GMP_SANDBOX']:
         'SandboxOpenedFiles.cpp',
     ]
 
 # This copy of SafeSPrintf doesn't need to avoid the Chromium logging
 # dependency like the one in libxul does, but this way the behavior is
 # consistent.  See also the comment in SandboxLogging.h.
 SOURCES['../chromium/base/strings/safe_sprintf.cc'].flags += ['-DNDEBUG']
 
-# Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
 if CONFIG['CLANG_CXX']:
+    # Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
     SOURCES['../chromium/base/third_party/icu/icu_utf.cc'].flags += ['-Wno-implicit-fallthrough']
+    SOURCES['../chromium/sandbox/linux/seccomp-bpf/trap.cc'].flags += ['-Wno-unreachable-code-return']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-shadow']
     SOURCES['../chromium/sandbox/linux/services/syscall_wrappers.cc'].flags += [
         '-Wno-empty-body',
     ]
 
 # gcc lto likes to put the top level asm in syscall.cc in a different partition
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/upload-generated-sources/kind.yml
@@ -0,0 +1,35 @@
+# 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/.
+
+loader: taskgraph.loader.single_dep:loader
+
+transforms:
+   - taskgraph.transforms.upload_generated_sources:transforms
+   - taskgraph.transforms.job:transforms
+   - taskgraph.transforms.task:transforms
+
+kind-dependencies:
+  - build
+
+only-for-attributes:
+  - nightly
+
+job-template:
+  description: Upload generated source files from build
+  attributes:
+    nightly: true
+  worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
+  treeherder:
+    symbol: Ugs
+    kind: build
+  worker:
+     docker-image: {in-tree: "lint"}
+     max-run-time: 600
+  run:
+    using: run-task
+    command: >
+            cd /home/worker/checkouts/gecko &&
+            ./mach python build/upload_generated_sources.py ${ARTIFACT_URL}
+  scopes:
+      - secrets:get:project/releng/gecko/build/level-{level}/gecko-generated-sources-upload
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -62,16 +62,21 @@ a source checkout, it is still possible 
 often they do not.
 
 upload-symbols
 --------------
 
 Upload-symbols tasks run after builds and upload the symbols files generated by
 build tasks to Socorro for later use in crash analysis.
 
+upload-generated-sources
+--------------
+
+Upload-generated-sources tasks run after builds and upload source files that were generated as part of the build process to an s3 bucket for later use in links from crash reports or when debugging shipped builds.
+
 valgrind
 --------
 
 Valgrind tasks produce builds instrumented by valgrind.
 
 static-analysis
 ---------------
 
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/transforms/upload_generated_sources.py
@@ -0,0 +1,43 @@
+# 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/.
+"""
+Transform the upload-generated-files task description template,
+  taskcluster/ci/upload-generated-sources/kind.yml
+into an actual task description.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+from taskgraph.transforms.base import TransformSequence
+from taskgraph.util.taskcluster import get_artifact_url
+
+
+transforms = TransformSequence()
+
+
+@transforms.add
+def add_task_info(config, jobs):
+    for job in jobs:
+        dep_task = job['dependent-task']
+        del job['dependent-task']
+
+        # Add a dependency on the build task.
+        job['dependencies'] = {'build': dep_task.label}
+        # Label the job to match the build task it's uploading from.
+        job['label'] = dep_task.label.replace("build-", "upload-generated-sources-")
+        # Copy over some bits of metdata from the build task.
+        dep_th = dep_task.task['extra']['treeherder']
+        job.setdefault('attributes', {})
+        job['attributes']['build_platform'] = dep_task.attributes.get('build_platform')
+        plat = '{}/{}'.format(dep_th['machine']['platform'], dep_task.attributes.get('build_type'))
+        job['treeherder']['platform'] = plat
+        job['treeherder']['tier'] = dep_th['tier']
+        # Add an environment variable pointing at the artifact from the build.
+        artifact_url = get_artifact_url('<build>',
+                                        'public/build/target.generated-files.tar.gz')
+        job['worker'].setdefault('env', {})['ARTIFACT_URL'] = {
+            'task-reference': artifact_url
+        }
+
+        yield job
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -405906,16 +405906,22 @@
     ]
    ],
    "webdriver/tests/get_window_rect.py": [
     [
      "/webdriver/tests/get_window_rect.py",
      {}
     ]
    ],
+   "webdriver/tests/minimize_window.py": [
+    [
+     "/webdriver/tests/minimize_window.py",
+     {}
+    ]
+   ],
    "webdriver/tests/navigation/current_url.py": [
     [
      "/webdriver/tests/navigation/current_url.py",
      {}
     ]
    ],
    "webdriver/tests/navigation/get_title.py": [
     [
@@ -626271,16 +626277,20 @@
   "webdriver/tests/get_window_rect.py": [
    "2d4c13edc4e659af864750d0341c06ff969a687f",
    "wdspec"
   ],
   "webdriver/tests/interface.html": [
    "d783d0dd370f58b264ef238d8da5cd8601dc3c7f",
    "testharness"
   ],
+  "webdriver/tests/minimize_window.py": [
+   "99fa058dd78c2f3a2d2c34dc7096edb9ca12ac4f",
+   "wdspec"
+  ],
   "webdriver/tests/navigation/current_url.py": [
    "cec2987258d9c807a247da9e0216b3af1f171484",
    "wdspec"
   ],
   "webdriver/tests/navigation/get_title.py": [
    "16bdf435f3d05ceca30394dee6d82adcb64c997b",
    "wdspec"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webdriver/tests/minimize_window.py
@@ -0,0 +1,56 @@
+from tests.support.inline import inline
+from tests.support.asserts import assert_error, assert_success
+
+alert_doc = inline("<script>window.alert()</script>")
+
+# 10.7.4 Minimize Window
+def test_minimize_no_browsing_context(session, create_window):
+    # Step 1
+    session.window_handle = create_window()
+    session.close()
+    result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
+    assert_error(result, "no such window")
+
+
+def test_handle_user_prompt(session):
+    # Step 2
+    session.url = alert_doc
+    result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
+    assert_error(result, "unexpected alert open")
+
+
+def test_minimize(session):
+    before_size = session.window.size
+    assert session.window.state == "normal"
+
+    # step 4
+    result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
+    assert_success(result)
+
+    assert session.window.state == "minimized"
+
+
+def test_payload(session):
+    before_size = session.window.size
+    assert session.window.state == "normal"
+
+    result = session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
+
+    # step 5
+    assert result.status == 200
+    assert isinstance(result.body["value"], dict)
+
+    resp = result.body["value"]
+    assert "width" in resp
+    assert "height" in resp
+    assert "x" in resp
+    assert "y" in resp
+    assert "state" in resp
+    assert isinstance(resp["width"], (int, float))
+    assert isinstance(resp["height"], (int, float))
+    assert isinstance(resp["x"], (int, float))
+    assert isinstance(resp["y"], (int, float))
+    assert isinstance(resp["state"], basestring)
+
+    assert session.window.state == "minimized"
+
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -478,116 +478,129 @@ this.ExtensionData = class {
     // a *.domain.com to specific-host.domain.com that's actually a
     // drop in permissions but the simple test below will cause a prompt.
     return {
       origins: newPermissions.origins.filter(perm => !oldPermissions.origins.includes(perm)),
       permissions: newPermissions.permissions.filter(perm => !oldPermissions.permissions.includes(perm)),
     };
   }
 
-  parseManifest() {
-    return Promise.all([
+  async parseManifest() {
+    let [manifest] = await Promise.all([
       this.readJSON("manifest.json"),
       Management.lazyInit(),
-    ]).then(([manifest]) => {
-      this.manifest = manifest;
-      this.rawManifest = manifest;
+    ]);
+
+    this.manifest = manifest;
+    this.rawManifest = manifest;
+
+    if (manifest && manifest.default_locale) {
+      await this.initLocale();
+    }
+
+    let context = {
+      url: this.baseURI && this.baseURI.spec,
+
+      principal: this.principal,
 
-      if (manifest && manifest.default_locale) {
-        return this.initLocale();
+      logError: error => {
+        this.manifestWarning(error);
+      },
+
+      preprocessors: {},
+    };
+
+    if (this.manifest.theme) {
+      let invalidProps = validateThemeManifest(Object.getOwnPropertyNames(this.manifest));
+
+      if (invalidProps.length) {
+        let message = `Themes defined in the manifest may only contain static resources. ` +
+          `If you would like to use additional properties, please use the "theme" permission instead. ` +
+          `(the invalid properties found are: ${invalidProps})`;
+        this.manifestError(message);
       }
-    }).then(() => {
-      let context = {
-        url: this.baseURI && this.baseURI.spec,
-
-        principal: this.principal,
+    }
 
-        logError: error => {
-          this.manifestWarning(error);
-        },
+    if (this.localeData) {
+      context.preprocessors.localize = (value, context) => this.localize(value);
+    }
 
-        preprocessors: {},
-      };
+    let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
+    if (normalized.error) {
+      this.manifestError(normalized.error);
+      return null;
+    }
+
+    manifest = normalized.value;
 
-      if (this.manifest.theme) {
-        let invalidProps = validateThemeManifest(Object.getOwnPropertyNames(this.manifest));
+    let id;
+    try {
+      if (manifest.applications.gecko.id) {
+        id = manifest.applications.gecko.id;
+      }
+    } catch (e) {
+      // Errors are handled by the type checks above.
+    }
 
-        if (invalidProps.length) {
-          let message = `Themes defined in the manifest may only contain static resources. ` +
-            `If you would like to use additional properties, please use the "theme" permission instead. ` +
-            `(the invalid properties found are: ${invalidProps})`;
-          this.manifestError(message);
+    if (!this.id) {
+      this.id = id;
+    }
+
+    let apiNames = new Set();
+    let dependencies = new Set();
+    let originPermissions = new Set();
+    let permissions = new Set();
+
+    for (let perm of manifest.permissions) {
+      if (perm === "geckoProfiler") {
+        const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
+        if (!acceptedExtensions.split(",").includes(id)) {
+          this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
+          continue;
         }
       }
 
-      if (this.localeData) {
-        context.preprocessors.localize = (value, context) => this.localize(value);
-      }
-
-      let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
-      if (normalized.error) {
-        this.manifestError(normalized.error);
-        return null;
-      }
+      let type = classifyPermission(perm);
+      if (type.origin) {
+        let matcher = new MatchPattern(perm, {ignorePath: true});
 
-      let manifest = normalized.value;
-
-      let id;
-      try {
-        if (manifest.applications.gecko.id) {
-          id = manifest.applications.gecko.id;
-        }
-      } catch (e) {
-        // Errors are handled by the type checks above.
+        perm = matcher.pattern;
+        originPermissions.add(perm);
+      } else if (type.api) {
+        apiNames.add(type.api);
       }
 
-      let apiNames = new Set();
-      let dependencies = new Set();
-      let hostPermissions = new Set();
-      let permissions = new Set();
+      permissions.add(perm);
+    }
 
-      for (let perm of manifest.permissions) {
-        if (perm === "geckoProfiler") {
-          const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
-          if (!acceptedExtensions.split(",").includes(id)) {
-            this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
-            continue;
-          }
-        }
+    if (this.id) {
+      // An extension always gets permission to its own url.
+      let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
+      originPermissions.add(matcher.pattern);
 
-        let type = classifyPermission(perm);
-        if (type.origin) {
-          let matcher = new MatchPattern(perm, {ignorePath: true});
-
-          perm = matcher.pattern;
-          hostPermissions.add(perm);
-        } else if (type.api) {
-          apiNames.add(type.api);
-        }
-
+      // Apply optional permissions
+      let perms = await ExtensionPermissions.get(this);
+      for (let perm of perms.permissions) {
         permissions.add(perm);
       }
-
-      // An extension always gets permission to its own url.
-      if (this.id) {
-        let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
-        hostPermissions.add(matcher.pattern);
+      for (let origin of perms.origins) {
+        originPermissions.add(origin);
       }
+    }
 
-      for (let api of apiNames) {
-        dependencies.add(`${api}@experiments.addons.mozilla.org`);
-      }
+    for (let api of apiNames) {
+      dependencies.add(`${api}@experiments.addons.mozilla.org`);
+    }
 
-      // Normalize all patterns to contain a single leading /
-      let webAccessibleResources = (manifest.web_accessible_resources || [])
-          .map(path => path.replace(/^\/*/, "/"));
+    // Normalize all patterns to contain a single leading /
+    let webAccessibleResources = (manifest.web_accessible_resources || [])
+        .map(path => path.replace(/^\/*/, "/"));
 
-      return {apiNames, dependencies, hostPermissions, id, manifest, permissions,
-              webAccessibleResources};
-    });
+    return {apiNames, dependencies, originPermissions, id, manifest, permissions,
+            webAccessibleResources};
   }
 
   // Reads the extension's |manifest.json| file, and stores its
   // parsed contents in |this.manifest|.
   async loadManifest() {
     let [manifestData] = await Promise.all([
       this.parseManifest(),
       Management.lazyInit(),
@@ -603,17 +616,17 @@ this.ExtensionData = class {
     }
 
     this.manifest = manifestData.manifest;
     this.apiNames = manifestData.apiNames;
     this.dependencies = manifestData.dependencies;
     this.permissions = manifestData.permissions;
 
     this.webAccessibleResources = manifestData.webAccessibleResources.map(res => new MatchGlob(res));
-    this.whiteListedHosts = new MatchPatternSet(manifestData.hostPermissions);
+    this.whiteListedHosts = new MatchPatternSet(manifestData.originPermissions);
 
     return this.manifest;
   }
 
   localizeMessage(...args) {
     return this.localeData.localizeMessage(...args);
   }
 
@@ -816,17 +829,18 @@ this.Extension = class extends Extension
       throw new Error("Out-of-process WebExtensions are not supported with multiple child processes");
     }
 
     // This is filled in the first time an extension child is created.
     this.parentMessageManager = null;
 
     this.id = addonData.id;
     this.version = addonData.version;
-    this.baseURI = Services.io.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
+    this.baseURL = this.getURL("");
+    this.baseURI = Services.io.newURI(this.baseURL).QueryInterface(Ci.nsIURL);
     this.principal = this.createPrincipal();
     this.views = new Set();
     this._backgroundPageFrameLoader = null;
 
     this.onStartup = null;
 
     this.hasShutdown = false;
     this.onShutdown = new Set();
@@ -844,38 +858,42 @@ this.Extension = class extends Extension
     this.on("add-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.add(perm);
       }
 
       if (permissions.origins.length > 0) {
         let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
 
-        this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
+        this.whiteListedHosts = new MatchPatternSet(new Set([...patterns, ...permissions.origins]),
                                                     {ignorePath: true});
       }
 
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
+
+      this.cachePermissions();
     });
 
     this.on("remove-permissions", (ignoreEvent, permissions) => {
       for (let perm of permissions.permissions) {
         this.permissions.delete(perm);
       }
 
       let origins = permissions.origins.map(
         origin => new MatchPattern(origin, {ignorePath: true}).pattern);
 
       this.whiteListedHosts = new MatchPatternSet(
         this.whiteListedHosts.patterns
             .filter(host => !origins.includes(host.pattern)));
 
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
+
+      this.cachePermissions();
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
   static getBootstrapScope(id, file) {
     return new BootstrapScope();
   }
 
@@ -938,17 +956,27 @@ this.Extension = class extends Extension
     return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
   }
 
   // Checks that the given URL is a child of our baseURI.
   isExtensionURL(url) {
     let uri = Services.io.newURI(url);
 
     let common = this.baseURI.getCommonBaseSpec(uri);
-    return common == this.baseURI.spec;
+    return common == this.baseURL;
+  }
+
+  checkLoadURL(url, options = {}) {
+    // As an optimization, f the URL starts with the extension's base URL,
+    // don't do any further checks. It's always allowed to load it.
+    if (url.startsWith(this.baseURL)) {
+      return true;
+    }
+
+    return ExtensionUtils.checkLoadURL(url, this.principal, options);
   }
 
   async promiseLocales(locale) {
     let locales = await StartupCache.locales
       .get([this.id, "@@all_locales"], () => this._promiseLocaleMap());
 
     return this._setupLocaleData(locales);
   }
@@ -956,19 +984,30 @@ this.Extension = class extends Extension
   readLocaleFile(locale) {
     return StartupCache.locales.get([this.id, this.version, locale],
                                     () => super.readLocaleFile(locale))
       .then(result => {
         this.localeData.messages.set(locale, result);
       });
   }
 
+  get manifestCacheKey() {
+    return [this.id, this.version, Services.locale.getAppLocaleAsLangTag()];
+  }
+
   parseManifest() {
-    return StartupCache.manifests.get([this.id, this.version, Services.locale.getAppLocaleAsLangTag()],
-                                      () => super.parseManifest());
+    return StartupCache.manifests.get(this.manifestCacheKey, () => super.parseManifest());
+  }
+
+  async cachePermissions() {
+    let manifestData = await this.parseManifest();
+
+    manifestData.originPermissions = this.whiteListedHosts.patterns.map(pat => pat.pattern);
+    manifestData.permissions = this.permissions;
+    return StartupCache.manifests.set(this.manifestCacheKey, manifestData);
   }
 
   async loadManifest() {
     let manifest = await super.loadManifest();
 
     if (this.errors.length) {
       return Promise.reject({errors: this.errors});
     }
@@ -1166,46 +1205,32 @@ this.Extension = class extends Extension
       // so during upgrades and add-on restarts, startup() gets called
       // before the last shutdown has completed, and this fails when
       // there's another active add-on with the same ID.
       this.policy.active = true;
     }
 
     TelemetryStopwatch.start("WEBEXT_EXTENSION_STARTUP_MS", this);
     try {
-      let [perms] = await Promise.all([
-        ExtensionPermissions.get(this),
-        this.loadManifest(),
-      ]);
+      await this.loadManifest();
 
       if (!this.hasShutdown) {
         await this.initLocale();
       }
 
       if (this.errors.length) {
         return Promise.reject({errors: this.errors});
       }
 
       if (this.hasShutdown) {
         return;
       }
 
       GlobalManager.init(this);
 
-      // Apply optional permissions
-      for (let perm of perms.permissions) {
-        this.permissions.add(perm);
-      }
-      if (perms.origins.length > 0) {
-        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
-
-        this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
-                                                    {ignorePath: true});
-      }
-
       this.policy.active = false;
       this.policy = processScript.initExtension(this);
 
       this.updatePermissions(this.startupReason);
 
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -333,16 +333,17 @@ class Messenger {
     let promise = this._sendMessage(messageManager, "Extension:Message", holder, recipient)
       .catch(error => {
         if (error.result == MessageChannel.RESULT_NO_HANDLER) {
           return Promise.reject({message: "Could not establish connection. Receiving end does not exist."});
         } else if (error.result != MessageChannel.RESULT_NO_RESPONSE) {
           return Promise.reject({message: error.message});
         }
       });
+    holder = null;
 
     return this.context.wrapPromise(promise, responseCallback);
   }
 
   sendNativeMessage(messageManager, msg, recipient, responseCallback) {
     msg = NativeApp.encodeMessage(this.context, msg);
     return this.sendMessage(messageManager, msg, recipient, responseCallback);
   }
@@ -369,22 +370,26 @@ class Messenger {
           let promise = new Promise(resolve => {
             sendResponse = value => {
               resolve(value);
               response = promise;
             };
           });
 
           let message = holder.deserialize(this.context.cloneScope);
+          holder = null;
+
           sender = Cu.cloneInto(sender, this.context.cloneScope);
           sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
 
           // Note: We intentionally do not use runSafe here so that any
           // errors are propagated to the message sender.
           let result = fire.raw(message, sender, sendResponse);
+          message = null;
+
           if (result instanceof this.context.cloneScope.Promise) {
             return result;
           } else if (result === true) {
             return promise;
           }
           return response;
         },
       };
@@ -506,16 +511,17 @@ class BrowserExtensionContent extends Ev
     this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts, {ignorePath: true});
     this.permissions = data.permissions;
     this.optionalPermissions = data.optionalPermissions;
     this.principal = data.principal;
 
     this.localeData = new LocaleData(data.localeData);
 
     this.manifest = data.manifest;
+    this.baseURL = data.baseURL;
     this.baseURI = Services.io.newURI(data.baseURL);
 
     // Only used in addon processes.
     this.views = new Set();
 
     // Only used for devtools views.
     this.devtoolsViews = new Set();
 
@@ -808,33 +814,29 @@ class ChildAPIManager {
    * Calls a function in the parent process and returns its result
    * asynchronously.
    *
    * @param {string} path The full name of the method, e.g. "tabs.create".
    * @param {Array} args The parameters for the function.
    * @param {function(*)} [callback] The callback to be called when the function
    *     completes.
    * @param {object} [options] Extra options.
-   * @param {boolean} [options.noClone = false] If true, do not clone
-   *     the arguments into an extension sandbox before calling the API
-   *     method.
    * @returns {Promise|undefined} Must be void if `callback` is set, and a
    *     promise otherwise. The promise is resolved when the function completes.
    */
   callParentAsyncFunction(path, args, callback, options = {}) {
     let callId = getUniqueId();
     let deferred = PromiseUtils.defer();
     this.callPromises.set(callId, deferred);
 
     this.messageManager.sendAsyncMessage("API:Call", {
       childId: this.id,
       callId,
       path,
       args,
-      noClone: options.noClone || false,
     });
 
     return this.context.wrapPromise(deferred.promise, callback);
   }
 
   /**
    * Create a proxy for an event in the parent process. The returned event
    * object shares its internal state with other instances. For instance, if
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -37,21 +37,20 @@ const global = this;
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
   defineLazyGetter,
+  filterStack,
   getConsole,
   getInnerWindowID,
   getUniqueId,
-  runSafeSync,
-  runSafeSyncWithoutClone,
   instanceOf,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyGetter(this, "console", getConsole);
 
 var ExtensionCommon;
 
 /**
@@ -245,58 +244,65 @@ class BaseContext {
   get cloneScope() {
     throw new Error("Not implemented");
   }
 
   get principal() {
     throw new Error("Not implemented");
   }
 
-  runSafe(...args) {
+  runSafe(callback, ...args) {
+    return this.applySafe(callback, args);
+  }
+
+  runSafeWithoutClone(callback, ...args) {
+    return this.applySafeWithoutClone(callback, args);
+  }
+
+  applySafe(callback, args) {
     if (this.unloaded) {
       Cu.reportError("context.runSafe called after context unloaded");
     } else if (!this.active) {
       Cu.reportError("context.runSafe called while context is inactive");
     } else {
-      return runSafeSync(this, ...args);
+      try {
+        let {cloneScope} = this;
+        args = args.map(arg => Cu.cloneInto(arg, cloneScope));
+      } catch (e) {
+        Cu.reportError(e);
+        dump(`runSafe failure: cloning into ${this.cloneScope}: ${e}\n\n${filterStack(Error())}`);
+      }
+
+      return this.applySafeWithoutClone(callback, args);
     }
   }
 
-  runSafeWithoutClone(...args) {
+  applySafeWithoutClone(callback, args) {
     if (this.unloaded) {
       Cu.reportError("context.runSafeWithoutClone called after context unloaded");
     } else if (!this.active) {
       Cu.reportError("context.runSafeWithoutClone called while context is inactive");
     } else {
-      return runSafeSyncWithoutClone(...args);
+      try {
+        return Reflect.apply(callback, null, args);
+      } catch (e) {
+        dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
+        Cu.reportError(e);
+      }
     }
   }
 
   checkLoadURL(url, options = {}) {
-    let ssm = Services.scriptSecurityManager;
-
-    let flags = ssm.STANDARD;
-    if (!options.allowScript) {
-      flags |= ssm.DISALLOW_SCRIPT;
-    }
-    if (!options.allowInheritsPrincipal) {
-      flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
-    }
-    if (options.dontReportErrors) {
-      flags |= ssm.DONT_REPORT_ERRORS;
+    // As an optimization, f the URL starts with the extension's base URL,
+    // don't do any further checks. It's always allowed to load it.
+    if (url.startsWith(this.extension.baseURL)) {
+      return true;
     }
 
-    try {
-      ssm.checkLoadURIWithPrincipal(this.principal,
-                                    Services.io.newURI(url),
-                                    flags);
-    } catch (e) {
-      return false;
-    }
-    return true;
+    return ExtensionUtils.checkLoadURL(url, this.principal, options);
   }
 
   /**
    * Safely call JSON.stringify() on an object that comes from an
    * extension.
    *
    * @param {array<any>} args Arguments for JSON.stringify()
    * @returns {string} The stringified representation of obj
@@ -425,71 +431,71 @@ class BaseContext {
    *     `callback` function or resolving the wrapped promise.
    *
    * @param {function} [callback] The callback function to wrap
    *
    * @returns {Promise|undefined} If callback is null, a promise object
    *     belonging to the target scope. Otherwise, undefined.
    */
   wrapPromise(promise, callback = null) {
-    let runSafe = this.runSafe.bind(this);
-    if (promise instanceof this.cloneScope.Promise) {
-      runSafe = this.runSafeWithoutClone.bind(this);
+    let applySafe = this.applySafe.bind(this);
+    if (Cu.getGlobalForObject(promise) === this.cloneScope) {
+      applySafe = this.applySafeWithoutClone.bind(this);
     }
 
     if (callback) {
       promise.then(
         args => {
           if (this.unloaded) {
             dump(`Promise resolved after context unloaded\n`);
           } else if (!this.active) {
             dump(`Promise resolved while context is inactive\n`);
           } else if (args instanceof NoCloneSpreadArgs) {
-            this.runSafeWithoutClone(callback, ...args.unwrappedValues);
+            this.applySafeWithoutClone(callback, args.unwrappedValues);
           } else if (args instanceof SpreadArgs) {
-            runSafe(callback, ...args);
+            applySafe(callback, args);
           } else {
-            runSafe(callback, args);
+            applySafe(callback, [args]);
           }
         },
         error => {
           this.withLastError(error, () => {
             if (this.unloaded) {
               dump(`Promise rejected after context unloaded\n`);
             } else if (!this.active) {
               dump(`Promise rejected while context is inactive\n`);
             } else {
-              this.runSafeWithoutClone(callback);
+              this.applySafeWithoutClone(callback, []);
             }
           });
         });
     } else {
       return new this.cloneScope.Promise((resolve, reject) => {
         promise.then(
           value => {
             if (this.unloaded) {
               dump(`Promise resolved after context unloaded\n`);
             } else if (!this.active) {
               dump(`Promise resolved while context is inactive\n`);
             } else if (value instanceof NoCloneSpreadArgs) {
               let values = value.unwrappedValues;
-              this.runSafeWithoutClone(resolve, values.length == 1 ? values[0] : values);
+              this.applySafeWithoutClone(resolve, values.length == 1 ? [values[0]] : [values]);
             } else if (value instanceof SpreadArgs) {
-              runSafe(resolve, value.length == 1 ? value[0] : value);
+              applySafe(resolve, value.length == 1 ? value : [value]);
             } else {
-              runSafe(resolve, value);
+              applySafe(resolve, [value]);
             }
           },
           value => {
             if (this.unloaded) {
               dump(`Promise rejected after context unloaded: ${value && value.message}\n`);
             } else if (!this.active) {
               dump(`Promise rejected while context is inactive: ${value && value.message}\n`);
             } else {
-              this.runSafeWithoutClone(reject, this.normalizeError(value));
+              this.applySafeWithoutClone(reject, [this.normalizeError(value)]);
             }
           });
       });
     }
   }
 
   unload() {
     this.unloaded = true;
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -40,16 +40,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   BaseContext,
   CanOfAPIs,
   SchemaAPIManager,
   SpreadArgs,
 } = ExtensionCommon;
 
 var {
+  DefaultMap,
   DefaultWeakMap,
   ExtensionError,
   MessageManagerProxy,
   defineLazyGetter,
   promiseDocumentLoaded,
   promiseEvent,
   promiseFileContents,
   promiseObserved,
@@ -408,16 +409,22 @@ class ProxyContextParent extends BaseCon
       this.pendingEventBrowser = savedBrowser;
     }
   }
 
   get cloneScope() {
     return this.sandbox;
   }
 
+  runSafe(...args) {
+    // There's no need to clone when calling listeners for a proxied
+    // context.
+    return this.runSafeWithoutClone(...args);
+  }
+
   get xulBrowser() {
     return this.messageManagerProxy.eventTarget;
   }
 
   get parentMessageManager() {
     return this.messageManagerProxy.messageManager;
   }
 
@@ -720,17 +727,17 @@ ParentAPIManager = {
         "API:CallResult",
         Object.assign({
           childId: data.childId,
           callId: data.callId,
         }, result));
     };
 
     try {
-      let args = data.noClone ? data.args : Cu.cloneInto(data.args, context.sandbox);
+      let args = data.args;
       let pendingBrowser = context.pendingEventBrowser;
       let fun = await context.apiCan.asyncFindAPIPath(data.path);
       let result = context.withPendingBrowser(pendingBrowser,
                                               () => fun(...args));
       if (data.callId) {
         result = result || Promise.resolve();
 
         result.then(result => {
@@ -776,17 +783,17 @@ ParentAPIManager = {
         },
         {
           recipient: {childId},
         });
     }
 
     context.listenerProxies.set(data.listenerId, listener);
 
-    let args = Cu.cloneInto(data.args, context.sandbox);
+    let args = data.args;
     let promise = context.apiCan.asyncFindAPIPath(data.path);
 
     // Store pending listener additions so we can be sure they're all
     // fully initialize before we consider extension startup complete.
     if (context.viewType === "background" && context.listenerPromises) {
       const {listenerPromises} = context;
       listenerPromises.add(promise);
       let remove = () => { listenerPromises.delete(promise); };
@@ -1209,44 +1216,47 @@ function extensionNameFromURI(uri) {
   } catch (ex) {
     if (ex.name != "NS_ERROR_XPC_BAD_CONVERT_JS") {
       Cu.reportError("Extension cannot be found in AddonPolicyService.");
     }
   }
   return GlobalManager.getExtension(id).name;
 }
 
-const INTEGER = /^[1-9]\d*$/;
-
 // Manages icon details for toolbar buttons in the |pageAction| and
 // |browserAction| APIs.
 let IconDetails = {
   // WeakMap<Extension -> Map<url-string -> object>>
-  iconCache: new DefaultWeakMap(() => new Map()),
+  iconCache: new DefaultWeakMap(() => new DefaultMap(() => new Map())),
 
   // Normalizes the various acceptable input formats into an object
   // with icon size as key and icon URL as value.
   //
   // If a context is specified (function is called from an extension):
   // Throws an error if an invalid icon size was provided or the
   // extension is not allowed to load the specified resources.
   //
   // If no context is specified, instead of throwing an error, this
   // function simply logs a warning message.
   normalize(details, extension, context = null) {
-    if (!details.imageData && typeof details.path === "string") {
-      let icons = this.iconCache.get(extension);
+    if (!details.imageData && details.path) {
+      // Pick a cache key for the icon paths. If the path is a string,
+      // use it directly. Otherwise, stringify the path object.
+      let key = details.path;
+      if (typeof key !== "string") {
+        key = uneval(key);
+      }
 
-      let baseURI = context ? context.uri : extension.baseURI;
-      let url = baseURI.resolve(details.path);
+      let icons = this.iconCache.get(extension)
+                      .get(context && context.uri.spec);
 
-      let icon = icons.get(url);
+      let icon = icons.get(key);
       if (!icon) {
         icon = this._normalize(details, extension, context);
-        icons.set(url, icon);
+        icons.set(key, icon);
       }
       return icon;
     }
 
     return this._normalize(details, extension, context);
   },
 
   _normalize(details, extension, context = null) {
@@ -1256,54 +1266,47 @@ let IconDetails = {
       let {imageData, path, themeIcons} = details;
 
       if (imageData) {
         if (typeof imageData == "string") {
           imageData = {"19": imageData};
         }
 
         for (let size of Object.keys(imageData)) {
-          if (!INTEGER.test(size)) {
-            throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
-          }
           result[size] = imageData[size];
         }
       }
 
       let baseURI = context ? context.uri : extension.baseURI;
 
       if (path) {
         if (typeof path != "object") {
           path = {"19": path};
         }
 
         for (let size of Object.keys(path)) {
-          if (!INTEGER.test(size)) {
-            throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
-          }
-
           let url = baseURI.resolve(path[size]);
 
           // The Chrome documentation specifies these parameters as
           // relative paths. We currently accept absolute URLs as well,
           // which means we need to check that the extension is allowed
           // to load them. This will throw an error if it's not allowed.
-          this._checkURL(url, extension.principal);
+          this._checkURL(url, extension);
 
           result[size] = url;
         }
       }
 
       if (themeIcons) {
         themeIcons.forEach(({size, light, dark}) => {
           let lightURL = baseURI.resolve(light);
           let darkURL = baseURI.resolve(dark);
 
-          this._checkURL(lightURL, extension.principal);
-          this._checkURL(darkURL, extension.principal);
+          this._checkURL(lightURL, extension);
+          this._checkURL(darkURL, extension);
 
           let defaultURL = result[size];
           result[size] = {
             "default": defaultURL || lightURL, // Fallback to the light url if no default is specified.
             "light": lightURL,
             "dark": darkURL,
           };
         });
@@ -1319,22 +1322,18 @@ let IconDetails = {
       extension.manifestError(`Invalid icon data: ${e}`);
     }
 
     return result;
   },
 
   // Checks if the extension is allowed to load the given URL with the specified principal.
   // This will throw an error if the URL is not allowed.
-  _checkURL(url, principal) {
-    try {
-      Services.scriptSecurityManager.checkLoadURIWithPrincipal(
-        principal, Services.io.newURI(url),
-        Services.scriptSecurityManager.DISALLOW_SCRIPT);
-    } catch (e) {
+  _checkURL(url, extension) {
+    if (!extension.checkLoadURL(url, {allowInheritsPrincipal: true})) {
       throw new ExtensionError(`Illegal URL ${url}`);
     }
   },
 
   // Returns the appropriate icon URL for the given icons object and the
   // screen resolution of the given window.
   getPreferredIcon(icons, extension = null, size = 16) {
     const DEFAULT = "chrome://browser/content/extension.svg";
@@ -1526,16 +1525,23 @@ class CacheStore {
       result = await createFunc(path);
       store.set(key, result);
       StartupCache.save();
     }
 
     return result;
   }
 
+  async set(path, value) {
+    let [store, key] = await this.getStore(path);
+
+    store.set(key, value);
+    StartupCache.save();
+  }
+
   async getAll() {
     let [store] = await this.getStore();
 
     return new Map(store);
   }
 
   async delete(path) {
     let [store, key] = await this.getStore(path);
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -647,17 +647,42 @@ class MessageManagerProxy {
   handleEvent(event) {
     if (event.type == "SwapDocShells") {
       this.removeListeners(this.eventTarget);
       this.addListeners(event.detail);
     }
   }
 }
 
+function checkLoadURL(url, principal, options) {
+  let ssm = Services.scriptSecurityManager;
+
+  let flags = ssm.STANDARD;
+  if (!options.allowScript) {
+    flags |= ssm.DISALLOW_SCRIPT;
+  }
+  if (!options.allowInheritsPrincipal) {
+    flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
+  }
+  if (options.dontReportErrors) {
+    flags |= ssm.DONT_REPORT_ERRORS;
+  }
+
+  try {
+    ssm.checkLoadURIWithPrincipal(principal,
+                                  Services.io.newURI(url),
+                                  flags);
+  } catch (e) {
+    return false;
+  }
+  return true;
+}
+
 this.ExtensionUtils = {
+  checkLoadURL,
   defineLazyGetter,
   flushJarCache,
   getConsole,
   getInnerWindowID,
   getMessageManager,
   getUniqueId,
   filterStack,
   getWinUtils,
--- a/toolkit/components/extensions/MessageChannel.jsm
+++ b/toolkit/components/extensions/MessageChannel.jsm
@@ -517,16 +517,17 @@ this.MessageChannel = {
    */
   sendMessage(target, messageName, data, options = {}) {
     let sender = options.sender || {};
     let recipient = options.recipient || {};
     let responseType = options.responseType || this.RESPONSE_SINGLE;
 
     let channelId = ExtensionUtils.getUniqueId();
     let message = {messageName, channelId, sender, recipient, data, responseType};
+    data = null;
 
     if (responseType == this.RESPONSE_NONE) {
       try {
         target.sendAsyncMessage(MESSAGE_MESSAGE, message);
       } catch (e) {
         // Caller is not expecting a reply, so dump the error to the console.
         Cu.reportError(e);
         return Promise.reject(e);
@@ -556,16 +557,17 @@ this.MessageChannel = {
     };
     deferred.promise.then(cleanup, cleanup);
 
     try {
       target.sendAsyncMessage(MESSAGE_MESSAGE, message);
     } catch (e) {
       deferred.reject(e);
     }
+    message = null;
     return deferred.promise;
   },
 
   _callHandlers(handlers, data) {
     let responseType = data.responseType;
 
     // At least one handler is required for all response types but
     // RESPONSE_ALL.
@@ -590,16 +592,17 @@ this.MessageChannel = {
 
     let responses = handlers.map(handler => {
       try {
         return handler.receiveMessage(data);
       } catch (e) {
         return Promise.reject(e);
       }
     });
+    data = null;
     responses = responses.filter(response => response !== undefined);
 
     switch (responseType) {
       case this.RESPONSE_FIRST:
         if (responses.length == 0) {
           return Promise.reject({result: MessageChannel.RESULT_NO_RESPONSE,
                                  message: "No handler returned a response"});
         }
@@ -629,36 +632,38 @@ this.MessageChannel = {
       handlers.forEach(handler => {
         // The sender expects no reply, so dump any errors to the console.
         new Promise(resolve => {
           resolve(handler.receiveMessage(data));
         }).catch(e => {
           Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e);
         });
       });
+      data = null;
       // Note: Unhandled messages are silently dropped.
       return;
     }
 
     let target = new MessageManagerProxy(data.target);
 
     let deferred = {
       sender: data.sender,
       messageManager: target,
       channelId: data.channelId,
     };
     deferred.promise = new Promise((resolve, reject) => {
       deferred.reject = reject;
 
       this._callHandlers(handlers, data).then(resolve, reject);
+      data = null;
     }).then(
       value => {
         let response = {
           result: this.RESULT_SUCCESS,
-          messageName: data.channelId,
+          messageName: deferred.channelId,
           recipient: {},
           value,
         };
 
         target.sendAsyncMessage(MESSAGE_RESPONSE, response);
       },
       error => {
         if (target.isDisconnected) {
@@ -668,17 +673,17 @@ this.MessageChannel = {
               error.result !== this.RESULT_NO_RESPONSE) {
             Cu.reportError(Cu.getClassName(error, false) === "Object" ? error.message : error);
           }
           return;
         }
 
         let response = {
           result: this.RESULT_ERROR,
-          messageName: data.channelId,
+          messageName: deferred.channelId,
           recipient: {},
           error: {},
         };
 
         if (error && typeof(error) == "object") {
           if (error.result) {
             response.result = error.result;
           }
--- a/toolkit/components/extensions/ext-c-storage.js
+++ b/toolkit/components/extensions/ext-c-storage.js
@@ -89,31 +89,31 @@ this.storage = class extends ExtensionAP
       storage: {
         local: {
           get: async function(keys) {
             const stopwatchKey = {};
             TelemetryStopwatch.start(storageGetHistogram, stopwatchKey);
             try {
               let result = await context.childManager.callParentAsyncFunction("storage.local.get", [
                 serialize(keys),
-              ], null, {noClone: true}).then(deserialize);
+              ]).then(deserialize);
               TelemetryStopwatch.finish(storageGetHistogram, stopwatchKey);
               return result;
             } catch (e) {
               TelemetryStopwatch.cancel(storageGetHistogram, stopwatchKey);
               throw e;
             }
           },
           set: async function(items) {
             const stopwatchKey = {};
             TelemetryStopwatch.start(storageSetHistogram, stopwatchKey);
             try {
               let result = await context.childManager.callParentAsyncFunction("storage.local.set", [
                 serialize(items),
-              ], null, {noClone: true});
+              ]);
               TelemetryStopwatch.finish(storageSetHistogram, stopwatchKey);
               return result;
             } catch (e) {
               TelemetryStopwatch.cancel(storageSetHistogram, stopwatchKey);
               throw e;
             }
           },
         },
--- a/toolkit/crashreporter/tools/symbolstore.py
+++ b/toolkit/crashreporter/tools/symbolstore.py
@@ -36,16 +36,22 @@ import subprocess
 import time
 import ctypes
 import urlparse
 import concurrent.futures
 import multiprocessing
 
 from optparse import OptionParser
 
+from mozbuild.util import memoize
+from mozbuild.generated_sources import (
+    get_filename_with_digest,
+    get_generated_sources,
+    get_s3_region_and_bucket,
+)
 from mozpack.copier import FileRegistry
 from mozpack.manifests import (
     InstallManifest,
     UnreadableInstallManifest,
 )
 
 # Utility classes
 
@@ -326,16 +332,25 @@ def make_file_mapping(install_manifests)
         reg = FileRegistry()
         manifest.populate_registry(reg)
         for dst, src in reg:
             if hasattr(src, 'path'):
                 abs_dest = os.path.normpath(os.path.join(destination, dst))
                 file_mapping[abs_dest] = src.path
     return file_mapping
 
+@memoize
+def get_generated_file_s3_path(filename, rel_path, bucket):
+    """Given a filename, return a path formatted similarly to
+    GetVCSFilename but representing a file available in an s3 bucket."""
+    with open(filename, 'rb') as f:
+        path = get_filename_with_digest(rel_path, f.read())
+        return 's3:{bucket}:{path}:'.format(bucket=bucket, path=path)
+
+
 def GetPlatformSpecificDumper(**kwargs):
     """This function simply returns a instance of a subclass of Dumper
     that is appropriate for the current platform."""
     return {'WINNT': Dumper_Win32,
             'Linux': Dumper_Linux,
             'Darwin': Dumper_Mac}[buildconfig.substs['OS_ARCH']](**kwargs)
 
 def SourceIndex(fileStream, outputPath, vcs_root):
@@ -371,29 +386,33 @@ class Dumper:
     srcdirRepoInfo = {}
 
     def __init__(self, dump_syms, symbol_path,
                  archs=None,
                  srcdirs=[],
                  copy_debug=False,
                  vcsinfo=False,
                  srcsrv=False,
+                 generated_files=None,
+                 s3_bucket=None,
                  file_mapping=None):
         # popen likes absolute paths, at least on windows
         self.dump_syms = os.path.abspath(dump_syms)
         self.symbol_path = symbol_path
         if archs is None:
             # makes the loop logic simpler
             self.archs = ['']
         else:
             self.archs = ['-a %s' % a for a in archs.split()]
         self.srcdirs = [os.path.normpath(self.FixFilenameCase(a)) for a in srcdirs]
         self.copy_debug = copy_debug
         self.vcsinfo = vcsinfo
         self.srcsrv = srcsrv
+        self.generated_files = generated_files or {}
+        self.s3_bucket = s3_bucket
         self.file_mapping = file_mapping or {}
         # Add a static mapping for Rust sources.
         target_os = buildconfig.substs['OS_ARCH']
         rust_srcdir = None
         if target_os == 'WINNT':
             rust_srcdir = 'C:/projects/rust/'
         elif target_os == 'Darwin':
             rust_srcdir = '/Users/travis/build/rust-lang/rust/'
@@ -491,17 +510,22 @@ class Dumper:
                         # FILE index filename
                         (x, index, filename) = line.rstrip().split(None, 2)
                         filename = os.path.normpath(self.FixFilenameCase(filename))
                         # We want original file paths for the source server.
                         sourcepath = filename
                         if filename in self.file_mapping:
                             filename = self.file_mapping[filename]
                         if self.vcsinfo:
-                            (filename, rootname) = GetVCSFilename(filename, self.srcdirs)
+                            gen_path = self.generated_files.get(filename)
+                            if gen_path and self.s3_bucket:
+                                filename = get_generated_file_s3_path(filename, gen_path, self.s3_bucket)
+                                rootname = ''
+                            else:
+                                (filename, rootname) = GetVCSFilename(filename, self.srcdirs)
                             # sets vcs_root in case the loop through files were to end on an empty rootname
                             if vcs_root is None:
                               if rootname:
                                  vcs_root = rootname
                         # gather up files with hg for indexing
                         if filename.startswith("hg"):
                             (ver, checkout, source_file, revision) = filename.split(":", 3)
                             sourceFileStream += sourcepath + "*" + source_file + '*' + revision + "\r\n"
@@ -662,16 +686,17 @@ class Dumper_Win32(Dumper):
             pdbstr = os.path.normpath(pdbstr_path)
             subprocess.call([pdbstr, "-w", "-p:" + os.path.basename(debug_file),
                              "-i:" + os.path.basename(streamFilename), "-s:srcsrv"],
                             cwd=os.path.dirname(stream_output_path))
             # clean up all the .stream files when done
             os.remove(stream_output_path)
         return result
 
+
 class Dumper_Linux(Dumper):
     objcopy = os.environ['OBJCOPY'] if 'OBJCOPY' in os.environ else 'objcopy'
     def ShouldProcess(self, file):
         """This function will allow processing of files that are
         executable, or end with the .so extension, and additionally
         file(1) reports as being ELF files.  It expects to find the file
         command in PATH."""
         if file.endswith(".so") or os.access(file, os.X_OK):
@@ -835,22 +860,27 @@ to canonical locations in the source rep
         exit(1)
 
     try:
         manifests = validate_install_manifests(options.install_manifests)
     except (IOError, ValueError) as e:
         parser.error(str(e))
         exit(1)
     file_mapping = make_file_mapping(manifests)
+    generated_files = {os.path.join(buildconfig.topobjdir, f): f
+                          for (f, _) in get_generated_sources()}
+    _, bucket = get_s3_region_and_bucket()
     dumper = GetPlatformSpecificDumper(dump_syms=args[0],
                                        symbol_path=args[1],
                                        copy_debug=options.copy_debug,
                                        archs=options.archs,
                                        srcdirs=options.srcdir,
                                        vcsinfo=options.vcsinfo,
                                        srcsrv=options.srcsrv,
+                                       generated_files=generated_files,
+                                       s3_bucket=bucket,
                                        file_mapping=file_mapping)
 
     dumper.Process(args[2])
 
 # run main if run directly
 if __name__ == "__main__":
     main()
--- a/toolkit/crashreporter/tools/unit-symbolstore.py
+++ b/toolkit/crashreporter/tools/unit-symbolstore.py
@@ -209,16 +209,38 @@ class TestGetVCSFilename(HelperMixin, un
         os.environ['MOZ_SOURCE_REPO'] = 'https://somewhere.com/repo'
         os.environ['MOZ_SOURCE_CHANGESET'] = 'abcdef0123456'
         os.mkdir(os.path.join(self.test_dir, '.hg'))
         filename = os.path.join(self.test_dir, 'foo.c')
         self.assertEqual('hg:somewhere.com/repo:foo.c:abcdef0123456',
                          symbolstore.GetVCSFilename(filename, [self.test_dir])[0])
 
 
+# SHA-512 of a zero-byte file
+EMPTY_SHA512 = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'
+
+
+class TestGeneratedFilePath(HelperMixin, unittest.TestCase):
+    def setUp(self):
+        HelperMixin.setUp(self)
+
+    def tearDown(self):
+        HelperMixin.tearDown(self)
+
+    def test_generated_file_path(self):
+        # Make an empty generated file
+        g = os.path.join(self.test_dir, 'generated')
+        rel_path = 'a/b/generated'
+        with open(g, 'wb') as f:
+            pass
+        expected = 's3:bucket:{}/{}:'.format(EMPTY_SHA512,
+                                             rel_path)
+        self.assertEqual(expected, symbolstore.get_generated_file_s3_path(g, rel_path, 'bucket'))
+
+
 if target_platform() == 'WINNT':
     class TestFixFilenameCase(HelperMixin, unittest.TestCase):
         def test_fix_filename_case(self):
             # self.test_dir is going to be 8.3 paths...
             junk = os.path.join(self.test_dir, 'x')
             with open(junk, 'wb') as o:
                 o.write('x')
             d = symbolstore.Dumper_Win32(dump_syms='dump_syms',
--- a/toolkit/mozapps/installer/package-name.mk
+++ b/toolkit/mozapps/installer/package-name.mk
@@ -75,16 +75,19 @@ LANGPACK = $(PKG_LANGPACK_PATH)$(PKG_LAN
 PKG_SRCPACK_BASENAME = $(MOZ_PKG_APPNAME)-$(MOZ_PKG_VERSION).source
 PKG_BUNDLE_BASENAME = $(MOZ_PKG_APPNAME)-$(MOZ_PKG_VERSION)
 PKG_SRCPACK_PATH =
 
 # Symbol package naming
 SYMBOL_FULL_ARCHIVE_BASENAME = $(PKG_BASENAME).crashreporter-symbols-full
 SYMBOL_ARCHIVE_BASENAME = $(PKG_BASENAME).crashreporter-symbols
 
+# Generated file package naming
+GENERATED_SOURCE_FILE_PACKAGE = $(PKG_BASENAME).generated-files.tar.gz
+
 # Code coverage package naming
 CODE_COVERAGE_ARCHIVE_BASENAME = $(PKG_BASENAME).code-coverage-gcno
 
 # Mozharness naming
 MOZHARNESS_PACKAGE = mozharness.zip
 
 # Test package naming
 TEST_PACKAGE = $(PKG_BASENAME).common.tests.zip
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -392,16 +392,17 @@ UPLOAD_FILES= \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(XPC_TEST_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(MOCHITEST_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(TALOS_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(AWSY_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(REFTEST_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(WP_TEST_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(GTEST_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip) \
+  $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(GENERATED_SOURCE_FILE_PACKAGE)) \
   $(call QUOTED_WILDCARD,$(MOZ_SOURCESTAMP_FILE)) \
   $(call QUOTED_WILDCARD,$(MOZ_BUILDINFO_FILE)) \
   $(call QUOTED_WILDCARD,$(MOZ_BUILDID_INFO_TXT_FILE)) \
   $(call QUOTED_WILDCARD,$(MOZ_MOZINFO_FILE)) \
   $(call QUOTED_WILDCARD,$(MOZ_TEST_PACKAGES_FILE)) \
   $(call QUOTED_WILDCARD,$(PKG_JSSHELL)) \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip) \
   $(call QUOTED_WILDCARD,$(topobjdir)/browser/installer/windows/instgen/setup.exe) \
--- a/tools/fuzzing/libfuzzer/moz.build
+++ b/tools/fuzzing/libfuzzer/moz.build
@@ -29,9 +29,12 @@ SOURCES += [
     'FuzzerShmemWindows.cpp',
     'FuzzerTracePC.cpp',
     'FuzzerTraceState.cpp',
     'FuzzerUtil.cpp',
     'FuzzerUtilDarwin.cpp',
     'FuzzerUtilLinux.cpp',
     'FuzzerUtilPosix.cpp',
     'FuzzerUtilWindows.cpp'
-]
\ No newline at end of file
+]
+
+if CONFIG['CLANG_CXX']:
+    CXXFLAGS += ['-Wno-unreachable-code-return']
--- a/tools/update-packaging/Makefile.in
+++ b/tools/update-packaging/Makefile.in
@@ -52,22 +52,22 @@ ifeq ($(OS_TARGET), WINNT)
 	cd $(PACKAGE_BASE_DIR) && $(INNER_UNMAKE_PACKAGE)
 endif
 	MAR=$(MAR_BIN) \
 	MOZ_PRODUCT_VERSION=$(MOZ_APP_VERSION) \
 	  $(srcdir)/make_full_update.sh \
 	  '$(DIST)/$(COMPLETE_MAR)' \
 	  '$(PACKAGE_DIR)'
 ifdef MOZ_SIGN_CMD
-	$(MOZ_SIGN_CMD) -f mar '$(DIST)/$(COMPLETE_MAR)'
+	$(MOZ_SIGN_CMD) -f mar_sha384 '$(DIST)/$(COMPLETE_MAR)'
 endif
 
 partial-patch:: $(dir-stage)
 	MAR=$(MAR_BIN) \
 	MBSDIFF=$(MBSDIFF_BIN) \
 	MOZ_PRODUCT_VERSION=$(MOZ_APP_VERSION) \
 	  $(srcdir)/make_incremental_update.sh \
 	  '$(STAGE_DIR)/$(PKG_UPDATE_BASENAME).partial.$(SRC_BUILD_ID)-$(DST_BUILD_ID).mar' \
 	  '$(SRC_BUILD)' \
 	  '$(DST_BUILD)'
 ifdef MOZ_SIGN_CMD
-	$(MOZ_SIGN_CMD) -f mar '$(STAGE_DIR)/$(PKG_UPDATE_BASENAME).partial.$(SRC_BUILD_ID)-$(DST_BUILD_ID).mar'
+	$(MOZ_SIGN_CMD) -f mar_sha384 '$(STAGE_DIR)/$(PKG_UPDATE_BASENAME).partial.$(SRC_BUILD_ID)-$(DST_BUILD_ID).mar'
 endif
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -270,30 +270,33 @@ PrioritizedEventQueue<InnerQueueT>::Coun
 // before starting event prioritization.
 template<class InnerQueueT>
 class PrioritizedEventQueue<InnerQueueT>::EnablePrioritizationRunnable final
   : public mozilla::Runnable
 {
 public:
   explicit EnablePrioritizationRunnable(PrioritizedEventQueue<InnerQueueT>* aQueue)
     : Runnable("EnablePrioritizationRunnable")
+    , mQueue(aQueue)
   {}
 
   NS_IMETHOD Run() override
   {
     // Do don't need to do this with the lock held because mReadFromInputQueue
     // is only read from the main thread.
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mQueue->mWriteToInputQueue);
     MOZ_ASSERT(!mQueue->mReadFromInputQueue);
     mQueue->mReadFromInputQueue = true;
     return NS_OK;
   }
 
 private:
+  // This is a weak pointer. It's guaranteed to stay alive until this runnable
+  // runs since it functions as the event loop in which the runnable is posted.
   PrioritizedEventQueue<InnerQueueT>* mQueue;
 };
 
 template<class InnerQueueT>
 void
 PrioritizedEventQueue<InnerQueueT>::EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock)
 {
   MOZ_ASSERT(!mWriteToInputQueue);