Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorDaniel Varga <dvarga@mozilla.com>
Thu, 23 May 2019 19:03:36 +0300
changeset 475398 386097a10f84a183ac8b16c4fbc7b391261f1fd2
parent 475397 740009023b1b6867a18b8f2f44e0ba84bfa229ec (current diff)
parent 475165 5a63f841eacb1632a595c30bfb3d81636a2cde8d (diff)
child 475399 f12c5ebd0bfce0915e7979bc95e7e8781c2f3a4f
push id36061
push usercbrindusan@mozilla.com
push dateFri, 24 May 2019 21:49:59 +0000
treeherdermozilla-central@5d3e1ea77693 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
browser/base/content/hiddenWindow.xul
gfx/tests/gtest/TestGfxPrefs.cpp
gfx/thebes/gfxPrefs.cpp
gfx/thebes/gfxPrefs.h
testing/web-platform/meta/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html.ini
testing/web-platform/meta/html/semantics/embedded-content/media-elements/track/track-element/src-clear-cues.html.ini
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -430,16 +430,19 @@ void DocAccessible::Shutdown() {
   }
 
   mPresShell->SetDocAccessible(nullptr);
   mPresShell = nullptr;  // Avoid reentrancy
 
   mDependentIDsHashes.Clear();
   mNodeToAccessibleMap.Clear();
 
+  mAnchorJumpElm = nullptr;
+  mInvalidationList.Clear();
+
   for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
     Accessible* accessible = iter.Data();
     MOZ_ASSERT(accessible);
     if (accessible && !accessible->IsDefunct()) {
       // Unlink parent to avoid its cleaning overhead in shutdown.
       accessible->mParent = nullptr;
       accessible->Shutdown();
     }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -16,17 +16,17 @@
 #
 
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 #define UNIX_BUT_NOT_MAC
 #endif
 #endif
 
-pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul");
+pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindowMac.xhtml");
 
 // Enables some extra Extension System Logging (can reduce performance)
 pref("extensions.logging.enabled", false);
 
 // Disables strict compatibility, making addons compatible-by-default.
 pref("extensions.strictCompatibility", false);
 
 // Temporary preference to forcibly make themes more safe with Australis even if
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -26,17 +26,17 @@
 <?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/searchbar.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/places/tree-icons.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?>
 
 # All DTD information is stored in a separate file so that it can be shared by
-# hiddenWindow.xul.
+# hiddenWindowMac.xhtml.
 <!DOCTYPE window [
 #include browser-doctype.inc
 ]>
 
 <window id="main-window"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:svg="http://www.w3.org/2000/svg"
         xmlns:html="http://www.w3.org/1999/xhtml"
rename from browser/base/content/hiddenWindow.xul
rename to browser/base/content/hiddenWindowMac.xhtml
--- a/browser/base/content/hiddenWindow.xul
+++ b/browser/base/content/hiddenWindowMac.xhtml
@@ -7,17 +7,16 @@
 
 <?xml-stylesheet href="chrome://browser/skin/webRTC-indicator.css" type="text/css"?>
 
 <!DOCTYPE window [
 #include browser-doctype.inc
 ]>
 
 <window id="main-window"
-        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 #include macWindow.inc.xul
 
 <!-- Dock menu -->
 <popupset>
   <menupopup id="menu_mac_dockmenu">
     <!-- The command cannot be cmd_newNavigator because we need to activate
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -146,17 +146,17 @@ with Files("browser-safebrowsing.js"):
     BUG_COMPONENT = ("Toolkit", "Safe Browsing")
 
 with Files("browser-sync.js"):
     BUG_COMPONENT = ("Firefox", "Sync")
 
 with Files("contentSearch*"):
     BUG_COMPONENT = ("Firefox", "Search")
 
-with Files("hiddenWindow.xul"):
+with Files("hiddenWindowMac.xhtml"):
     BUG_COMPONENT = ("Firefox", "Device Permissions")
 
 with Files("macWindow.inc.xul"):
     BUG_COMPONENT = ("Firefox", "Shell Integration")
 
 with Files("tabbrowser*"):
     BUG_COMPONENT = ("Firefox", "Tabbed Browser")
 
--- a/browser/base/content/nonbrowser-mac.js
+++ b/browser/base/content/nonbrowser-mac.js
@@ -39,17 +39,17 @@ function nonBrowserWindowStartup() {
   for (let shownItem of shownItems) {
     element = document.getElementById(shownItem);
     if (element)
       element.removeAttribute("hidden");
   }
 
   // If no windows are active (i.e. we're the hidden window), disable the close, minimize
   // and zoom menu commands as well
-  if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+  if (window.location.href == "chrome://browser/content/hiddenWindowMac.xhtml") {
     var hiddenWindowDisabledItems = ["cmd_close", "minimizeWindow", "zoomWindow"];
     for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
       element = document.getElementById(hiddenWindowDisabledItem);
       if (element)
         element.setAttribute("disabled", "true");
     }
 
     // also hide the window-list separator
@@ -91,17 +91,17 @@ function nonBrowserWindowDelayedStartup(
 
   // initialize the private browsing UI
   gPrivateBrowsingUI.init();
 }
 
 function nonBrowserWindowShutdown() {
   // If this is the hidden window being closed, release our reference to
   // the dock menu element to prevent leaks on shutdown
-  if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+  if (window.location.href == "chrome://browser/content/hiddenWindowMac.xhtml") {
     let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
                       .getService(Ci.nsIMacDockSupport);
     dockSupport.dockMenu = null;
   }
 
   // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
   // just cancel the pending timeout and return;
   if (delayedStartupTimeoutId) {
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -6,18 +6,20 @@
 #tabbrowser-tabs[closebuttons="activetab"] > .tabbrowser-tab > .tab-stack > .tab-content > .tab-close-button:not([selected="true"]),
 .tab-icon-pending:not([pendingicon]),
 .tab-icon-pending[busy],
 .tab-icon-pending[pinned],
 .tab-icon-image:not([src]):not([pinned]):not([crashed])[selected],
 .tab-icon-image:not([src]):not([pinned]):not([crashed]):not([sharing]),
 .tab-icon-image[busy],
 .tab-throbber:not([busy]),
+.tab-icon-pip:not([pictureinpicture]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
 .tab-icon-sound[pinned],
+.tab-icon-sound[pictureinpicture],
 .tab-sharing-icon-overlay,
 .tab-icon-overlay {
   display: none;
 }
 
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1946,17 +1946,20 @@
                     xbl:inherits="pinned,selected=visuallyselected,labeldirection"
                     onoverflow="this.setAttribute('textoverflow', 'true');"
                     onunderflow="this.removeAttribute('textoverflow');"
                     flex="1">
             <xul:label class="tab-text tab-label" anonid="tab-label"
                        xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
                        role="presentation"/>
           </xul:hbox>
-          <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked"
+          <xul:image xbl:inherits="pictureinpicture"
+                     class="tab-icon-pip"
+                     role="presentation"/>
+          <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,pictureinpicture"
                      anonid="soundplaying-icon"
                      class="tab-icon-sound"
                      role="presentation"/>
           <xul:image anonid="close-button"
                      xbl:inherits="fadein,pinned,selected=visuallyselected"
                      class="tab-close-button close-icon"
                      role="presentation"/>
         </xul:hbox>
@@ -2058,16 +2061,22 @@
       </property>
 
       <property name="soundPlaying" readonly="true">
         <getter>
           return this.getAttribute("soundplaying") == "true";
         </getter>
       </property>
 
+      <property name="pictureinpicture" readonly="true">
+        <getter>
+          return this.getAttribute("pictureinpicture") == "true";
+        </getter>
+      </property>
+
       <property name="activeMediaBlocked" readonly="true">
         <getter>
           return this.getAttribute("activemedia-blocked") == "true";
         </getter>
       </property>
 
       <property name="isEmpty" readonly="true">
         <getter>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -99,17 +99,17 @@ browser.jar:
 *       content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
         content/browser/utilityOverlay.js             (content/utilityOverlay.js)
         content/browser/webext-panels.js              (content/webext-panels.js)
 *       content/browser/webext-panels.xul             (content/webext-panels.xul)
         content/browser/nsContextMenu.js              (content/nsContextMenu.js)
         content/browser/contentTheme.js               (content/contentTheme.js)
 #ifdef XP_MACOSX
 # XXX: We should exclude this one as well (bug 71895)
-*       content/browser/hiddenWindow.xul              (content/hiddenWindow.xul)
+*       content/browser/hiddenWindowMac.xhtml         (content/hiddenWindowMac.xhtml)
         content/browser/nonbrowser-mac.js             (content/nonbrowser-mac.js)
 #endif
 #ifndef XP_MACOSX
 *       content/browser/webrtcIndicator.xul           (content/webrtcIndicator.xul)
         content/browser/webrtcIndicator.js            (content/webrtcIndicator.js)
 #endif
 # the following files are browser-specific overrides
 *       content/browser/license.html                  (/toolkit/content/license.html)
--- a/browser/modules/TabsList.jsm
+++ b/browser/modules/TabsList.jsm
@@ -258,16 +258,17 @@ class TabsPanel extends TabsListBase {
     });
 
     this._setImageAttributes(row, tab);
 
     let secondaryButton = row.querySelector(".all-tabs-secondary-button");
     setAttributes(secondaryButton, {
       muted: tab.muted,
       soundplaying: tab.soundPlaying,
+      pictureinpicture: tab.pictureinpicture,
       hidden: !(tab.muted || tab.soundPlaying),
     });
   }
 
   _setImageAttributes(row, tab) {
     let button = row.firstElementChild;
     let image = this.doc.getAnonymousElementByAttribute(
       button, "class", "toolbarbutton-icon") ||
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -372,51 +372,65 @@
   stroke-opacity: var(--toolbarbutton-icon-fill-opacity);
 }
 
 :root[uidensity=touch] .tab-close-button {
   margin-inline-end: -@horizontalTabPadding@;
   padding: 10px calc(@horizontalTabPadding@ - 2px);
 }
 
+.tab-icon-pip,
 .tab-icon-sound {
   margin-inline-start: 1px;
   width: 16px;
   height: 16px;
   padding: 0;
 }
 
+.tab-icon-pip[pictureinpicture] {
+  background-image: url(chrome://global/skin/media/pictureinpicture.svg);
+  -moz-context-properties: fill, stroke;
+  fill: currentColor;
+  stroke: currentColor;
+  width: 14px;
+  height: 14px;
+  margin-inline-start: 5px
+}
+
 .tab-icon-sound[soundplaying],
 .tab-icon-sound[muted],
 .tab-icon-sound[activemedia-blocked] {
   list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-playing.svg);
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .tab-icon-sound[muted] {
   list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-muted.svg);
 }
 
 .tab-icon-sound[activemedia-blocked] {
   list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-blocked.svg);
 }
 
+:root[lwtheme-image] .tab-icon-pip:-moz-lwtheme-darktext[pictureinpicture],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-darktext[soundplaying],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-darktext[muted],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-darktext[activemedia-blocked] {
   filter: drop-shadow(1px 1px 1px white);
 }
 
+:root[lwtheme-image] .tab-icon-pip:-moz-lwtheme-brighttext[pictureinpicture],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-brighttext[soundplaying],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-brighttext[muted],
 :root[lwtheme-image] .tab-icon-sound:-moz-lwtheme-brighttext[activemedia-blocked] {
   filter: drop-shadow(1px 1px 1px black);
 }
 
+.tab-icon-pip[pictureinpicture]:not(:hover),
 .tab-icon-sound[soundplaying]:not(:hover),
 .tab-icon-sound[muted]:not(:hover),
 .tab-icon-sound[activemedia-blocked]:not(:hover) {
   opacity: .8;
 }
 
 .tab-icon-sound[soundplaying-scheduledremoval]:not([muted]):not(:hover),
 .tab-icon-overlay[soundplaying-scheduledremoval]:not([muted]):not(:hover) {
--- a/build/moz.configure/warnings.configure
+++ b/build/moz.configure/warnings.configure
@@ -16,16 +16,18 @@ add_old_configure_assignment(
 @depends('--enable-warnings-as-errors')
 def rust_warning_flags(warnings_as_errors):
     flags = []
 
     # Note that cargo passes --cap-lints warn to rustc for third-party code, so
     # we don't need a very complicated setup.
     if warnings_as_errors:
         flags.append('-Dwarnings')
+    else:
+        flags.extend(('--cap-lints', 'warn'))
 
     return flags
 
 
 # GCC/Clang warnings:
 # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
 # https://clang.llvm.org/docs/DiagnosticsReference.html
 
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -53,12 +53,12 @@ if test -z "$bucket" -a -z "$SCCACHE_DIS
 fi
 
 if test -n "$bucket"; then
     if [ -n "${SCCACHE_GCS_KEY_PATH}" ]; then
         mk_add_options "export SCCACHE_GCS_BUCKET=$bucket"
     else
         mk_add_options "export SCCACHE_BUCKET=$bucket"
     fi
-    export CCACHE="$topsrcdir/sccache2/sccache"
+    export CCACHE="$topsrcdir/sccache/sccache"
     export SCCACHE_VERBOSE_STATS=1
-    mk_add_options MOZBUILD_MANAGE_SCCACHE_DAEMON=${topsrcdir}/sccache2/sccache
+    mk_add_options MOZBUILD_MANAGE_SCCACHE_DAEMON=${topsrcdir}/sccache/sccache
 fi
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -208,17 +208,21 @@ module.exports = {
     // since only let and const are used, see "no-var").
     "block-scoped-var": "error",
     // Enforce one true brace style (opening brace on the same line) and avoid
     // start and end braces on the same line.
     "brace-style": ["error", "1tbs", {"allowSingleLine": false}],
     // Require camel case names
     "camelcase": "error",
     // Warn about cyclomatic complexity in functions.
-    "complexity": ["error", 40],
+    // 20 is ESLint's default, and we want to keep it this way to prevent new highly
+    // complex functions from being introduced. However, because Mozilla's eslintrc has
+    // some other value defined, we need to override it here. See bug 1553449 for more
+    // information on complex DevTools functions that are currently excluded.
+    "complexity": ["error", 20],
     // Don't warn for inconsistent naming when capturing this (not so important
     // with auto-binding fat arrow functions).
     "consistent-this": "off",
     // Enforce curly brace conventions for all control statements.
     "curly": "error",
     // Don't require a default case in switch statements. Avoid being forced to
     // add a bogus default when you know all possible cases are handled.
     "default-case": "off",
--- a/devtools/client/inspector/animation/components/graph/SummaryGraph.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraph.js
@@ -33,16 +33,17 @@ class SummaryGraph extends PureComponent
     this.onClick = this.onClick.bind(this);
   }
 
   onClick(event) {
     event.stopPropagation();
     this.props.selectAnimation(this.props.animation);
   }
 
+  /* eslint-disable complexity */
   getTitleText(state) {
     const getTime =
       time => getFormatStr("player.timeLabel", numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedTitle(state);
@@ -131,16 +132,17 @@ class SummaryGraph extends PureComponent
         text += getStr("player.somePropertiesOnCompositorTooltip");
       }
     } else if (state.isRunningOnCompositor) {
       text += getStr("player.runningOnCompositorTooltip");
     }
 
     return text;
   }
+  /* eslint-enable complexity */
 
   render() {
     const {
       animation,
       emitEventForTest,
       getAnimatedPropertyMap,
       simulateAnimation,
       timeScale,
--- a/devtools/client/inspector/changes/reducers/changes.js
+++ b/devtools/client/inspector/changes/reducers/changes.js
@@ -194,16 +194,17 @@ const reducers = {
    *   addition;
    * - when a declaration is removed, we update the indices of other tracked declarations
    *   in the same rule which may have changed position in the rule as a result;
    * - changes which cancel each other out (i.e. return to original) are both removed
    *   from the store;
    * - when changes cancel each other out leaving the rule unchanged, the rule is removed
    *   from the store. Its parent rule is removed as well if it too ends up unchanged.
    */
+  /* eslint-disable complexity */
   [TRACK_CHANGE](state, { change }) {
     const defaults = {
       selector: null,
       source: {},
       ancestors: [],
       add: [],
       remove: [],
     };
@@ -346,16 +347,17 @@ const reducers = {
     if (!Object.keys(source.rules).length) {
       delete state[sourceId];
     } else {
       state[sourceId] = source;
     }
 
     return state;
   },
+  /* eslint-enable complexity */
 
   [RESET_CHANGES](state) {
     return INITIAL_STATE;
   },
 
 };
 
 module.exports = function(state = INITIAL_STATE, action) {
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -320,16 +320,17 @@ CssComputedView.prototype = {
    *        The node which we want information about
    * @return {Object} The type information object contains the following props:
    * - view {String} Always "computed" to indicate the computed view.
    * - type {String} One of the VIEW_NODE_XXX_TYPE const in
    *   client/inspector/shared/node-types
    * - value {Object} Depends on the type of the node
    * returns null if the node isn't anything we care about
    */
+  /* eslint-disable complexity */
   getNodeInfo: function(node) {
     if (!node) {
       return null;
     }
 
     const classes = node.classList;
 
     // Check if the node isn't a selector first since this doesn't require
@@ -423,16 +424,17 @@ CssComputedView.prototype = {
     }
 
     return {
       view: "computed",
       type,
       value,
     };
   },
+  /* eslint-enable complexity */
 
   _createPropertyViews: function() {
     if (this._createViewsPromise) {
       return this._createViewsPromise;
     }
 
     this.refreshSourceFilter();
     this.numVisibleProperties = 0;
--- a/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js
@@ -108,16 +108,17 @@ class FlexItemSizingProperties extends P
           title,
           property
         ),
         this.renderSize(mainBaseSize)
       )
     );
   }
 
+  /* eslint-disable complexity */
   renderFlexibilitySection(flexItemSizing, mainFinalSize, properties, computedStyle) {
     const {
       mainDeltaSize,
       mainBaseSize,
       lineGrowthState,
     } = flexItemSizing;
 
     // Don't display anything if all interesting sizes are 0.
@@ -178,16 +179,17 @@ class FlexItemSizingProperties extends P
           getStr("flexbox.itemSizing.flexibilitySectionHeader"),
           property
         ),
         this.renderSize(mainDeltaSize, true),
         this.renderReasons(reasons)
       )
     );
   }
+  /* eslint-enable complexity */
 
   renderMinimumSizeSection(flexItemSizing, properties, dimension) {
     const { clampState, mainMinSize, mainDeltaSize } = flexItemSizing;
     const grew = mainDeltaSize > 0;
     const shrank = mainDeltaSize < 0;
     const minDimensionValue = properties[`min-${dimension}`];
 
     // We only display the minimum size when the item actually violates that size during
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -208,16 +208,17 @@ SelectorAutocompleter.prototype = {
    * currently requires a class suggestion, or a tag, or an Id suggestion.
    * This getter will effectively compute the state by traversing the query
    * character by character each time the query changes.
    *
    * @example
    *        '#f' requires an Id suggestion, so the state is States.ID
    *        'div > .foo' requires class suggestion, so state is States.CLASS
    */
+  /* eslint-disable complexity */
   get state() {
     if (!this.searchBox || !this.searchBox.value) {
       return null;
     }
 
     const query = this.searchBox.value;
     if (this._lastStateCheckAt == query) {
       // If query is the same, return early.
@@ -301,16 +302,17 @@ SelectorAutocompleter.prototype = {
               this._state = this.States.ATTRIBUTE;
             }
           }
           break;
       }
     }
     return this._state;
   },
+  /* eslint-enable complexity */
 
   /**
    * Removes event listeners and cleans up references.
    */
   destroy: function() {
     this.searchBox.removeEventListener("input", this.showSuggestions, true);
     this.searchBox.removeEventListener("keypress",
       this._onSearchKeypress, true);
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -890,16 +890,17 @@ MarkupView.prototype = {
      "PageDown", "Esc", "Enter", "Space"].forEach(key => {
        shortcuts.on(key, event => this._onShortcut(key, event));
      });
   },
 
   /**
    * Key shortcut listener.
    */
+  /* eslint-disable complexity */
   _onShortcut(name, event) {
     if (this._isInputOrTextarea(event.target)) {
       return;
     }
     switch (name) {
       // Localizable keys
       case "markupView.hide.key": {
         const node = this._selectedContainer.node;
@@ -1019,16 +1020,17 @@ MarkupView.prototype = {
       default:
         console.error("Unexpected markup-view key shortcut", name);
         return;
     }
     // Prevent default for this action
     event.stopPropagation();
     event.preventDefault();
   },
+  /* eslint-enable complexity */
 
   /**
    * Check if a node is an input or textarea
    */
   _isInputOrTextarea: function(element) {
     const name = element.tagName.toLowerCase();
     return name === "input" || name === "textarea";
   },
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -269,16 +269,17 @@ class ElementStyle {
    * an earlier property overrides it and update the editor to show it in the
    * UI. If there is any inactive CSS we also update the editors state to show
    * the inactive CSS icon.
    *
    * @param  {String} pseudo
    *         Which pseudo element to flag as overridden.
    *         Empty string or undefined will default to no pseudo element.
    */
+  /* eslint-disable complexity */
   updateDeclarations(pseudo = "") {
     // Gather all the text properties applied by these rules, ordered
     // from more- to less-specific. Text properties from keyframes rule are
     // excluded from being marked as overridden since a number of criteria such
     // as time, and animation overlay are required to be check in order to
     // determine if the property is overridden.
     const textProps = [];
     for (const rule of this.rules) {
@@ -371,16 +372,17 @@ class ElementStyle {
       }
 
       // For each editor show or hide the inactive CSS icon as needed.
       if (textProp.editor && this.unusedCssEnabled) {
         textProp.editor.updatePropertyState();
       }
     }
   }
+  /* eslint-enable complexity */
 
   /**
    * Adds a new declaration to the rule.
    *
    * @param  {String} ruleId
    *         The id of the Rule to be modified.
    * @param  {String} value
    *         The new declaration value.
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -366,16 +366,17 @@ CssRuleView.prototype = {
    *        The node which we want information about
    * @return {Object} The type information object contains the following props:
    * - view {String} Always "rule" to indicate the rule view.
    * - type {String} One of the VIEW_NODE_XXX_TYPE const in
    *   client/inspector/shared/node-types
    * - value {Object} Depends on the type of the node
    * returns null of the node isn't anything we care about
    */
+  /* eslint-disable complexity */
   getNodeInfo: function(node) {
     if (!node) {
       return null;
     }
 
     let type, value;
     const classes = node.classList;
     const prop = getParentTextProperty(node);
@@ -481,16 +482,17 @@ CssRuleView.prototype = {
     }
 
     return {
       view: "rule",
       type,
       value,
     };
   },
+  /* eslint-enable complexity */
 
   /**
    * Retrieve the RuleEditor instance.
    */
   _getRuleEditorForNode: function(node) {
     return node.closest(".ruleview-rule")._ruleEditor;
   },
 
@@ -1137,16 +1139,17 @@ CssRuleView.prototype = {
     const baseClassName = "ruleview-header";
     return isPseudo ? baseClassName + " ruleview-expandable-header" :
       baseClassName;
   },
 
   /**
    * Creates editor UI for each of the rules in _elementStyle.
    */
+  /* eslint-disable complexity */
   _createEditors: function() {
     // Run through the current list of rules, attaching
     // their editors in order.  Create editors if needed.
     let lastInheritedSource = "";
     let lastKeyframes = null;
     let seenPseudoElement = false;
     let seenNormalElement = false;
     let seenSearchTerm = false;
@@ -1217,16 +1220,17 @@ CssRuleView.prototype = {
     const searchBox = this.searchField.parentNode;
     searchBox.classList.toggle(
       "devtools-searchbox-no-match",
       this.searchValue && !seenSearchTerm,
     );
 
     return promise.all(editorReadyPromises);
   },
+  /* eslint-enable complexity */
 
   /**
    * Highlight rules that matches the filter search value and returns a
    * boolean indicating whether or not rules were highlighted.
    *
    * @param  {Rule} rule
    *         The rule object we're highlighting if its rule selectors or
    *         property values match the search value.
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -384,16 +384,17 @@ TextPropertyEditor.prototype = {
       return domRule.href || domRule.nodeHref;
     }
     return undefined;
   },
 
   /**
    * Populate the span based on changes to the TextProperty.
    */
+  /* eslint-disable complexity */
   update: function() {
     if (this.ruleView.isDestroyed) {
       return;
     }
 
     this.updatePropertyState();
 
     const name = this.prop.name;
@@ -605,16 +606,17 @@ TextPropertyEditor.prototype = {
 
     // Populate the computed styles and shorthand overridden styles.
     this._updateComputed();
     this._updateShorthandOverridden();
 
     // Update the rule property highlight.
     this.ruleView._updatePropertyHighlight(this);
   },
+  /* eslint-enable complexity */
 
   _onStartEditing: function() {
     this.element.classList.remove("ruleview-overridden");
     this.filterProperty.hidden = true;
     this.enable.style.visibility = "hidden";
     this.expander.style.display = "none";
   },
 
--- a/devtools/client/memory/utils.js
+++ b/devtools/client/memory/utils.js
@@ -98,16 +98,17 @@ exports.getCustomTreeMapDisplays = funct
 
 /**
  * Returns a string representing a readable form of the snapshot's state. More
  * concise than `getStatusTextFull`.
  *
  * @param {snapshotState | diffingState} state
  * @return {String}
  */
+/* eslint-disable complexity */
 exports.getStatusText = function(state) {
   assert(state, "Must have a state");
 
   switch (state) {
     case diffingState.ERROR:
       return L10N.getStr("diffing.state.error");
 
     case states.ERROR:
@@ -165,24 +166,26 @@ exports.getStatusText = function(state) 
     case individualsState.FETCHED:
       return "";
 
     default:
       assert(false, `Unexpected state: ${state}`);
       return "";
   }
 };
+/* eslint-enable complexity */
 
 /**
  * Returns a string representing a readable form of the snapshot's state;
  * more verbose than `getStatusText`.
  *
  * @param {snapshotState | diffingState} state
  * @return {String}
  */
+/* eslint-disable complexity */
 exports.getStatusTextFull = function(state) {
   assert(!!state, "Must have a state");
 
   switch (state) {
     case diffingState.ERROR:
       return L10N.getStr("diffing.state.error.full");
 
     case states.ERROR:
@@ -240,16 +243,17 @@ exports.getStatusTextFull = function(sta
     case individualsState.FETCHED:
       return "";
 
     default:
       assert(false, `Unexpected state: ${state}`);
       return "";
   }
 };
+/* eslint-enable complexity */
 
 /**
  * Return true if the snapshot is in a diffable state, false otherwise.
  *
  * @param {snapshotModel} snapshot
  * @returns {Boolean}
  */
 exports.snapshotIsDiffable = function snapshotIsDiffable(snapshot) {
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -181,16 +181,17 @@ class RequestListItem extends Component 
     if (!prevProps.isSelected && this.props.isSelected) {
       this.refs.listItem.focus();
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
     }
   }
 
+  /* eslint-disable complexity */
   render() {
     const {
       blocked,
       connector,
       columns,
       item,
       index,
       isSelected,
@@ -279,11 +280,12 @@ class RequestListItem extends Component 
           connector,
           firstRequestStartedMillis,
           item,
           onWaterfallMouseDown,
         }),
       )
     );
   }
+  /* eslint-enable complexity */
 }
 
 module.exports = RequestListItem;
--- a/devtools/client/netmonitor/src/components/SecurityPanel.js
+++ b/devtools/client/netmonitor/src/components/SecurityPanel.js
@@ -114,16 +114,17 @@ class SecurityPanel extends Component {
           className: "security-warning-icon",
           title: WARNING_CIPHER_LABEL,
         })
         :
         null
     );
   }
 
+  /* eslint-disable complexity */
   render() {
     const { openLink, request } = this.props;
     const { securityInfo, url } = request;
 
     if (!securityInfo || !url) {
       return null;
     }
 
@@ -227,11 +228,12 @@ class SecurityPanel extends Component {
         object,
         renderValue: (props) => this.renderValue(props, securityInfo.weaknessReasons),
         enableFilter: false,
         expandedNodes: TreeViewClass.getExpandedNodes(object),
         openLink,
       })
     );
   }
+  /* eslint-enable complexity */
 }
 
 module.exports = SecurityPanel;
--- a/devtools/client/netmonitor/src/reducers/requests.js
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -41,16 +41,17 @@ function Requests() {
     lastEndedMillis: -Infinity,
   };
 }
 
 /**
  * This reducer is responsible for maintaining list of request
  * within the Network panel.
  */
+/* eslint-disable complexity */
 function requestsReducer(state = Requests(), action) {
   switch (action.type) {
     // Appending new request into the list/map.
     case ADD_REQUEST: {
       const nextState = { ...state };
 
       const newRequest = {
         id: action.id,
@@ -177,16 +178,17 @@ function requestsReducer(state = Request
 
       return state;
     }
 
     default:
       return state;
   }
 }
+/* eslint-enable complexity */
 
 // Helpers
 
 function cloneRequest(state, id) {
   const { requests } = state;
 
   if (!id) {
     return state;
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -115,16 +115,17 @@ function processFlagFilter(type, value) 
         return null;
       }
       return quantity * multiplier;
     default:
       return value.toLowerCase();
   }
 }
 
+/* eslint-disable complexity */
 function isFlagFilterMatch(item, { type, value, negative }) {
   if (value == null) {
     return false;
   }
 
   // Ensures when filter token is exactly a flag ie. "remote-ip:", all values are shown
   if (value.length < 1) {
     return true;
@@ -228,16 +229,17 @@ function isFlagFilterMatch(item, { type,
         c.value.toLowerCase().includes(value)) > -1;
       break;
   }
   if (negative) {
     return !match;
   }
   return match;
 }
+/* eslint-enable complexity */
 
 function isSizeMatch(value, size) {
   return value >= (size - size / 10) && value <= (size + size / 10);
 }
 
 function isTextFilterMatch({ url }, text) {
   const lowerCaseUrl = getUnicodeUrl(url).toLowerCase();
   let lowerCaseText = text.toLowerCase();
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -20,16 +20,17 @@ loader.lazyRequireGetter(this, "copyStri
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 loader.lazyRequireGetter(this, "HarMenuUtils", "devtools/client/netmonitor/src/har/har-menu-utils", true);
 
 class RequestListContextMenu {
   constructor(props) {
     this.props = props;
   }
 
+  /* eslint-disable complexity */
   open(event, clickedRequest, requests) {
     const {
       id,
       blockedReason,
       isCustom,
       formDataSections,
       method,
       mimeType,
@@ -279,16 +280,17 @@ class RequestListContextMenu {
         this.useAsFetch(id, url, method, requestHeaders, requestPostData),
     });
 
     showMenu(menu, {
       screenX: event.screenX,
       screenY: event.screenY,
     });
   }
+  /* eslint-enable complexity */
 
   /**
    * Opens selected item in the debugger
    */
   openInDebugger(url) {
     const toolbox = gDevTools.getToolbox(this.props.connector.getTabTarget());
     toolbox.viewSourceInDebugger(url, 0);
   }
--- a/devtools/client/performance/modules/logic/frame-utils.js
+++ b/devtools/client/performance/modules/logic/frame-utils.js
@@ -30,16 +30,17 @@ const gInflatedFrameStore = new WeakMap(
 
 // The cache used to store frame data from `getInfo`.
 const gFrameData = new WeakMap();
 
 /**
  * Parses the raw location of this function call to retrieve the actual
  * function name, source url, host name, line and column.
  */
+/* eslint-disable complexity */
 function parseLocation(location, fallbackLine, fallbackColumn) {
   // Parse the `location` for the function name, source url, line, column etc.
 
   let line, column, url;
 
   // These two indices are used to extract the resource substring, which is
   // location[parenIndex + 1 .. lineAndColumnIndex].
   //
@@ -177,16 +178,17 @@ function parseLocation(location, fallbac
     }
   } else {
     functionName = location;
     url = null;
   }
 
   return { functionName, fileName, host, port, url, line, column };
 }
+/* eslint-enable complexity */
 
 /**
  * Sets the properties of `isContent` and `category` on a frame.
  *
  * @param {InflatedFrame} frame
  */
 function computeIsContentAndCategory(frame) {
   // Only C++ stack frames have associated category information.
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -307,16 +307,18 @@ pref("devtools.hud.loglimit", 10000);
 pref("devtools.editor.tabsize", 2);
 pref("devtools.editor.expandtab", true);
 pref("devtools.editor.keymap", "default");
 pref("devtools.editor.autoclosebrackets", true);
 pref("devtools.editor.detectindentation", true);
 pref("devtools.editor.enableCodeFolding", true);
 pref("devtools.editor.autocomplete", true);
 
+// The angle of the viewport.
+pref("devtools.responsive.viewport.angle", 0);
 // The width of the viewport.
 pref("devtools.responsive.viewport.width", 320);
 // The height of the viewport.
 pref("devtools.responsive.viewport.height", 480);
 // The pixel ratio of the viewport.
 pref("devtools.responsive.viewport.pixelRatio", 0);
 // Whether or not the viewports are left aligned.
 pref("devtools.responsive.leftAlignViewport.enabled", false);
--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -17,17 +17,17 @@ const {
   REMOVE_DEVICE,
   UPDATE_DEVICE_DISPLAYED,
   UPDATE_DEVICE_MODAL,
 } = require("./index");
 const { post } = require("../utils/message");
 
 const { addDevice, editDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
 const { changeUserAgent, toggleTouchSimulation } = require("./ui");
-const { changeDevice, changePixelRatio } = require("./viewports");
+const { changeDevice, changePixelRatio, changeViewportAngle } = require("./viewports");
 
 const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
 
 /**
  * Returns an object containing the user preference of displayed devices.
  *
  * @return {Object} containing two Sets:
  * - added: Names of the devices that were explicitly enabled by the user
@@ -108,16 +108,17 @@ module.exports = {
   editCustomDevice(viewport, oldDevice, newDevice) {
     return async function(dispatch) {
       // Edit custom device in storage
       await editDevice(oldDevice, newDevice, "custom");
       // Notify the window that the device should be updated in the device selector.
       post(window, {
         type: "change-device",
         device: newDevice,
+        viewport,
       });
 
       // Update UI if the device is selected.
       if (viewport) {
         dispatch(changeUserAgent(newDevice.userAgent));
         dispatch(toggleTouchSimulation(newDevice.touch));
       }
 
@@ -207,23 +208,26 @@ module.exports = {
       }
 
       const device = devices[deviceType].find(d => d.name === deviceName);
       if (!device) {
         // Can't find device with the same device name.
         return;
       }
 
+      const viewport = getState().viewports[0];
       post(window, {
         type: "change-device",
         device,
+        viewport,
       });
 
       dispatch(changeDevice(id, device.name, deviceType));
       dispatch(changePixelRatio(id, device.pixelRatio));
+      dispatch(changeViewportAngle(id, viewport.angle));
       dispatch(changeUserAgent(device.userAgent));
       dispatch(toggleTouchSimulation(device.touch));
     };
   },
 
   updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
     return {
       type: UPDATE_DEVICE_MODAL,
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -43,16 +43,19 @@ createEnum([
   // Change the user agent of the viewport.
   "CHANGE_USER_AGENT",
 
   // The pixel ratio of the viewport has changed. This may be triggered by the user
   // when changing the device displayed in the viewport, or when a pixel ratio is
   // selected from the device pixel ratio dropdown.
   "CHANGE_PIXEL_RATIO",
 
+  // Change the viewport angle.
+  "CHANGE_VIEWPORT_ANGLE",
+
   // Edit a device.
   "EDIT_DEVICE",
 
   // Indicates that the device list is being loaded.
   "LOAD_DEVICE_LIST_START",
 
   // Indicates that the device list loading action threw an error.
   "LOAD_DEVICE_LIST_ERROR",
--- a/devtools/client/responsive.html/actions/viewports.js
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const asyncStorage = require("devtools/shared/async-storage");
 
 const {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
   CHANGE_PIXEL_RATIO,
+  CHANGE_VIEWPORT_ANGLE,
   REMOVE_DEVICE_ASSOCIATION,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT,
 } = require("./index");
 
 const { post } = require("../utils/message");
 
 module.exports = {
@@ -58,16 +59,24 @@ module.exports = {
   changePixelRatio(id, pixelRatio = 0) {
     return {
       type: CHANGE_PIXEL_RATIO,
       id,
       pixelRatio,
     };
   },
 
+  changeViewportAngle(id, angle) {
+    return {
+      type: CHANGE_VIEWPORT_ANGLE,
+      id,
+      angle,
+    };
+  },
+
   /**
    * Remove the viewport's device assocation.
    */
   removeDeviceAssociation(id) {
     return async function(dispatch) {
       post(window, "remove-device-association");
 
       dispatch({
@@ -90,27 +99,15 @@ module.exports = {
       height,
     };
   },
 
   /**
    * Rotate the viewport.
    */
   rotateViewport(id) {
-    // TODO: Add `orientation` and `angle` properties to message data. See Bug 1357774.
-
-    // There is no window object to post to when ran on XPCShell as part of the unit
-    // tests and will immediately throw an error as a result.
-    try {
-      post(window, {
-        type: "viewport-orientation-change",
-      });
-    } catch (e) {
-      console.log("Unable to post message to window");
-    }
-
     return {
       type: ROTATE_VIEWPORT,
       id,
     };
   },
 
 };
--- a/devtools/client/responsive.html/browser/content.js
+++ b/devtools/client/responsive.html/browser/content.js
@@ -116,16 +116,21 @@ var global = this;
       return;
     }
     active = false;
     removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
     const webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     webProgress.removeProgressListener(WebProgressListener);
     docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
+    // Restore the original physical screen orientation values before RDM is stopped.
+    // This is necessary since the window document's `setCurrentRDMPaneOrientation`
+    // WebIDL operation can only modify the window's screen orientation values while the
+    // window content is in RDM.
+    restoreScreenOrientation();
     restoreScrollbars();
     setDocumentInRDMPane(false);
     stopOnResize();
     sendAsyncMessage("ResponsiveMode:Stop:Done");
   }
 
   function makeScrollbarsFloating() {
     if (!requiresFloatingScrollbars) {
@@ -160,16 +165,20 @@ var global = this;
       const winUtils = win.windowUtils;
       try {
         winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
       } catch (e) { }
     }
     flushStyle();
   }
 
+  function restoreScreenOrientation() {
+    docShell.contentViewer.DOMDocument.setRDMPaneOrientation("landscape-primary", 0);
+  }
+
   function setDocumentInRDMPane(inRDMPane) {
     // We don't propegate this property to descendent documents.
     docShell.contentViewer.DOMDocument.inRDMPane = inRDMPane;
   }
 
   function flushStyle() {
     // Force presContext destruction
     const isSticky = docShell.contentViewer.sticky;
@@ -194,16 +203,23 @@ var global = this;
   }
 
   const WebProgressListener = {
     onLocationChange(webProgress, request, URI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
         return;
       }
       setDocumentInRDMPane(true);
+      // Notify the Responsive UI manager to set orientation state on a location change.
+      // This is necessary since we want to ensure that the RDM Document's orientation
+      // state persists throughout while RDM is opened.
+      sendAsyncMessage("ResponsiveMode:OnLocationChange", {
+        width: content.innerWidth,
+        height: content.innerHeight,
+      });
       makeScrollbarsFloating();
     },
     QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
                                             "nsISupportsWeakReference"]),
   };
 })();
 
 global.responsiveFrameScriptLoaded = true;
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -1,16 +1,17 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
+const Services = require("Services");
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const Toolbar = createFactory(require("./Toolbar"));
 const Viewports = createFactory(require("./Viewports"));
 
@@ -33,20 +34,22 @@ const {
   toggleReloadOnTouchSimulation,
   toggleReloadOnUserAgent,
   toggleTouchSimulation,
   toggleUserAgentInput,
 } = require("../actions/ui");
 const {
   changeDevice,
   changePixelRatio,
+  changeViewportAngle,
   removeDeviceAssociation,
   resizeViewport,
   rotateViewport,
 } = require("../actions/viewports");
+const { getOrientation } = require("../utils/orientation");
 
 const Types = require("../types");
 
 class App extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       dispatch: PropTypes.func.isRequired,
@@ -62,16 +65,17 @@ class App extends PureComponent {
     this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
     this.onBrowserContextMenu = this.onBrowserContextMenu.bind(this);
     this.onBrowserMounted = this.onBrowserMounted.bind(this);
     this.onChangeDevice = this.onChangeDevice.bind(this);
     this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
     this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
     this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
     this.onChangeUserAgent = this.onChangeUserAgent.bind(this);
+    this.onChangeViewportOrientation = this.onChangeViewportOrientation.bind(this);
     this.onContentResize = this.onContentResize.bind(this);
     this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
     this.onEditCustomDevice = this.onEditCustomDevice.bind(this);
     this.onExit = this.onExit.bind(this);
     this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
     this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
     this.doResizeViewport = this.doResizeViewport.bind(this);
     this.onResizeViewport = this.onResizeViewport.bind(this);
@@ -109,17 +113,22 @@ class App extends PureComponent {
 
   onChangeDevice(id, device, deviceType) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
     // message is sent from the action creator and device property changes are sent from
     // there instead of this function.
     window.postMessage({
       type: "change-device",
       device,
+      viewport: this.props.viewports[id],
     }, "*");
+
+    const orientation = getOrientation(device, this.props.viewports[0]);
+
+    this.props.dispatch(changeViewportAngle(0, orientation.angle));
     this.props.dispatch(changeDevice(id, device.name, deviceType));
     this.props.dispatch(changePixelRatio(id, device.pixelRatio));
     this.props.dispatch(changeUserAgent(device.userAgent));
     this.props.dispatch(toggleTouchSimulation(device.touch));
   }
 
   onChangeNetworkThrottling(enabled, profile) {
     window.postMessage({
@@ -149,16 +158,25 @@ class App extends PureComponent {
   onChangeUserAgent(userAgent) {
     window.postMessage({
       type: "change-user-agent",
       userAgent,
     }, "*");
     this.props.dispatch(changeUserAgent(userAgent));
   }
 
+  onChangeViewportOrientation(id, { type, angle }) {
+    window.postMessage({
+      type: "viewport-orientation-change",
+      orientationType: type,
+      angle,
+    }, "*");
+    this.props.dispatch(changeViewportAngle(id, angle));
+  }
+
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
   }
 
@@ -223,17 +241,54 @@ class App extends PureComponent {
     // and sends out a "viewport-resize" message with the new size.
     window.postMessage({
       type: "viewport-resize",
       width,
       height,
     }, "*");
   }
 
+  /**
+   * Dispatches the rotateViewport action creator. This utilized by the RDM toolbar as
+   * a prop.
+   *
+   * @param {Number} id
+   *        The viewport ID.
+   */
   onRotateViewport(id) {
+    let currentDevice;
+    const viewport = this.props.viewports[id];
+
+    for (const type of this.props.devices.types) {
+      for (const device of this.props.devices[type]) {
+        if (viewport.device === device.name) {
+          currentDevice = device;
+        }
+      }
+    }
+
+    // If no device is selected, then assume the selected device's primary orientation is
+    // opposite of the viewport orientation.
+    if (!currentDevice) {
+      currentDevice = {
+        height: viewport.width,
+        width: viewport.height,
+      };
+    }
+
+    const currentAngle = Services.prefs.getIntPref("devtools.responsive.viewport.angle");
+    // TODO: For Firefox Android, when the device is rotated clock-wise, the angle is
+    // updated to 270 degrees. This is valid since the user-agent determines whether it
+    // will be set to 90 or 270. However, we should update the angle based on the how the
+    // user-agent assigns the angle value.
+    // See https://w3c.github.io/screen-orientation/#dfn-screen-orientation-values-table
+    const angleToRotateTo = currentAngle === 270 ? 0 : 270;
+    const orientation = getOrientation(currentDevice, viewport, angleToRotateTo);
+
+    this.onChangeViewportOrientation(id, orientation);
     this.props.dispatch(rotateViewport(id));
   }
 
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
   }
 
   onToggleLeftAlignment() {
@@ -271,16 +326,17 @@ class App extends PureComponent {
     const {
       onAddCustomDevice,
       onBrowserMounted,
       onChangeDevice,
       onChangeNetworkThrottling,
       onChangePixelRatio,
       onChangeTouchSimulation,
       onChangeUserAgent,
+      onChangeViewportOrientation,
       onContentResize,
       onDeviceListUpdate,
       onEditCustomDevice,
       onExit,
       onRemoveCustomDevice,
       onRemoveDeviceAssociation,
       doResizeViewport,
       onResizeViewport,
@@ -330,16 +386,17 @@ class App extends PureComponent {
           onToggleReloadOnUserAgent,
           onToggleUserAgentInput,
           onUpdateDeviceModal,
         }),
         Viewports({
           screenshot,
           viewports,
           onBrowserMounted,
+          onChangeViewportOrientation,
           onContentResize,
           onRemoveDeviceAssociation,
           doResizeViewport,
           onResizeViewport,
         }),
         devices.isModalOpen ?
           DeviceModal({
             deviceAdderViewportTemplate,
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -7,42 +7,46 @@
 "use strict";
 
 const Services = require("Services");
 const flags = require("devtools/shared/flags");
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
+const { PORTRAIT_PRIMARY, LANDSCAPE_PRIMARY } = require("../constants");
 const e10s = require("../utils/e10s");
 const message = require("../utils/message");
 const { getTopLevelWindow } = require("../utils/window");
 
 const FRAME_SCRIPT = "resource://devtools/client/responsive.html/browser/content.js";
 
 class Browser extends PureComponent {
   /**
    * This component is not allowed to depend directly on frequently changing data (width,
    * height). Any changes in props would cause the <iframe> to be removed and added again,
    * throwing away the current state of the page.
    */
   static get propTypes() {
     return {
       onBrowserMounted: PropTypes.func.isRequired,
+      onChangeViewportOrientation: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       swapAfterMount: PropTypes.bool.isRequired,
       userContextId: PropTypes.number.isRequired,
+      viewportId: PropTypes.number.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onContentResize = this.onContentResize.bind(this);
     this.onResizeViewport = this.onResizeViewport.bind(this);
+    this.onSetScreenOrientation = this.onSetScreenOrientation.bind(this);
   }
 
   /**
    * Before the browser is mounted, listen for `remote-browser-shown` so that we know when
    * the browser is fully ready.  Without waiting for an event such as this, we don't know
    * whether all frame state for the browser is fully initialized (since some happens
    * async after the element is added), and swapping browsers can fail if this state is
    * not ready.
@@ -103,30 +107,40 @@ class Browser extends PureComponent {
     const { onResizeViewport } = this.props;
     const { width, height } = msg.data;
     onResizeViewport({
       width,
       height,
     });
   }
 
+  onSetScreenOrientation(msg) {
+    const { width, height } = msg.data;
+    const angle = Services.prefs.getIntPref("devtools.responsive.viewport.angle");
+    const type = height >= width ? PORTRAIT_PRIMARY : LANDSCAPE_PRIMARY;
+
+    this.props.onChangeViewportOrientation(this.props.viewportId, { type, angle });
+  }
+
   async startFrameScript() {
     const {
       browser,
       onContentResize,
       onResizeViewport,
+      onSetScreenOrientation,
     } = this;
     const mm = browser.frameLoader.messageManager;
 
     // Notify tests when the content has received a resize event.  This is not
     // quite the same timing as when we _set_ a new size around the browser,
     // since it still needs to do async work before the content is actually
     // resized to match.
     e10s.on(mm, "OnContentResize", onContentResize);
     e10s.on(mm, "OnResizeViewport", onResizeViewport);
+    e10s.on(mm, "OnLocationChange", onSetScreenOrientation);
 
     const ready = e10s.once(mm, "ChildScriptReady");
     mm.loadFrameScript(FRAME_SCRIPT, true);
     await ready;
 
     const browserWindow = getTopLevelWindow(window);
     const requiresFloatingScrollbars =
       !browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
@@ -138,21 +152,23 @@ class Browser extends PureComponent {
     });
   }
 
   async stopFrameScript() {
     const {
       browser,
       onContentResize,
       onResizeViewport,
+      onSetScreenOrientation,
     } = this;
     const mm = browser.frameLoader.messageManager;
 
     e10s.off(mm, "OnContentResize", onContentResize);
     e10s.off(mm, "OnResizeViewport", onResizeViewport);
+    e10s.off(mm, "OnLocationChange", onSetScreenOrientation);
     await e10s.request(mm, "Stop");
     message.post(window, "stop-frame-script:done");
   }
 
   render() {
     const {
       userContextId,
     } = this.props;
--- a/devtools/client/responsive.html/components/ResizableViewport.js
+++ b/devtools/client/responsive.html/components/ResizableViewport.js
@@ -18,16 +18,17 @@ const Types = require("../types");
 const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION;
 const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION;
 
 class ResizableViewport extends PureComponent {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
+      onChangeViewportOrientation: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       doResizeViewport: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       swapAfterMount: PropTypes.bool.isRequired,
       viewport: PropTypes.shape(Types.viewport).isRequired,
     };
@@ -145,16 +146,17 @@ class ResizableViewport extends PureComp
   }
 
   render() {
     const {
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
+      onChangeViewportOrientation,
       onContentResize,
       onResizeViewport,
     } = this.props;
 
     let resizeHandleClass = "viewport-resize-handle";
     if (screenshot.isCapturing) {
       resizeHandleClass += " hidden";
     }
@@ -173,17 +175,19 @@ class ResizableViewport extends PureComp
               style: {
                 width: viewport.width + "px",
                 height: viewport.height + "px",
               },
             },
             Browser({
               swapAfterMount,
               userContextId: viewport.userContextId,
+              viewportId: viewport.id,
               onBrowserMounted,
+              onChangeViewportOrientation,
               onContentResize,
               onResizeViewport,
             })
           ),
           dom.div({
             className: resizeHandleClass,
             onMouseDown: this.onResizeStart,
           }),
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -13,29 +13,31 @@ const ResizableViewport = createFactory(
 
 const Types = require("../types");
 
 class Viewports extends PureComponent {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
+      onChangeViewportOrientation: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       doResizeViewport: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
     };
   }
 
   render() {
     const {
       leftAlignmentEnabled,
       onBrowserMounted,
+      onChangeViewportOrientation,
       onContentResize,
       onRemoveDeviceAssociation,
       doResizeViewport,
       onResizeViewport,
       screenshot,
       viewports,
     } = this.props;
 
@@ -66,16 +68,17 @@ class Viewports extends PureComponent {
             id: "viewports",
             className: leftAlignmentEnabled ? "left-aligned" : "",
           },
           viewports.map((viewport, i) => {
             return ResizableViewport({
               key: viewport.id,
               leftAlignmentEnabled,
               onBrowserMounted,
+              onChangeViewportOrientation,
               onContentResize,
               onRemoveDeviceAssociation,
               doResizeViewport,
               onResizeViewport,
               screenshot,
               swapAfterMount: i == 0,
               viewport,
             });
--- a/devtools/client/responsive.html/constants.js
+++ b/devtools/client/responsive.html/constants.js
@@ -1,8 +1,14 @@
 /* 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";
 
 // The minimum viewport width and height
 exports.MIN_VIEWPORT_DIMENSION = 50;
+
+// Orientation types
+exports.PORTRAIT_PRIMARY = "portrait-primary";
+exports.PORTRAIT_SECONDARY = "portrait-secondary";
+exports.LANDSCAPE_PRIMARY = "landscape-primary";
+exports.LANDSCAPE_SECONDARY = "landscape-secondary";
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
+const { getOrientation } = require("./utils/orientation");
 
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/debugger-client", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "throttlingProfiles", "devtools/client/shared/components/throttling/profiles");
 loader.lazyRequireGetter(this, "SettingOnboardingTooltip", "devtools/client/responsive.html/setting-onboarding-tooltip");
 loader.lazyRequireGetter(this, "swapToInnerBrowser", "devtools/client/responsive.html/browser/swap", true);
 loader.lazyRequireGetter(this, "startup", "devtools/client/responsive.html/utils/window", true);
 loader.lazyRequireGetter(this, "message", "devtools/client/responsive.html/utils/message");
@@ -577,19 +578,22 @@ ResponsiveUI.prototype = {
         break;
       case "viewport-resize":
         this.onResizeViewport(event);
         break;
     }
   },
 
   async onChangeDevice(event) {
-    const { userAgent, pixelRatio, touch } = event.data.device;
+    const { device, viewport } = event.data;
+    const { pixelRatio, touch, userAgent } = event.data.device;
+
     let reloadNeeded = false;
     await this.updateDPPX(pixelRatio);
+    await this.updateScreenOrientation(getOrientation(device, viewport), true);
     reloadNeeded |= await this.updateUserAgent(userAgent) &&
                     this.reloadOnChange("userAgent");
     reloadNeeded |= await this.updateTouchSimulation(touch) &&
                     this.reloadOnChange("touchSimulation");
     if (reloadNeeded) {
       this.getViewportBrowser().reload();
     }
     // Used by tests
@@ -660,47 +664,46 @@ ResponsiveUI.prototype = {
     const { width, height } = event.data;
     this.emit("viewport-resize", {
       width,
       height,
     });
   },
 
   async onRotateViewport(event) {
-    const targetFront = await this.client.mainRoot.getTab();
-
-    // Ensure that simulateScreenOrientationChange is supported.
-    if (await targetFront.actorHasMethod("emulation",
-        "simulateScreenOrientationChange")) {
-      // TODO: From event.data, pass orientation and angle as arguments.
-      // See Bug 1357774.
-      await this.emulationFront.simulateScreenOrientationChange();
-    }
+    const { orientationType: type, angle } = event.data;
+    await this.updateScreenOrientation({ type, angle }, false);
   },
 
   /**
    * Restores the previous state of RDM.
    */
   async restoreState() {
     const deviceState = await asyncStorage.getItem("devtools.responsive.deviceState");
     if (deviceState) {
       // Return if there is a device state to restore, this will be done when the
       // device list is loaded after the post-init.
       return;
     }
 
+    const height =
+      Services.prefs.getIntPref("devtools.responsive.viewport.height", 0);
     const pixelRatio =
       Services.prefs.getIntPref("devtools.responsive.viewport.pixelRatio", 0);
     const touchSimulationEnabled =
       Services.prefs.getBoolPref("devtools.responsive.touchSimulation.enabled", false);
     const userAgent = Services.prefs.getCharPref("devtools.responsive.userAgent", "");
+    const width =
+      Services.prefs.getIntPref("devtools.responsive.viewport.width", 0);
 
     let reloadNeeded = false;
+    const viewportOrientation = this.getInitialViewportOrientation({ width, height });
 
     await this.updateDPPX(pixelRatio);
+    await this.updateScreenOrientation(viewportOrientation, true);
 
     if (touchSimulationEnabled) {
       reloadNeeded |= await this.updateTouchSimulation(touchSimulationEnabled) &&
                       this.reloadOnChange("touchSimulation");
     }
     if (userAgent) {
       reloadNeeded |= await this.updateUserAgent(userAgent) &&
                       this.reloadOnChange("userAgent");
@@ -787,16 +790,39 @@ ResponsiveUI.prototype = {
     } else {
       reloadNeeded = await this.emulationFront.clearTouchEventsOverride();
       reloadNeeded |= await this.emulationFront.clearMetaViewportOverride();
     }
     return reloadNeeded;
   },
 
   /**
+   * Sets the screen orientation values of the simulated device.
+   *
+   * @param {Object} orientation
+   *        The orientation to update the current device screen to.
+   * @param {Boolean} changeDevice
+   *        Whether or not the reason for updating the screen orientation is a result
+   *        of actually rotating the device via the RDM toolbar or if the user switched to
+   *        another device.
+   */
+  async updateScreenOrientation(orientation, changeDevice = false) {
+    const targetFront = await this.client.mainRoot.getTab();
+    const simulateOrientationChangeSupported =
+    await targetFront.actorHasMethod("emulation", "simulateScreenOrientationChange");
+
+    // Ensure that simulateScreenOrientationChange is supported.
+    if (simulateOrientationChangeSupported) {
+      const { type, angle } = orientation;
+      await this.emulationFront.simulateScreenOrientationChange(type, angle,
+                                                                changeDevice);
+    }
+  },
+
+  /**
    * Helper for tests. Assumes a single viewport for now.
    */
   getViewportSize() {
     return this.toolWindow.getViewportSize();
   },
 
   /**
    * Helper for tests, etc. Assumes a single viewport for now.
@@ -815,11 +841,17 @@ ResponsiveUI.prototype = {
 
   /**
    * Helper for contacting the viewport content. Assumes a single viewport for now.
    */
   getViewportMessageManager() {
     return this.getViewportBrowser().messageManager;
   },
 
+  /**
+   * Helper for getting the initial viewport orientation.
+   */
+  getInitialViewportOrientation(viewport) {
+    return getOrientation(viewport, viewport);
+  },
 };
 
 EventEmitter.decorate(ResponsiveUI.prototype);
--- a/devtools/client/responsive.html/reducers/viewports.js
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -5,31 +5,34 @@
 "use strict";
 
 const Services = require("Services");
 
 const {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
   CHANGE_PIXEL_RATIO,
+  CHANGE_VIEWPORT_ANGLE,
   EDIT_DEVICE,
   REMOVE_DEVICE_ASSOCIATION,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT,
 } = require("../actions/index");
 
 const VIEWPORT_WIDTH_PREF = "devtools.responsive.viewport.width";
 const VIEWPORT_HEIGHT_PREF = "devtools.responsive.viewport.height";
 const VIEWPORT_PIXEL_RATIO_PREF = "devtools.responsive.viewport.pixelRatio";
+const VIEWPORT_ANGLE_PREF = "devtools.responsive.viewport.angle";
 
 let nextViewportId = 0;
 
 const INITIAL_VIEWPORTS = [];
 const INITIAL_VIEWPORT = {
   id: nextViewportId++,
+  angle: Services.prefs.getIntPref(VIEWPORT_ANGLE_PREF, 0),
   device: "",
   deviceType: "",
   height: Services.prefs.getIntPref(VIEWPORT_HEIGHT_PREF, 480),
   width: Services.prefs.getIntPref(VIEWPORT_WIDTH_PREF, 320),
   pixelRatio: Services.prefs.getIntPref(VIEWPORT_PIXEL_RATIO_PREF, 0),
   userContextId: 0,
 };
 
@@ -74,16 +77,31 @@ const reducers = {
 
       return {
         ...viewport,
         pixelRatio,
       };
     });
   },
 
+  [CHANGE_VIEWPORT_ANGLE](viewports, { id, angle }) {
+    return viewports.map(viewport => {
+      if (viewport.id !== id) {
+        return viewport;
+      }
+
+      Services.prefs.setIntPref(VIEWPORT_ANGLE_PREF, angle);
+
+      return {
+        ...viewport,
+        angle,
+      };
+    });
+  },
+
   [EDIT_DEVICE](viewports, { viewport, newDevice, deviceType }) {
     if (!viewport) {
       return viewports;
     }
 
     return viewports.map(v => {
       if (v.id !== viewport.id) {
         return viewport;
--- a/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js
+++ b/devtools/client/responsive.html/test/browser/browser_orientationchange_event.js
@@ -4,23 +4,34 @@
 "use strict";
 
 // Test that the "orientationchange" event is fired when the "rotate button" is clicked.
 
 const TEST_URL = "data:text/html;charset=utf-8,";
 
 addRDMTask(TEST_URL, async function({ ui }) {
   info("Rotate viewport to trigger 'orientationchange' event.");
+  await pushPref("devtools.responsive.viewport.angle", 0);
   rotateViewport(ui);
 
   await ContentTask.spawn(ui.getViewportBrowser(), {},
     async function() {
+      info("Check the original orientation values before the orientationchange");
+      is(content.screen.orientation.type, "portrait-primary",
+        "Primary orientation type is portrait-primary.");
+      is(content.screen.orientation.angle, 0,
+        "Original angle is set at 0 degrees");
+
       const orientationChange = new Promise(resolve => {
         content.window.addEventListener("orientationchange", () => {
           ok(true, "'orientationchange' event fired");
+          is(content.screen.orientation.type, "landscape-primary",
+            "Orientation state was updated to landscape-primary");
+          is(content.screen.orientation.angle, 270,
+            "Orientation angle was updated to 270 degrees.");
           resolve();
         });
       });
 
       await orientationChange;
     }
   );
 });
--- a/devtools/client/responsive.html/utils/moz.build
+++ b/devtools/client/responsive.html/utils/moz.build
@@ -5,10 +5,11 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'e10s.js',
     'key.js',
     'l10n.js',
     'message.js',
     'notification.js',
+    'orientation.js',
     'window.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/utils/orientation.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PORTRAIT_PRIMARY, LANDSCAPE_PRIMARY } = require("../constants");
+
+/**
+ * Helper that gets the screen orientation of the device displayed in the RDM viewport.
+ * This function take in both a device and viewport object and an optional rotated angle.
+ * If a rotated angle is passed, then we calculate what the orientation type of the device
+ * would be in relation to its current orientation. Otherwise, return the current
+ * orientation and angle.
+ *
+ * @param {Object} device
+ *        The device whose content is displayed in the viewport. Used to determine the
+ *        primary orientation.
+ * @param {Object} viewport
+ *        The viewport displaying device content. Used to determine the current
+ *        orientation type of the device while in RDM.
+ * @param {Number|null} angleToRotateTo
+ *        Optional. The rotated angle specifies the degree to which the device WILL be
+ *        turned to. If undefined, then only return the current orientation and angle
+ *        of the device.
+ * @return {Object} the orientation of the device.
+ */
+function getOrientation(device, viewport, angleToRotateTo = null) {
+  const { width: deviceWidth, height: deviceHeight } = device;
+  const { width: viewportWidth, height: viewportHeight } = viewport;
+
+  // Determine the primary orientation of the device screen.
+  const primaryOrientation = deviceHeight >= deviceWidth ?
+    PORTRAIT_PRIMARY :
+    LANDSCAPE_PRIMARY;
+
+  // Determine the current orientation of the device screen.
+  const currentOrientation = viewportHeight >= viewportWidth ?
+    PORTRAIT_PRIMARY :
+    LANDSCAPE_PRIMARY;
+
+  // Calculate the orientation angle of the device.
+  let angle;
+
+  if (typeof angleToRotateTo === "number") {
+    angle = angleToRotateTo;
+  } else if (currentOrientation !== primaryOrientation) {
+    angle = 270;
+  } else {
+    angle = 0;
+  }
+
+  // Calculate the orientation type of the device.
+  let orientationType = currentOrientation;
+
+  // If the viewport orientation is different from the primary orientation and the angle
+  // to rotate to is 0, then we are moving the device orientation back to its primary
+  // orientation.
+  if (currentOrientation !== primaryOrientation && angleToRotateTo === 0) {
+    orientationType = primaryOrientation;
+  } else if (angleToRotateTo === 90 || angleToRotateTo === 270) {
+    if (currentOrientation.includes("portrait")) {
+      orientationType = LANDSCAPE_PRIMARY;
+    } else if (currentOrientation.includes("landscape")) {
+      orientationType = PORTRAIT_PRIMARY;
+    }
+  }
+
+  return {
+    type: orientationType,
+    angle,
+  };
+}
+
+exports.getOrientation = getOrientation;
--- a/devtools/client/shared/components/Frame.js
+++ b/devtools/client/shared/components/Frame.js
@@ -101,16 +101,17 @@ class Frame extends Component {
       url: source,
       line,
       column,
       functionDisplayName: this.props.frame.functionDisplayName,
       sourceId,
     };
   }
 
+  /* eslint-disable complexity */
   render() {
     let frame, isSourceMapped;
     const {
       onClick,
       showFunctionName,
       showAnonymousFunctionName,
       showHost,
       showEmptyPathAsHost,
@@ -245,11 +246,12 @@ class Frame extends Component {
       elements.push(dom.span({
         key: "host",
         className: "frame-link-host",
       }, unicodeHost));
     }
 
     return dom.span(attributes, ...elements);
   }
+  /* eslint-enable complexity */
 }
 
 module.exports = Frame;
--- a/devtools/client/shared/components/VirtualizedTree.js
+++ b/devtools/client/shared/components/VirtualizedTree.js
@@ -548,16 +548,17 @@ class Tree extends Component {
     });
   }
 
   /**
    * Handles key down events in the tree's container.
    *
    * @param {Event} e
    */
+  /* eslint-disable complexity */
   _onKeyDown(e) {
     if (this.props.focused == null) {
       return;
     }
 
     // Allow parent nodes to use navigation arrows with modifiers.
     if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
       return;
@@ -619,16 +620,17 @@ class Tree extends Component {
         }
 
         if (this.refs.tree !== this.activeElement) {
           this.refs.tree.focus();
         }
         break;
     }
   }
+  /* eslint-enable complexity */
 
   get activeElement() {
     return this.refs.tree.ownerDocument.activeElement;
   }
 
   _focusFirstNode() {
     const traversal = this._dfsFromRoots();
     this._focus(0, traversal[0].item, { alignTo: "top" });
--- a/devtools/client/shared/components/splitter/SplitBox.js
+++ b/devtools/client/shared/components/splitter/SplitBox.js
@@ -194,16 +194,17 @@ class SplitBox extends Component {
       this.setState({
         height: size,
       });
     }
   }
 
   // Rendering
 
+  /* eslint-disable complexity */
   render() {
     const { endPanelControl, splitterSize, vert } = this.state;
     const { startPanel, endPanel, minSize, maxSize } = this.props;
 
     const style = Object.assign({
       // Set the size of the controlled panel (height or width depending on the
       // current state). This can be used to help with styling of dependent
       // panels.
@@ -285,11 +286,12 @@ class SplitBox extends Component {
             ref: div => {
               this.endPanelContainer = div;
             }},
             endPanel
           ) : null
       )
     );
   }
+  /* eslint-enable complexity */
 }
 
 module.exports = SplitBox;
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -265,16 +265,17 @@ define(function(require, exports, module
     }
 
     isExpanded(nodePath) {
       return this.state.expandedNodes.has(nodePath);
     }
 
     // Event Handlers
 
+    /* eslint-disable complexity */
     onKeyDown(event) {
       if (!SUPPORTED_KEYS.includes(event.key)) {
         return;
       }
 
       const row = this.getSelectedRow();
       if (!row) {
         return;
@@ -347,16 +348,17 @@ define(function(require, exports, module
           }
           break;
       }
 
       // Focus should always remain on the tree container itself.
       this.treeRef.current.focus();
       event.preventDefault();
     }
+    /* eslint-enable complexity */
 
     onClickRow(nodePath, event) {
       const onClickRow = this.props.onClickRow;
 
       if (onClickRow) {
         onClickRow.call(this, nodePath, event);
         return;
       }
--- a/devtools/client/shared/natural-sort.js
+++ b/devtools/client/shared/natural-sort.js
@@ -43,16 +43,17 @@ function naturalSortCaseInsensitive(a, b
  *
  * @param  {Object} a
  *         Passed in by Array.sort(a, b)
  * @param  {Object} b
  *         Passed in by Array.sort(a, b)
  * @param  {Boolean} insensitive
  *         Should the search be case insensitive?
  */
+/* eslint-disable complexity */
 function naturalSort(a, b, insensitive) {
   // convert all to strings strip whitespace
   const i = function(s) {
     return (insensitive && ("" + s).toLowerCase() || "" + s)
                                    .replace(sre, "");
   };
   const x = i(a) || "";
   const y = i(b) || "";
@@ -99,8 +100,9 @@ function naturalSort(a, b, insensitive) 
     if (oFxNcL < oFyNcL) {
       return -1;
     } else if (oFxNcL > oFyNcL) {
       return 1;
     }
   }
   return null;
 }
+/* eslint-enable complexity */
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -649,16 +649,17 @@ OutputParser.prototype = {
    * adding it to the given container node.
    *
    * @param {String} coords
    *        The string of coordinate pairs.
    * @param {Node} container
    *        The node to which spans containing points are added.
    * @returns {Node} The container to which spans have been added.
    */
+  /* eslint-disable complexity */
   _addPolygonPointNodes: function(coords, container) {
     const tokenStream = getCSSLexer(coords);
     let token = tokenStream.nextToken();
     let coord = "";
     let i = 0;
     let depth = 0;
     let isXCoord = true;
     let fillRule = false;
@@ -753,27 +754,29 @@ OutputParser.prototype = {
         "data-point": `${i}`,
         "data-pair": (isXCoord) ? "x" : "y",
       }, coord);
       coordNode.appendChild(node);
       container.appendChild(coordNode);
     }
     return container;
   },
+  /* eslint-enable complexity */
 
   /**
    * Parse the given circle coordinates and populate the given container appropriately
    * with a separate span for the center point.
    *
    * @param {String} coords
    *        The circle definition.
    * @param {Node} container
    *        The node to which the definition is added.
    * @returns {Node} The container to which the definition has been added.
    */
+  /* eslint-disable complexity */
   _addCirclePointNodes: function(coords, container) {
     const tokenStream = getCSSLexer(coords);
     let token = tokenStream.nextToken();
     let depth = 0;
     let coord = "";
     let point = "radius";
     const centerNode = this._createNode("span", {
       class: "ruleview-shape-point",
@@ -869,27 +872,29 @@ OutputParser.prototype = {
       }
     }
 
     if (centerNode.textContent) {
       container.appendChild(centerNode);
     }
     return container;
   },
+  /* eslint-enable complexity */
 
   /**
    * Parse the given ellipse coordinates and populate the given container appropriately
    * with a separate span for each point
    *
    * @param {String} coords
    *        The ellipse definition.
    * @param {Node} container
    *        The node to which the definition is added.
    * @returns {Node} The container to which the definition has been added.
    */
+  /* eslint-disable complexity */
   _addEllipsePointNodes: function(coords, container) {
     const tokenStream = getCSSLexer(coords);
     let token = tokenStream.nextToken();
     let depth = 0;
     let coord = "";
     let point = "rx";
     const centerNode = this._createNode("span", {
       class: "ruleview-shape-point",
@@ -995,26 +1000,28 @@ OutputParser.prototype = {
       }
     }
 
     if (centerNode.textContent) {
       container.appendChild(centerNode);
     }
     return container;
   },
+  /* eslint-enable complexity */
 
   /**
    * Parse the given inset coordinates and populate the given container appropriately.
    *
    * @param {String} coords
    *        The inset definition.
    * @param {Node} container
    *        The node to which the definition is added.
    * @returns {Node} The container to which the definition has been added.
    */
+  /* eslint-disable complexity */
   _addInsetPointNodes: function(coords, container) {
     const insetPoints = ["top", "right", "bottom", "left"];
     const tokenStream = getCSSLexer(coords);
     let token = tokenStream.nextToken();
     let depth = 0;
     let coord = "";
     let i = 0;
     let round = false;
@@ -1118,16 +1125,17 @@ OutputParser.prototype = {
     if (otherText[nodes.length]) {
       for (const text of otherText[nodes.length]) {
         appendText(container, text);
       }
     }
 
     return container;
   },
+  /* eslint-enable complexity */
 
   /**
    * Append a angle value to the output
    *
    * @param {String} angle
    *        angle to append
    * @param {Object} options
    *        Options object. For valid options and default values see
--- a/devtools/client/shared/source-utils.js
+++ b/devtools/client/shared/source-utils.js
@@ -265,16 +265,17 @@ function isContentScheme(location, i = 0
       }
       return false;
 
     default:
       return false;
   }
 }
 
+/* eslint-disable complexity */
 function isChromeScheme(location, i = 0) {
   const firstChar = location.charCodeAt(i);
 
   switch (firstChar) {
     // "chrome://"
     case CHAR_CODE_C:
       if (location.charCodeAt(++i) === CHAR_CODE_H &&
           location.charCodeAt(++i) === CHAR_CODE_R &&
@@ -310,16 +311,17 @@ function isChromeScheme(location, i = 0)
         return isColonSlashSlash(location, i);
       }
       return false;
 
     default:
       return false;
   }
 }
+/* eslint-enable complexity */
 
 function isWASM(location, i = 0) {
   return (
     // "wasm-function["
     location.charCodeAt(i) === CHAR_CODE_W &&
     location.charCodeAt(++i) === CHAR_CODE_A &&
     location.charCodeAt(++i) === CHAR_CODE_S &&
     location.charCodeAt(++i) === CHAR_CODE_M &&
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -666,16 +666,17 @@ class Telemetry {
 
 /**
  * Returns the telemetry charts for a specific tool.
  *
  * @param {String} id
  *        The ID of the tool that has been opened.
  *
  */
+/* eslint-disable complexity */
 function getChartsFromToolId(id) {
   if (!id) {
     return null;
   }
 
   const lowerCaseId = id.toLowerCase();
 
   let useTimedEvent = null;
@@ -745,16 +746,17 @@ function getChartsFromToolId(id) {
 
   return {
     useTimedEvent: useTimedEvent,
     timerHist: timerHist,
     countHist: countHist,
     countScalar: countScalar,
   };
 }
+/* eslint-enable complexity */
 
 /**
  * Displays the first caller and calling line outside of this file in the
  * event of an error. This is the line that made the call that produced the
  * error.
  */
 function getCaller() {
   return getNthPathExcluding(0, "/telemetry.js");
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -794,16 +794,17 @@ VariablesView.prototype = {
     clipboardHelper.copyString(
       item._nameString + item.separatorStr + item._valueString
     );
   },
 
   /**
    * Listener handling a key down event on the view.
    */
+  /* eslint-disable complexity */
   _onViewKeyDown: function(e) {
     const item = this.getFocusedItem();
 
     // Prevent scrolling when pressing navigation keys.
     ViewHelpers.preventScrolling(e);
 
     switch (e.keyCode) {
       case KeyCodes.DOM_VK_C:
@@ -884,16 +885,17 @@ VariablesView.prototype = {
           item._onDelete(e);
         }
         return;
 
       case KeyCodes.DOM_VK_INSERT:
         item._onAddProperty(e);
     }
   },
+  /* eslint-enable complexity */
 
   /**
    * Sets the text displayed in this container when there are no available items.
    * @param string aValue
    */
   set emptyText(aValue) {
     if (this._emptyTextNode) {
       this._emptyTextNode.setAttribute("value", aValue);
@@ -2788,16 +2790,17 @@ Variable.prototype = extend(Scope.protot
     }
   },
 
   /**
    * Sets a variable's configurable, enumerable and writable attributes,
    * and specifies if it's a 'this', '<exception>', '<return>' or '__proto__'
    * reference.
    */
+  /* eslint-disable complexity */
   _setAttributes: function() {
     const ownerView = this.ownerView;
     if (ownerView.preventDescriptorModifiers) {
       return;
     }
 
     const descriptor = this._initialDescriptor;
     const target = this._target;
@@ -2845,16 +2848,17 @@ Variable.prototype = extend(Scope.protot
       target.setAttribute("proto", "");
       target.setAttribute("pseudo-item", "");
     }
 
     if (Object.keys(descriptor).length == 0) {
       target.setAttribute("pseudo-item", "");
     }
   },
+  /* eslint-enable complexity */
 
   /**
    * Adds the necessary event listeners for this variable.
    */
   _addEventListeners: function() {
     this._name.addEventListener("dblclick", this._activateNameInput);
     this._valueLabel.addEventListener("mousedown", this._activateValueInput);
     this._title.addEventListener("mousedown", this._onClick);
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -501,16 +501,17 @@ class JSTerm extends Component {
 
   /**
    * The JavaScript evaluation response handler.
    *
    * @private
    * @param {Object} response
    *        The message received from the server.
    */
+  /* eslint-disable complexity */
   async _executeResultCallback(response) {
     if (!this.webConsoleUI) {
       return null;
     }
 
     if (response.error) {
       console.error("Evaluation error " + response.error + ": " + response.message);
       return null;
@@ -573,16 +574,17 @@ class JSTerm extends Component {
     }
 
     if (this.webConsoleUI.wrapper) {
       return this.webConsoleUI.wrapper.dispatchMessageAdd(response, true);
     }
 
     return null;
   }
+  /* eslint-enable complexity */
 
   screenshotNotify(results) {
     const wrappedResults = results.map(message => ({ message, type: "logMessage" }));
     this.webConsoleUI.wrapper.dispatchMessagesAdd(wrappedResults);
   }
 
   /**
    * Execute a string. Execution happens asynchronously in the content process.
--- a/devtools/client/webconsole/components/Message.js
+++ b/devtools/client/webconsole/components/Message.js
@@ -152,16 +152,17 @@ class Message extends Component {
       level,
       onRewindClick: (serviceContainer.canRewind() && executionPoint)
         ? () => serviceContainer.jumpToExecutionPoint(executionPoint, messageId)
         : null,
       type,
     });
   }
 
+  /* eslint-disable complexity */
   render() {
     const {
       open,
       collapsible,
       collapseTitle,
       source,
       type,
       isPaused,
@@ -341,11 +342,12 @@ class Message extends Component {
         ),
         attachment,
         ...notesNodes
       ),
       // If an attachment is displayed, the final newline is handled by the attachment.
       attachment ? null : dom.br(),
     );
   }
+  /* eslint-enable complexity */
 }
 
 module.exports = Message;
--- a/devtools/client/webconsole/components/ReverseSearchInput.js
+++ b/devtools/client/webconsole/components/ReverseSearchInput.js
@@ -63,16 +63,17 @@ class ReverseSearchInput extends Compone
       prevProps.visible === false &&
       this.props.visible === true &&
       this.props.initialValue
     ) {
       this.inputNode.value = this.props.initialValue;
     }
   }
 
+  /* eslint-disable complexity */
   onInputKeyDown(event) {
     const {
       keyCode,
       key,
       ctrlKey,
       shiftKey,
     } = event;
 
@@ -122,16 +123,17 @@ class ReverseSearchInput extends Compone
     ) {
       event.stopPropagation();
       event.preventDefault();
       if (canNavigate) {
         dispatch(actions.showReverseSearchNext());
       }
     }
   }
+  /* eslint-enable complexity */
 
   renderSearchInformation() {
     const {
       reverseSearchTotalResults,
       reverseSearchResultPosition,
     } = this.props;
 
     if (!Number.isInteger(reverseSearchTotalResults)) {
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -100,16 +100,17 @@ function cloneState(state) {
  *
  * @param {ConsoleMessage} newMessage: The message to add to the state.
  * @param {MessageState} state: The message state ( = managed by this reducer).
  * @param {FiltersState} filtersState: The filters state.
  * @param {PrefsState} prefsState: The preferences state.
  * @param {UiState} uiState: The ui state.
  * @returns {MessageState} a new messages state.
  */
+/* eslint-disable complexity */
 function addMessage(newMessage, state, filtersState, prefsState, uiState) {
   const {
     messagesById,
     groupsById,
     currentGroup,
     repeatById,
   } = state;
 
@@ -269,17 +270,19 @@ function addMessage(newMessage, state, f
   // Append received network-data also into networkMessagesUpdateById
   // that is responsible for collecting (lazy loaded) HTTP payload data.
   if (newMessage.source == "network") {
     state.networkMessagesUpdateById[newMessage.actor] = newMessage;
   }
 
   return state;
 }
+/* eslint-enable complexity */
 
+/* eslint-disable complexity */
 function messages(state = MessageState(), action, filtersState, prefsState, uiState) {
   const {
     messagesById,
     messagesPayloadById,
     messagesUiById,
     messagesTableDataById,
     networkMessagesUpdateById,
     groupsById,
@@ -536,16 +539,17 @@ function messages(state = MessageState()
       };
       maybeSortVisibleMessages(filteredState, true);
 
       return filteredState;
   }
 
   return state;
 }
+/* eslint-enable complexity */
 
 /**
  * Returns the new current group id given the previous current group and the groupsById
  * state property.
  *
  * @param {String} currentGroup: id of the current group
  * @param {Map} groupsById
  * @param {Array} ignoredIds: An array of ids which can't be the new current group.
@@ -779,16 +783,17 @@ function getToplevelMessageCount(state) 
  *                   - {Boolean} checkParentWarningGroupVisibility: Set to false to not
  *                                 check if a message should be visible because it is in a
  *                                 warningGroup and the warningGroup is visible.
  *
  * @return {Object} An object of the following form:
  *         - visible {Boolean}: true if the message should be visible
  *         - cause {String}: if visible is false, what causes the message to be hidden.
  */
+/* eslint-disable complexity */
 function getMessageVisibility(message, {
     messagesState,
     filtersState,
     prefsState,
     uiState,
     checkGroup = true,
     checkParentWarningGroupVisibility = true,
 }) {
@@ -949,16 +954,17 @@ function getMessageVisibility(message, {
       cause: FILTERS.TEXT,
     };
   }
 
   return {
     visible: true,
   };
 }
+/* eslint-enable complexity */
 
 function isUnfilterable(message) {
   return [
     MESSAGE_TYPE.COMMAND,
     MESSAGE_TYPE.RESULT,
     MESSAGE_TYPE.START_GROUP,
     MESSAGE_TYPE.START_GROUP_COLLAPSED,
     MESSAGE_TYPE.NAVIGATION_MARKER,
--- a/devtools/client/webconsole/test/mocha-test-setup.js
+++ b/devtools/client/webconsole/test/mocha-test-setup.js
@@ -70,16 +70,17 @@ if (!global.URLSearchParams) {
 // Mock ChromeUtils.
 global.ChromeUtils = {
   import: () => {},
   defineModuleGetter: () => {},
 };
 
 // Point to vendored-in files and mocks when needed.
 const requireHacker = require("require-hacker");
+/* eslint-disable complexity */
 requireHacker.global_hook("default", (path, module) => {
   switch (path) {
     // For Enzyme
     case "react-dom":
       return getModule("devtools/client/shared/vendor/react-dom");
     case "react-dom/server":
       return getModule("devtools/client/shared/vendor/react-dom-server");
     case "react-dom/test-utils":
@@ -133,14 +134,15 @@ requireHacker.global_hook("default", (pa
   // We need to rewrite all the modules assuming the root is mozilla-central and give them
   // an absolute path.
   if (path.startsWith("devtools/")) {
     return getModule(path);
   }
 
   return undefined;
 });
+/* eslint-enable complexity */
 
 // Configure enzyme with React 16 adapter. This needs to be done after we set the
 // requireHack hook so `require()` calls in Enzyme are handled as well.
 const Enzyme = require("enzyme");
 const Adapter = require("enzyme-adapter-react-16");
 Enzyme.configure({ adapter: new Adapter() });
--- a/devtools/client/webconsole/utils/messages.js
+++ b/devtools/client/webconsole/utils/messages.js
@@ -62,16 +62,17 @@ function transformPacket(packet) {
 
     case "evaluationResult":
     default: {
       return transformEvaluationResultPacket(packet);
     }
   }
 }
 
+/* eslint-disable complexity */
 function transformConsoleAPICallPacket(packet) {
   const { message } = packet;
 
   let parameters = message.arguments;
   let type = message.level;
   let level = getLevelFromType(type);
   let messageText = null;
   const timer = message.timer;
@@ -193,16 +194,17 @@ function transformConsoleAPICallPacket(p
     userProvidedStyles: message.styles,
     prefix: message.prefix,
     private: message.private,
     executionPoint: message.executionPoint,
     logpointId: message.logpointId,
     chromeContext: message.chromeContext,
   });
 }
+/* eslint-enable complexity */
 
 function transformNavigationMessagePacket(packet) {
   const { url } = packet;
   return new ConsoleMessage({
     source: MESSAGE_SOURCE.CONSOLE_API,
     type: MESSAGE_TYPE.NAVIGATION_MARKER,
     level: MESSAGE_LEVEL.LOG,
     messageText: l10n.getFormatStr("webconsole.navigated", [url]),
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -841,17 +841,17 @@ var UI = {
   },
 
   createToolbox: function() {
     // If |this.toolboxPromise| exists, there is already a live toolbox
     if (this.toolboxPromise) {
       return this.toolboxPromise;
     }
 
-    const iframe = document.createElement("iframe");
+    const iframe = document.createXULElement("iframe");
     iframe.id = "toolbox";
 
     // Compute a uid on the iframe in order to identify toolbox iframe
     // when receiving toolbox-close event
     iframe.uid = new Date().getTime();
 
     const height = Services.prefs.getIntPref("devtools.toolbox.footer.height");
     iframe.height = height;
--- a/devtools/server/actors/accessibility/walker.js
+++ b/devtools/server/actors/accessibility/walker.js
@@ -500,16 +500,17 @@ const AccessibleWalkerActor = ActorClass
   },
 
   /**
    * Accessible event observer function.
    *
    * @param {Ci.nsIAccessibleEvent} subject
    *                                      accessible event object.
    */
+  /* eslint-disable complexity */
   observe(subject) {
     const event = subject.QueryInterface(Ci.nsIAccessibleEvent);
     const rawAccessible = event.accessible;
     const accessible = this.getRef(rawAccessible);
 
     if ((rawAccessible instanceof Ci.nsIAccessibleDocument) && !accessible) {
       const rootDocAcc = this.getRawAccessibleFor(this.rootDoc);
       if (rawAccessible === rootDocAcc && !isStale(rawAccessible)) {
@@ -595,16 +596,17 @@ const AccessibleWalkerActor = ActorClass
         if (accessible) {
           events.emit(accessible, "shortcut-change", accessible.keyboardShortcut);
         }
         break;
       default:
         break;
     }
   },
+  /* eslint-enable complexity */
 
   /**
    * Ensure that nothing interferes with the audit for an accessible object
    * (CSS, overlays) by load accessibility highlighter style sheet used for
    * preventing transitions and applying transparency when calculating colour
    * contrast as well as temporarily hiding accessible highlighter overlay.
    * @param  {Object} win
    *         Window where highlighting happens.
--- a/devtools/server/actors/addon/webextension-inspected-window.js
+++ b/devtools/server/actors/addon/webextension-inspected-window.js
@@ -458,16 +458,17 @@ var WebExtensionInspectedWindowActor = p
      *
      * @param {DOMWindow|undefined} customTargetWindow
      *   Used in the CustomizedReload instances to evaluate the `injectedScript`
      *   javascript code in every sub-frame of the target window during the tab reload.
      *   NOTE: this parameter is not part of the RDP protocol exposed by this actor, when
      *   it is called over the remote debugging protocol the target window is always
      *   `targetActor.window`.
      */
+    /* eslint-disable complexity */
     eval(callerInfo, expression, options, customTargetWindow) {
       const window = customTargetWindow || this.window;
       options = options || {};
 
       const extensionPolicy = WebExtensionPolicy.getByID(callerInfo.addonId);
 
       if (!extensionPolicy) {
         return createExceptionInfoResult({
@@ -634,12 +635,13 @@ var WebExtensionInspectedWindowActor = p
               String(err),
             ],
           });
         }
       }
 
       return {value: evalResult};
     },
+    /* eslint-enable complexity */
   }
 );
 
 exports.WebExtensionInspectedWindowActor = WebExtensionInspectedWindowActor;
--- a/devtools/server/actors/emulation.js
+++ b/devtools/server/actors/emulation.js
@@ -71,16 +71,20 @@ const EmulationActor = protocol.ActorCla
   get touchSimulator() {
     if (!this._touchSimulator) {
       this._touchSimulator = new TouchSimulator(this.targetActor.chromeEventHandler);
     }
 
     return this._touchSimulator;
   },
 
+  get win() {
+    return this.docShell.chromeEventHandler.ownerGlobal;
+  },
+
   onWillNavigate({ isTopLevel }) {
     // Make sure that print simulation is stopped before navigating to another page. We
     // need to do this since the browser will cache the last state of the page in its
     // session history.
     if (this._printSimulationEnabled && isTopLevel) {
       this.stopPrintMediaSimulation(true);
     }
   },
@@ -347,23 +351,44 @@ const EmulationActor = protocol.ActorCla
    *        navigating to the next page. Defaults to false, meaning we want to completely
    *        stop print simulation.
    */
   async stopPrintMediaSimulation(state = false) {
     this._printSimulationEnabled = state;
     this.targetActor.docShell.contentViewer.stopEmulatingMedium();
   },
 
+  setScreenOrientation(type, angle) {
+    if (this.win.screen.orientation.angle !== angle ||
+        this.win.screen.orientation.type !== type) {
+      this.win.document.setRDMPaneOrientation(type, angle);
+    }
+  },
+
   /**
    * Simulates the "orientationchange" event when device screen is rotated.
    *
-   * TODO: Update `window.screen.orientation` and `window.screen.angle` here.
-   * See Bug 1357774.
+   * @param {String} type
+   *        The orientation type of the rotated device.
+   * @param {Number} angle
+   *        The rotated angle of the device.
+   * @param {Boolean} deviceChange
+   *        Whether or not screen orientation change is a result of changing the device
+   *        or rotating the current device. If the latter, then dispatch the
+   *        "orientationchange" event on the content window.
    */
-  simulateScreenOrientationChange() {
-    const win = this.docShell.chromeEventHandler.ownerGlobal;
-    const { CustomEvent } = win;
+  async simulateScreenOrientationChange(type, angle, deviceChange) {
+    // Don't dispatch the "orientationchange" event if orientation change is a result
+    // of switching to a new device.
+    if (deviceChange) {
+      this.setScreenOrientation(type, angle);
+      return;
+    }
+
+    const { CustomEvent } = this.win;
     const orientationChangeEvent = new CustomEvent("orientationchange");
-    win.dispatchEvent(orientationChangeEvent);
+
+    this.setScreenOrientation(type, angle);
+    this.win.dispatchEvent(orientationChangeEvent);
   },
 });
 
 exports.EmulationActor = EmulationActor;
--- a/devtools/server/actors/emulation/touch-simulator.js
+++ b/devtools/server/actors/emulation/touch-simulator.js
@@ -83,16 +83,17 @@ TouchSimulator.prototype = {
    * touch events.
    * False means the element picker is not active and it is ok to emulate touch events.
    * @param {Boolean} state
    */
   setElementPickerState(state) {
     this._isPicking = state;
   },
 
+  /* eslint-disable complexity */
   handleEvent(evt) {
     // Bail out if devtools is in pick mode in the same tab.
     if (this._isPicking) {
       return;
     }
 
     // The gaia system window use an hybrid system even on the device which is
     // a mix of mouse/touch events. So let's not cancel *all* mouse events
@@ -237,16 +238,17 @@ TouchSimulator.prototype = {
       this.sendTouchEvent(evt, target, type);
     }
 
     if (!isSystemWindow) {
       evt.preventDefault();
       evt.stopImmediatePropagation();
     }
   },
+  /* eslint-enable complexity */
 
   fireMouseEvent(type, evt) {
     const content = this.getContent(evt.target);
     const utils = content.windowUtils;
     utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0,
                          evt.MOZ_SOURCE_TOUCH);
   },
 
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -545,16 +545,17 @@ class FlexboxHighlighter extends AutoRef
     }
 
     this.ctx.restore();
   }
 
   /**
    * Clear the whole alignment container along the main axis for each flex item.
    */
+  /* eslint-disable complexity */
   renderJustifyContent() {
     if (!this.flexData || !this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     const { width: containerWidth, height: containerHeight } =
       getUntransformedQuad(this.container, "content").getBounds();
 
@@ -625,16 +626,17 @@ class FlexboxHighlighter extends AutoRef
           this.drawJustifyContent(containerWidth - crossStart - crossSize, mainStart,
             containerWidth - crossStart, containerHeight);
           break;
       }
     }
 
     this.ctx.restore();
   }
+  /* eslint-enable complexity */
 
   /**
    * Set up the canvas with the given options prior to drawing.
    *
    * @param {String} [options.lineDash = null]
    *        An Array of numbers that specify distances to alternately draw a
    *        line and a gap (in coordinate space units). If the number of
    *        elements in the array is odd, the elements of the array get copied
@@ -819,16 +821,17 @@ function getRectFromFlexItemValues(item,
  * Returns whether or not the flex data has changed.
  *
  * @param  {Flex} oldFlexData
  *         The old Flex data object.
  * @param  {Flex} newFlexData
  *         The new Flex data object.
  * @return {Boolean} true if the flex data has changed and false otherwise.
  */
+/* eslint-disable complexity */
 function compareFlexData(oldFlexData, newFlexData) {
   if (!oldFlexData || !newFlexData) {
     return true;
   }
 
   const oldLines = oldFlexData.lines;
   const newLines = newFlexData.lines;
 
@@ -879,10 +882,11 @@ function compareFlexData(oldFlexData, ne
           oldItemRect.height !== newItemRect.height) {
         return true;
       }
     }
   }
 
   return false;
 }
+/* eslint-enable complexity */
 
 exports.FlexboxHighlighter = FlexboxHighlighter;
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -473,16 +473,17 @@ class ShapesHighlighter extends AutoRefr
     const { clientHeight, clientWidth } = this.win.document.documentElement;
     const left = pageXOffset + padding - xOffset;
     const right = clientWidth + pageXOffset - padding - xOffset;
     const top = pageYOffset + padding - yOffset;
     const bottom = clientHeight + pageYOffset - padding - yOffset;
     this.viewport = { left, right, top, bottom, padding };
   }
 
+  /* eslint-disable complexity */
   handleEvent(event, id) {
     // No event handling if the highlighter is hidden
     if (this.areShapesHidden()) {
       return;
     }
 
     let { target, type, pageX, pageY } = event;
 
@@ -572,16 +573,17 @@ class ShapesHighlighter extends AutoRefr
             return;
           }
 
           this._deletePolygonPoint(index);
         }
         break;
     }
   }
+  /* eslint-enable complexity */
 
   /**
    * Handle a mouse click in transform mode.
    * @param {Number} pageX the x coordinate of the mouse
    * @param {Number} pageY the y coordinate of the mouse
    */
   _handleTransformClick(pageX, pageY) {
     const { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
@@ -1699,16 +1701,17 @@ class ShapesHighlighter extends AutoRefr
 
   /**
    * Check if the edges of the inset highlighter is at given coords
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
    * @param {Number} pageY the y coordinate on the page, in % relative to the element
    * @returns {String} "top", "left", "right", or "bottom" if any of those edges were
    *          clicked. "" if none were clicked.
    */
+  /* eslint-disable complexity */
   getInsetPointAt(pageX, pageY) {
     const { top, left, right, bottom } = this.coordinates;
     const zoom = getCurrentZoom(this.win);
     const { width, height } = this.currentDimensions;
     const clickWidthX = LINE_CLICK_WIDTH * 100 / width;
     const clickWidthY = LINE_CLICK_WIDTH * 100 / height;
     const clickRadiusX = BASE_MARKER_SIZE / zoom * 100 / width;
     const clickRadiusY = BASE_MARKER_SIZE / zoom * 100 / height;
@@ -1736,16 +1739,17 @@ class ShapesHighlighter extends AutoRefr
     if ((pageY >= 100 - bottom - clickWidthY && pageY <= 100 - bottom + clickWidthY &&
         pageX >= left && pageX <= 100 - right) ||
         clickedOnPoint(pageX, pageY, centerX, 100 - bottom, clickRadiusX, clickRadiusY)) {
       return "bottom";
     }
 
     return "";
   }
+  /* eslint-enable complexity */
 
   /**
    * Parses the CSS definition given and returns the shape type associated
    * with the definition and the coordinates necessary to draw the shape.
    * @param {String} definition the input CSS definition
    * @returns {Object} null if the definition is not of a known shape type,
    *          or an object of the type { shapeType, coordinates }, where
    *          shapeType is the name of the shape and coordinates are an array
--- a/devtools/server/actors/inspector/event-collector.js
+++ b/devtools/server/actors/inspector/event-collector.js
@@ -380,16 +380,17 @@ class DOMEventCollector extends MainEven
     return handlers;
   }
 }
 
 /**
  * Get or detect jQuery events.
  */
 class JQueryEventCollector extends MainEventCollector {
+  /* eslint-disable complexity */
   getListeners(node, {checkOnly} = {}) {
     const jQuery = this.getJQuery(node);
     const handlers = [];
 
     // If jQuery is not on the page, if this is an anonymous node or a pseudo
     // element we need to return early.
     if (!jQuery || isNativeAnonymous(node) || isMarkerPseudoElement(node) ||
         isBeforePseudoElement(node) || isAfterPseudoElement(node)) {
@@ -466,22 +467,24 @@ class JQueryEventCollector extends MainE
       }
     }
 
     if (checkOnly) {
       return false;
     }
     return handlers;
   }
+  /* eslint-enable complexity */
 }
 
 /**
  * Get or detect jQuery live events.
  */
 class JQueryLiveEventCollector extends MainEventCollector {
+  /* eslint-disable complexity */
   getListeners(node, {checkOnly} = {}) {
     const jQuery = this.getJQuery(node);
     const handlers = [];
 
     if (!jQuery) {
       if (checkOnly) {
         return false;
       }
@@ -564,16 +567,17 @@ class JQueryLiveEventCollector extends M
       }
     }
 
     if (checkOnly) {
       return false;
     }
     return handlers;
   }
+  /* eslint-enable complexity */
 
   normalizeListener(handlerDO) {
     function isFunctionInProxy(funcDO) {
       // If the anonymous function is inside the |proxy| function and the
       // function only has guessed atom, the guessed atom should starts with
       // "proxy/".
       const displayName = funcDO.displayName;
       if (displayName && displayName.startsWith("proxy/")) {
@@ -861,16 +865,17 @@ class EventCollector {
    *             DOM0: true,
    *             capturing: true,
    *             hide: {
    *               DOM0: true
    *             },
    *             native: false
    *           }
    */
+  /* eslint-disable complexity */
   processHandlerForEvent(listenerArray, listener, dbg) {
     let globalDO;
 
     try {
       const { capturing, handler } = listener;
 
       let listenerDO;
       if (isReplaying) {
@@ -1008,11 +1013,12 @@ class EventCollector {
       listenerArray.push(eventObj);
     } finally {
       // Ensure that we always remove the debuggee.
       if (globalDO) {
         dbg.removeDebuggee(globalDO);
       }
     }
   }
+  /* eslint-enable complexity */
 }
 
 exports.EventCollector = EventCollector;
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -723,16 +723,17 @@ var WalkerActor = protocol.ActorClassWit
    *    See JSDoc for children()
    * @param object options
    *    See JSDoc for children()
    * @return  an object with three items:
    *    hasFirst: true if the first child of the node is included in the list.
    *    hasLast: true if the last child of the node is included in the list.
    *    nodes: Array of DOMNodes.
    */
+  /* eslint-disable complexity */
   _getChildren: function(node, options = {}) {
     if (isNodeDead(node)) {
       return { hasFirst: true, hasLast: true, nodes: [] };
     }
 
     if (options.center && options.start) {
       throw Error("Can't specify both 'center' and 'start' options.");
     }
@@ -896,16 +897,17 @@ var WalkerActor = protocol.ActorClassWit
           this.getNativeAnonymousChildren(node.rawNode) : []),
         // ::after
         ...(hasAfter ? [last] : []),
       ];
     }
 
     return { hasFirst, hasLast, nodes };
   },
+  /* eslint-enable complexity */
 
   getNativeAnonymousChildren: function(rawNode) {
     // Get an anonymous walker and start on the first child.
     const walker = this.getDocumentWalker(rawNode);
     let node = walker.firstChild();
 
     const nodes = [];
     while (node) {
@@ -1088,16 +1090,17 @@ var WalkerActor = protocol.ActorClassWit
    *
    * @param string query
    *        The selector query being completed
    * @param string completing
    *        The exact token being completed out of the query
    * @param string selectorState
    *        One of "pseudo", "id", "tag", "class", "null"
    */
+  /* eslint-disable complexity */
   getSuggestionsForQuery: function(query, completing, selectorState) {
     const sugs = {
       classes: new Map(),
       tags: new Map(),
       ids: new Map(),
     };
     let result = [];
     let nodes = null;
@@ -1230,16 +1233,17 @@ var WalkerActor = protocol.ActorClassWit
 
     result = result.slice(0, 25);
 
     return {
       query: query,
       suggestions: result,
     };
   },
+  /* eslint-enable complexity */
 
   /**
    * Add a pseudo-class lock to a node.
    *
    * @param NodeActor node
    * @param string pseudo
    *    A pseudoclass: ':hover', ':active', ':focus', ':focus-within'
    * @param options
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -324,16 +324,17 @@ const proto = {
    *        The array that holds the list of known ownProperties names for
    *        |this.obj|.
    * @param number [limit=0]
    *        Optional limit of getter values to find.
    * @return object
    *         An object that maps property names to safe getter descriptors as
    *         defined by the remote debugging protocol.
    */
+  /* eslint-disable complexity */
   _findSafeGetterValues: function(ownProperties, limit = 0) {
     const safeGetterValues = Object.create(null);
     let obj = this.obj;
     let level = 0, i = 0;
 
     // Do not search safe getters in unsafe objects.
     if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
       return safeGetterValues;
@@ -427,16 +428,17 @@ const proto = {
       }
 
       obj = obj.proto;
       level++;
     }
 
     return safeGetterValues;
   },
+  /* eslint-enable complexity */
 
   /**
    * Find the safe getters for a given Debugger.Object. Safe getters are native
    * getters which are safe to execute.
    *
    * @private
    * @param Debugger.Object object
    *        The Debugger.Object where you want to find safe getters.
--- a/devtools/server/actors/object/previewers.js
+++ b/devtools/server/actors/object/previewers.js
@@ -350,16 +350,17 @@ function wrappedPrimitivePreviewer(class
     return false;
   }
 
   grip.preview.wrappedValue =
     hooks.createValueGrip(ObjectUtils.makeDebuggeeValueIfNeeded(obj, v));
   return true;
 }
 
+/* eslint-disable complexity */
 function GenericObject(objectActor, grip, rawObj, specialStringBehavior = false) {
   const {obj, hooks} = objectActor;
   if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
     return false;
   }
 
   let i = 0, names = [], symbols = [];
   const preview = grip.preview = {
@@ -437,16 +438,17 @@ function GenericObject(objectActor, grip
   if (i < OBJECT_PREVIEW_MAX_ITEMS) {
     preview.safeGetterValues = objectActor._findSafeGetterValues(
       Object.keys(preview.ownProperties),
       OBJECT_PREVIEW_MAX_ITEMS - i);
   }
 
   return true;
 }
+/* eslint-enable complexity */
 
 // Preview functions that do not rely on the object class.
 previewers.Object = [
   function TypedArray({obj, hooks}, grip) {
     if (!ObjectUtils.isTypedArray(obj)) {
       return false;
     }
 
@@ -667,16 +669,17 @@ previewers.Object = [
                obj.class == "CDATASection" ||
                obj.class == "Comment") {
       preview.textContent = hooks.createValueGrip(rawObj.textContent);
     }
 
     return true;
   },
 
+  /* eslint-disable complexity */
   function DOMEvent({obj, hooks}, grip, rawObj) {
     if (isWorker || !rawObj || !Event.isInstance(rawObj)) {
       return false;
     }
 
     const preview = grip.preview = {
       kind: "DOMEvent",
       type: rawObj.type,
@@ -753,16 +756,17 @@ previewers.Object = [
         if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
           break;
         }
       }
     }
 
     return true;
   },
+  /* eslint-enable complexity */
 
   function DOMException({obj, hooks}, grip, rawObj) {
     if (isWorker || !rawObj || obj.class !== "DOMException") {
       return false;
     }
 
     grip.preview = {
       kind: "DOMException",
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -830,16 +830,17 @@ var cookieHelpers = {
    *            lastAccessed: "Wed, 17 Feb 2016 10:06:23 GMT",
    *            value: "%7BHelloo%7D",
    *            isDomain: "true",
    *            isSecure: "false",
    *            isHttpOnly: "false"
    *          }
    *        }
    */
+  /* eslint-disable complexity */
   editCookie(data) {
     let {field, oldValue, newValue} = data;
     const origName = field === "name" ? oldValue : data.items.name;
     const origHost = field === "host" ? oldValue : data.items.host;
     const origPath = field === "path" ? oldValue : data.items.path;
     let cookie = null;
 
     const cookies = Services.cookies.getCookiesFromHost(origHost,
@@ -915,16 +916,17 @@ var cookieHelpers = {
       cookie.isSecure,
       cookie.isHttpOnly,
       cookie.isSession,
       cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expires,
       cookie.originAttributes,
       cookie.sameSite
     );
   },
+  /* eslint-enable complexity */
 
   _removeCookies(host, opts = {}) {
     // We use a uniqueId to emulate compound keys for cookies. We need to
     // extract the cookie name to remove the correct cookie.
     if (opts.name) {
       const split = opts.name.split(SEPARATOR_GUID);
 
       opts.name = split[0];
@@ -2770,16 +2772,17 @@ const StorageActor = protocol.ActorClass
    *             <host1>: [<store_names1>, <store_name2>...],
    *             <host2>: [<store_names34>...],
    *           }
    *           Where host1, host2 are the host in which this change happened and
    *           [<store_namesX] is an array of the names of the changed store objects.
    *           Pass an empty array if the host itself was affected: either completely
    *           removed or cleared.
    */
+  /* eslint-disable complexity */
   update(action, storeType, data) {
     if (action == "cleared") {
       this.emit("stores-cleared", { [storeType]: data });
       return null;
     }
 
     if (this.batchTimer) {
       clearTimeout(this.batchTimer);
@@ -2834,16 +2837,17 @@ const StorageActor = protocol.ActorClass
     this.batchTimer = setTimeout(() => {
       clearTimeout(this.batchTimer);
       this.emit("stores-update", this.boundUpdate);
       this.boundUpdate = {};
     }, BATCH_DELAY);
 
     return null;
   },
+  /* eslint-enable complexity */
 
   /**
    * This method removes data from the this.boundUpdate object in the same
    * manner like this.update() adds data to it.
    *
    * @param {string} action
    *        The type of change. One of "added", "changed" or "deleted"
    * @param {string} storeType
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -529,16 +529,17 @@ WebConsoleActor.prototype =
   /**
    * Handler for the "startListeners" request.
    *
    * @param object request
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The response object which holds the startedListeners array.
    */
+  /* eslint-disable complexity */
   startListeners: async function(request) {
     const startedListeners = [];
     const window = !this.parentActor.isRootActor ? this.window : null;
 
     while (request.listeners.length > 0) {
       const listener = request.listeners.shift();
       switch (listener) {
         case "PageError":
@@ -687,16 +688,17 @@ WebConsoleActor.prototype =
     startedListeners.forEach(this._listeners.add, this._listeners);
 
     return {
       startedListeners: startedListeners,
       nativeConsoleAPI: this.hasNativeConsoleAPI(this.window),
       traits: this.traits,
     };
   },
+  /* eslint-enable complexity */
 
   /**
    * Handler for the "stopListeners" request.
    *
    * @param object request
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The response packet to send to the client: holds the
@@ -980,16 +982,17 @@ WebConsoleActor.prototype =
    * Handler for the "evaluateJS" request. This method evaluates the given
    * JavaScript string and sends back the result.
    *
    * @param object request
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The evaluation response packet.
    */
+  /* eslint-disable complexity */
   evaluateJS: function(request) {
     const input = request.text;
     const timestamp = Date.now();
 
     const evalOptions = {
       bindObjectActor: request.bindObjectActor,
       frameActor: request.frameActor,
       url: request.url,
@@ -1149,16 +1152,17 @@ WebConsoleActor.prototype =
       exceptionDocURL: errorDocURL,
       exceptionStack,
       errorMessageName,
       frame,
       helperResult: helperResult,
       notes: errorNotes,
     };
   },
+  /* eslint-enable complexity */
 
   /**
    * The Autocomplete request handler.
    *
    * @param object request
    *        The request message - what input to autocomplete.
    * @return object
    *         The response message - matched properties.
--- a/devtools/shared/css/lexer.js
+++ b/devtools/shared/css/lexer.js
@@ -5,17 +5,17 @@
 // A CSS Lexer.  This file is a bit unusual -- it is a more or less
 // direct translation of layout/style/nsCSSScanner.cpp and
 // layout/style/CSSLexer.cpp into JS.  This implemented the
 // CSSLexer.webidl interface, and the intent is to try to keep it in
 // sync with changes to the platform CSS lexer.  Due to this goal,
 // this file violates some naming conventions and consequently locally
 // disables some eslint rules.
 
-/* eslint-disable camelcase, mozilla/no-aArgs, no-else-return */
+/* eslint-disable camelcase, mozilla/no-aArgs, no-else-return, complexity */
 
 "use strict";
 
 // White space of any kind.  No value fields are used.  Note that
 // comments do *not* count as white space; comments separate tokens
 // but are not themselves tokens.
 const eCSSToken_Whitespace = "whitespace"; //
 // A comment.
--- a/devtools/shared/css/parsing-utils.js
+++ b/devtools/shared/css/parsing-utils.js
@@ -534,16 +534,17 @@ function parseNamedDeclarations(isCssPro
  * (2) SELECTOR_ELEMENT
  * (3) SELECTOR_PSEUDO_CLASS
  *
  * @param {String} value
  *        The CSS selector text.
  * @return {Array} an array of objects with the following signature:
  *         [{ "value": string, "type": integer }, ...]
  */
+/* eslint-disable complexity */
 function parsePseudoClassesAndAttributes(value) {
   if (!value) {
     throw new Error("empty input string");
   }
 
   const tokens = cssTokenizer(value);
   const result = [];
   let current = "";
@@ -619,16 +620,17 @@ function parsePseudoClassesAndAttributes
   }
 
   if (current) {
     result.push({ value: current, type: SELECTOR_ELEMENT });
   }
 
   return result;
 }
+/* eslint-enable complexity */
 
 /**
  * Expects a single CSS value to be passed as the input and parses the value
  * and priority.
  *
  * @param {Function} isCssPropertyKnown
  *        A function to check if the CSS property is known. This is either an
  *        internal server function or from the CssPropertiesFront.
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -253,16 +253,17 @@ class WalkerFront extends FrontClassWith
     node.reparent(null);
     node.destroy();
   }
 
   /**
    * Get any unprocessed mutation records and process them.
    */
   getMutations(options = {}) {
+    /* eslint-disable complexity */
     return super.getMutations(options).then(mutations => {
       const emitMutations = [];
       for (const change of mutations) {
         // The target is only an actorID, get the associated front.
         let targetID;
         let targetFront;
 
         if (change.type === "newRoot") {
@@ -396,16 +397,17 @@ class WalkerFront extends FrontClassWith
           // This will move retained nodes to this._retainedOrphans.
           this._releaseFront(node);
         }
         this._orphaned = new Set();
       }
 
       this.emit("mutations", emitMutations);
     });
+    /* eslint-enable complexity */
   }
 
   /**
    * Handle the `new-mutations` notification by fetching the
    * available mutation records.
    */
   onMutations() {
     // Fetch and process the mutations.
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -175,16 +175,17 @@ function getLineCountInComments(text) {
 
 /**
  * Prettify minified CSS text.
  * This prettifies CSS code where there is no indentation in usual places while
  * keeping original indentation as-is elsewhere.
  * @param string text The CSS source to prettify.
  * @return string Prettified CSS source
  */
+/* eslint-disable complexity */
 function prettifyCSS(text, ruleCount) {
   if (prettifyCSS.LINE_SEPARATOR == null) {
     const os = Services.appinfo.OS;
     prettifyCSS.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n");
   }
 
   // Stylesheets may start and end with HTML comment tags (possibly with whitespaces
   // before and after). Remove those first. Don't do anything there aren't any.
@@ -400,16 +401,17 @@ function prettifyCSS(text, ruleCount) {
     // Maybe we hit EOF.
     if (!pushbackToken) {
       break;
     }
   }
 
   return result;
 }
+/* eslint-enable complexity */
 
 exports.prettifyCSS = prettifyCSS;
 
 /**
  * Find a unique CSS selector for a given element
  * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  * and ele.ownerDocument.querySelectorAll(reply).length === 1
  */
--- a/devtools/shared/specs/emulation.js
+++ b/devtools/shared/specs/emulation.js
@@ -146,15 +146,19 @@ const emulationSpec = generateActorSpec(
     stopPrintMediaSimulation: {
       request: {
         state: Arg(0, "boolean"),
       },
       response: {},
     },
 
     simulateScreenOrientationChange: {
-      request: {},
+      request: {
+        orientation: Arg(0, "string"),
+        angle: Arg(1, "number"),
+        deviceChange: Arg(2, "boolean"),
+      },
       response: {},
     },
   },
 });
 
 exports.emulationSpec = emulationSpec;
--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -55,16 +55,17 @@ function hasArrayIndex(str) {
  *
  *            {
  *              state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
  *              lastStatement: the last statement in the string,
  *              isElementAccess: boolean that indicates if the lastStatement has an open
  *                               element access (e.g. `x["match`).
  *            }
  */
+/* eslint-disable complexity */
 function analyzeInputString(str) {
   const bodyStack = [];
 
   let state = STATE_NORMAL;
   let start = 0;
   let c;
 
   // Use an array in order to handle character with a length > 2 (e.g. 😎).
@@ -190,16 +191,17 @@ function analyzeInputString(str) {
           state = STATE_NORMAL;
         }
         break;
     }
   }
 
   return buildReturnObject();
 }
+/* eslint-enable complexity */
 
 /**
  * Provides a list of properties, that are possible matches based on the passed
  * Debugger.Environment/Debugger.Object and inputValue.
  *
  * @param {Object} An object of the following shape:
  * - {Object} dbgObject
  *        When the debugger is not paused this Debugger.Object wraps
--- a/dom/animation/CSSPseudoElement.cpp
+++ b/dom/animation/CSSPseudoElement.cpp
@@ -40,17 +40,17 @@ ParentObject CSSPseudoElement::GetParent
   return mOriginatingElement->GetParentObject();
 }
 
 JSObject* CSSPseudoElement::WrapObject(JSContext* aCx,
                                        JS::Handle<JSObject*> aGivenProto) {
   return CSSPseudoElement_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-void CSSPseudoElement::GetAnimations(const AnimationFilter& filter,
+void CSSPseudoElement::GetAnimations(const GetAnimationsOptions& aOptions,
                                      nsTArray<RefPtr<Animation>>& aRetVal) {
   Document* doc = mOriginatingElement->GetComposedDoc();
   if (doc) {
     // We don't need to explicitly flush throttled animations here, since
     // updating the animation style of (pseudo-)elements will never affect the
     // set of running animations and it's only the set of running animations
     // that is important here.
     doc->FlushPendingNotifications(
--- a/dom/animation/CSSPseudoElement.h
+++ b/dom/animation/CSSPseudoElement.h
@@ -49,17 +49,17 @@ class CSSPseudoElement final : public ns
     aRetVal.Append(
         nsDependentAtomString(nsCSSPseudoElements::GetPseudoAtom(mPseudoType)));
   }
   already_AddRefed<dom::Element> Element() const {
     RefPtr<dom::Element> retVal(mOriginatingElement);
     return retVal.forget();
   }
 
-  void GetAnimations(const AnimationFilter& filter,
+  void GetAnimations(const GetAnimationsOptions& aOptions,
                      nsTArray<RefPtr<Animation>>& aRetVal);
   already_AddRefed<Animation> Animate(
       JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
       const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
       ErrorResult& aError);
 
   // Given an element:pseudoType pair, returns the CSSPseudoElement stored as a
   // property on |aElement|. If there is no CSSPseudoElement for the specified
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1233,18 +1233,18 @@ Document::Document(const char* aContentT
       mScrolledToRefAlready(false),
       mChangeScrollPosWhenScrollingToRef(false),
       mHasWarnedAboutBoxObjects(false),
       mDelayFrameLoaderInitialization(false),
       mSynchronousDOMContentLoaded(false),
       mMaybeServiceWorkerControlled(false),
       mAllowZoom(false),
       mValidScaleFloat(false),
+      mValidMinScale(false),
       mValidMaxScale(false),
-      mScaleStrEmpty(false),
       mWidthStrEmpty(false),
       mParserAborted(false),
       mReportedUseCounters(false),
       mHasReportedShadowDOMUsage(false),
       mDocTreeHadAudibleMedia(false),
       mDocTreeHadPlayRevoked(false),
       mHasDelayedRefreshEvent(false),
       mLoadEventFiring(false),
@@ -3438,19 +3438,19 @@ DocumentTimeline* Document::Timeline() {
 
 void Document::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations) {
   // Hold a strong ref for the root element since Element::GetAnimations() calls
   // FlushPendingNotifications() which may destroy the element.
   RefPtr<Element> root = GetRootElement();
   if (!root) {
     return;
   }
-  AnimationFilter filter;
-  filter.mSubtree = true;
-  root->GetAnimations(filter, aAnimations);
+  GetAnimationsOptions options;
+  options.mSubtree = true;
+  root->GetAnimations(options, aAnimations);
 }
 
 SVGSVGElement* Document::GetSVGRootElement() const {
   Element* root = GetRootElement();
   if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
     return nullptr;
   }
   return static_cast<SVGSVGElement*>(root);
@@ -5518,16 +5518,27 @@ static PseudoStyleType GetPseudoElementT
 already_AddRefed<Element> Document::CreateElement(
     const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
     ErrorResult& rv) {
   rv = nsContentUtils::CheckQName(aTagName, false);
   if (rv.Failed()) {
     return nullptr;
   }
 
+  // Temporary check until XULDocument has been removed.
+  if (IsXULDocument()) {
+#if DEBUG
+    xpc_DumpJSStack(true, true, false);
+#endif
+    MOZ_DIAGNOSTIC_ASSERT(false,
+                          "CreateElement() not allowed in XUL document.");
+    rv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
   nsAutoString lcTagName;
   if (needsLowercase) {
     nsContentUtils::ASCIIToLower(aTagName, lcTagName);
   }
 
   const nsString* is = nullptr;
   PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
@@ -6827,19 +6838,83 @@ nsINode* Document::AdoptNode(nsINode& aA
   NS_ASSERTION(adoptedNode->OwnerDoc() == this,
                "Should still be in the document we just got adopted into");
 
   return adoptedNode;
 }
 
 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
 
-void Document::ParseWidthAndHeightInMetaViewport(
-    const nsAString& aWidthString, const nsAString& aHeightString,
-    const nsAString& aScaleString) {
+static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
+    const nsString& aScaleString) {
+  // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
+  if (aScaleString.EqualsLiteral("device-width") ||
+      aScaleString.EqualsLiteral("device-height")) {
+    return Some(LayoutDeviceToScreenScale(10.0f));
+  } else if (aScaleString.EqualsLiteral("yes")) {
+    return Some(LayoutDeviceToScreenScale(1.0f));
+  } else if (aScaleString.EqualsLiteral("no")) {
+    return Some(LayoutDeviceToScreenScale(kViewportMinScale));
+  } else if (aScaleString.IsEmpty()) {
+    return Nothing();
+  }
+
+  nsresult scaleErrorCode;
+  float scale = aScaleString.ToFloat(&scaleErrorCode);
+  if (NS_FAILED(scaleErrorCode)) {
+    return Some(LayoutDeviceToScreenScale(kViewportMinScale));
+  }
+
+  if (scale < 0) {
+    return Nothing();
+  }
+  return Some(clamped(LayoutDeviceToScreenScale(scale), kViewportMinScale,
+                      kViewportMaxScale));
+}
+
+Maybe<LayoutDeviceToScreenScale> Document::ParseScaleInHeader(
+  nsAtom* aHeaderField) {
+  MOZ_ASSERT(aHeaderField == nsGkAtoms::viewport_initial_scale ||
+             aHeaderField == nsGkAtoms::viewport_maximum_scale ||
+             aHeaderField == nsGkAtoms::viewport_minimum_scale);
+
+  nsAutoString scaleStr;
+  GetHeaderData(aHeaderField, scaleStr);
+
+  return ParseScaleString(scaleStr);
+}
+
+void Document::ParseScalesInMetaViewport() {
+  Maybe<LayoutDeviceToScreenScale> scale;
+
+  scale = ParseScaleInHeader(nsGkAtoms::viewport_initial_scale);
+  mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
+  mValidScaleFloat = scale.isSome();
+
+  scale = ParseScaleInHeader(nsGkAtoms::viewport_maximum_scale);
+  // Chrome uses '5' for the fallback value of maximum-scale, we might
+  // consider matching it in future.
+  // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
+  mScaleMaxFloat = scale.valueOr(kViewportMaxScale);
+  mValidMaxScale = scale.isSome();
+
+  scale = ParseScaleInHeader(nsGkAtoms::viewport_minimum_scale);
+  mScaleMinFloat = scale.valueOr(kViewportMinScale);
+  mValidMinScale = scale.isSome();
+
+  // Resolve min-zoom and max-zoom values.
+  // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
+  if (mValidMaxScale && mValidMinScale) {
+    mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
+  }
+}
+
+void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
+                                                 const nsAString& aHeightString,
+                                                 bool aHasValidScale) {
   // The width and height properties
   // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
   //
   // The width and height viewport <META> properties are translated into width
   // and height descriptors, setting the min-width/min-height value to
   // extend-to-zoom and the max-width/max-height value to the length from the
   // viewport <META> property as follows:
   //
@@ -6860,18 +6935,17 @@ void Document::ParseWidthAndHeightInMeta
       if (NS_FAILED(widthErrorCode)) {
         mMaxWidth = 1.0f;
       } else if (mMaxWidth >= 0.0f) {
         mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
       } else {
         mMaxWidth = nsViewportInfo::Auto;
       }
     }
-    // FIXME: Check the scale is not 'auto' once we support auto value for it.
-  } else if (!aScaleString.IsEmpty()) {
+  } else if (aHasValidScale) {
     if (aHeightString.IsEmpty()) {
       mMinWidth = nsViewportInfo::ExtendToZoom;
       mMaxWidth = nsViewportInfo::ExtendToZoom;
     }
   } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
     mMinWidth = nsViewportInfo::ExtendToZoom;
     mMaxWidth = nsViewportInfo::DeviceSize;
   }
@@ -6969,83 +7043,39 @@ nsViewportInfo Document::GetViewportInfo
         GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
         if (handheldFriendly.EqualsLiteral("true")) {
           mViewportType = DisplayWidthHeight;
           return nsViewportInfo(aDisplaySize, defaultScale,
                                 nsViewportInfo::ZoomFlag::AllowZoom);
         }
       }
 
-      nsAutoString minScaleStr;
-      GetHeaderData(nsGkAtoms::viewport_minimum_scale, minScaleStr);
-
-      nsresult scaleMinErrorCode;
-      mScaleMinFloat =
-          LayoutDeviceToScreenScale(minScaleStr.ToFloat(&scaleMinErrorCode));
-
-      if (NS_FAILED(scaleMinErrorCode)) {
-        mScaleMinFloat = kViewportMinScale;
-      }
-
-      mScaleMinFloat = mozilla::clamped(mScaleMinFloat, kViewportMinScale,
-                                        kViewportMaxScale);
-
-      nsAutoString maxScaleStr;
-      GetHeaderData(nsGkAtoms::viewport_maximum_scale, maxScaleStr);
-
-      // We define a special error code variable for the scale and max scale,
-      // because they are used later (see the width calculations).
-      nsresult scaleMaxErrorCode;
-      mScaleMaxFloat =
-          LayoutDeviceToScreenScale(maxScaleStr.ToFloat(&scaleMaxErrorCode));
-
-      if (NS_FAILED(scaleMaxErrorCode)) {
-        mScaleMaxFloat = kViewportMaxScale;
-      }
-
-      // Resolve min-zoom and max-zoom values.
-      // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
-      if (NS_SUCCEEDED(scaleMaxErrorCode) && NS_SUCCEEDED(scaleMinErrorCode)) {
-        mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
-      }
-
-      mScaleMaxFloat = mozilla::clamped(mScaleMaxFloat, kViewportMinScale,
-                                        kViewportMaxScale);
-
-      nsAutoString scaleStr;
-      GetHeaderData(nsGkAtoms::viewport_initial_scale, scaleStr);
-
-      nsresult scaleErrorCode;
-      mScaleFloat =
-          LayoutDeviceToScreenScale(scaleStr.ToFloat(&scaleErrorCode));
+      // Parse initial-scale, minimum-scale and maximum-scale.
+      ParseScalesInMetaViewport();
 
       nsAutoString widthStr, heightStr;
 
       GetHeaderData(nsGkAtoms::viewport_height, heightStr);
       GetHeaderData(nsGkAtoms::viewport_width, widthStr);
 
       // Parse width and height properties
       // This function sets m{Min,Max}{Width,Height}.
-      ParseWidthAndHeightInMetaViewport(widthStr, heightStr, scaleStr);
+      ParseWidthAndHeightInMetaViewport(widthStr, heightStr, mValidScaleFloat);
 
       mAllowZoom = true;
       nsAutoString userScalable;
       GetHeaderData(nsGkAtoms::viewport_user_scalable, userScalable);
 
       if ((userScalable.EqualsLiteral("0")) ||
           (userScalable.EqualsLiteral("no")) ||
           (userScalable.EqualsLiteral("false"))) {
         mAllowZoom = false;
       }
 
-      mScaleStrEmpty = scaleStr.IsEmpty();
       mWidthStrEmpty = widthStr.IsEmpty();
-      mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode);
-      mValidMaxScale =
-          !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode);
 
       mViewportType = viewportIsEmpty ? Empty : Specified;
       MOZ_FALLTHROUGH;
     }
     case Specified:
     case Empty:
     default:
       LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
@@ -7219,17 +7249,17 @@ nsViewportInfo Document::GetViewportInfo
       // prevent the viewport from taking on that size.
       CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
 
       size.width = clamped(size.width, effectiveMinSize.width,
                            float(kViewportMaxSize.width));
 
       // Also recalculate the default zoom, if it wasn't specified in the
       // metadata, and the width is specified.
-      if (mScaleStrEmpty && !mWidthStrEmpty) {
+      if (!mValidScaleFloat && !mWidthStrEmpty) {
         CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
         scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
       }
 
       size.height = clamped(size.height, effectiveMinSize.height,
                             float(kViewportMaxSize.height));
 
       // We need to perform a conversion, but only if the initial or maximum
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3682,17 +3682,25 @@ class Document : public nsINode,
    */
   virtual bool UseWidthDeviceWidthFallbackViewport() const;
 
  private:
   void InitializeLocalization(nsTArray<nsString>& aResourceIds);
 
   void ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
                                          const nsAString& aHeightString,
-                                         const nsAString& aScaleString);
+                                         bool aIsAutoScale);
+
+  // Parse scale values in viewport meta tag for a given |aHeaderField| which
+  // represents the scale property and returns the scale value if it's valid.
+  Maybe<LayoutDeviceToScreenScale> ParseScaleInHeader(nsAtom* aHeaderField);
+
+  // Parse scale values in viewport meta tag and set the values in
+  // mScaleMinFloat, mScaleMaxFloat and mScaleFloat respectively.
+  void ParseScalesInMetaViewport();
 
   FlashClassification DocumentFlashClassificationInternal();
 
   nsTArray<nsString> mL10nResources;
 
   // The application cache that this document is associated with, if
   // any.  This can change during the lifetime of the document.
   nsCOMPtr<nsIApplicationCache> mApplicationCache;
@@ -4316,18 +4324,18 @@ class Document : public nsINode,
   // Set to true when the document is possibly controlled by the ServiceWorker.
   // Used to prevent multiple requests to ServiceWorkerManager.
   bool mMaybeServiceWorkerControlled : 1;
 
   // These member variables cache information about the viewport so we don't
   // have to recalculate it each time.
   bool mAllowZoom : 1;
   bool mValidScaleFloat : 1;
+  bool mValidMinScale : 1;
   bool mValidMaxScale : 1;
-  bool mScaleStrEmpty : 1;
   bool mWidthStrEmpty : 1;
 
   // Parser aborted. True if the parser of this document was forcibly
   // terminated instead of letting it finish at its own pace.
   bool mParserAborted : 1;
 
   // Whether we have reported use counters for this document with Telemetry yet.
   // Normally this is only done at document destruction time, but for image
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3469,17 +3469,17 @@ already_AddRefed<Animation> Element::Ani
   animation->Play(aError, Animation::LimitBehavior::AutoRewind);
   if (aError.Failed()) {
     return nullptr;
   }
 
   return animation.forget();
 }
 
-void Element::GetAnimations(const AnimationFilter& filter,
+void Element::GetAnimations(const GetAnimationsOptions& aOptions,
                             nsTArray<RefPtr<Animation>>& aAnimations) {
   Document* doc = GetComposedDoc();
   if (doc) {
     // We don't need to explicitly flush throttled animations here, since
     // updating the animation style of elements will never affect the set of
     // running animations and it's only the set of running animations that is
     // important here.
     doc->FlushPendingNotifications(
@@ -3500,17 +3500,17 @@ void Element::GetAnimations(const Animat
     elem = GetParentElement();
     pseudoType = PseudoStyleType::marker;
   }
 
   if (!elem) {
     return;
   }
 
-  if (!filter.mSubtree || pseudoType == PseudoStyleType::before ||
+  if (!aOptions.mSubtree || pseudoType == PseudoStyleType::before ||
       pseudoType == PseudoStyleType::after ||
       pseudoType == PseudoStyleType::marker) {
     GetAnimationsUnsorted(elem, pseudoType, aAnimations);
   } else {
     for (nsIContent* node = this; node; node = node->GetNextNode(this)) {
       if (!node->IsElement()) {
         continue;
       }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -79,17 +79,17 @@ class nsIAutoCompletePopup;
 namespace mozilla {
 class DeclarationBlock;
 struct MutationClosureData;
 class TextEditor;
 namespace css {
 struct URLValue;
 }  // namespace css
 namespace dom {
-struct AnimationFilter;
+struct GetAnimationsOptions;
 struct ScrollIntoViewOptions;
 struct ScrollToOptions;
 class DOMIntersectionObserver;
 class DOMMatrixReadOnly;
 class Element;
 class ElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 enum class CallerType : uint32_t;
@@ -1334,17 +1334,17 @@ class Element : public FragmentOrElement
       const Nullable<ElementOrCSSPseudoElement>& aTarget, JSContext* aContext,
       JS::Handle<JSObject*> aKeyframes,
       const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
       ErrorResult& aError);
 
   // Note: GetAnimations will flush style while GetAnimationsUnsorted won't.
   // Callers must keep this element alive because flushing style may destroy
   // this element.
-  void GetAnimations(const AnimationFilter& filter,
+  void GetAnimations(const GetAnimationsOptions& aOptions,
                      nsTArray<RefPtr<Animation>>& aAnimations);
   static void GetAnimationsUnsorted(Element* aElement,
                                     PseudoStyleType aPseudoType,
                                     nsTArray<RefPtr<Animation>>& aAnimations);
 
   virtual void GetInnerHTML(nsAString& aInnerHTML, OOMReporter& aError);
   virtual void SetInnerHTML(const nsAString& aInnerHTML,
                             nsIPrincipal* aSubjectPrincipal,
--- a/dom/ipc/tests/JSWindowActor/head.js
+++ b/dom/ipc/tests/JSWindowActor/head.js
@@ -59,17 +59,18 @@ function declTest(name, cfg) {
 
     // Wait for the provided URL to load in our browser
     let browser = win.gBrowser.selectedBrowser;
     BrowserTestUtils.loadURI(browser, url);
     await BrowserTestUtils.browserLoaded(browser);
 
     // Run the provided test
     info("browser ready");
-    await Promise.resolve(test(browser, win));
-
-    // Clean up after we're done.
-    ChromeUtils.unregisterWindowActor("Test");
-    await BrowserTestUtils.closeWindow(win);
-
-    info("Exiting test: " + name);
+    try {
+      await Promise.resolve(test(browser, win));
+    } finally {
+      // Clean up after we're done.
+      ChromeUtils.unregisterWindowActor("Test");
+      await BrowserTestUtils.closeWindow(win);
+      info("Exiting test: " + name);
+    }
   });
 }
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -64,28 +64,31 @@ static bool IsSameOriginWithAllParentDoc
     }
   }
   return true;
 }
 
 already_AddRefed<Promise> MediaDevices::GetUserMedia(
     const MediaStreamConstraints& aConstraints, CallerType aCallerType,
     ErrorResult& aRv) {
-  if (Document* doc = GetOwner()->GetExtantDoc()) {
-    if (!GetOwner()->IsSecureContext()) {
-      doc->SetDocumentAndPageUseCounter(eUseCounter_custom_GetUserMediaInsec);
-    }
-    if (!IsSameOriginWithAllParentDocs(doc)) {
-      doc->SetDocumentAndPageUseCounter(eUseCounter_custom_GetUserMediaXOrigin);
-    }
-    Document* topDoc = doc->GetTopLevelContentDocument();
-    IgnoredErrorResult ignored;
-    if (topDoc && !topDoc->HasFocus(ignored)) {
-      doc->SetDocumentAndPageUseCounter(
-          eUseCounter_custom_GetUserMediaUnfocused);
+  if (RefPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+    if (Document* doc = owner->GetExtantDoc()) {
+      if (!owner->IsSecureContext()) {
+        doc->SetDocumentAndPageUseCounter(eUseCounter_custom_GetUserMediaInsec);
+      }
+      if (!IsSameOriginWithAllParentDocs(doc)) {
+        doc->SetDocumentAndPageUseCounter(
+            eUseCounter_custom_GetUserMediaXOrigin);
+      }
+      Document* topDoc = doc->GetTopLevelContentDocument();
+      IgnoredErrorResult ignored;
+      if (topDoc && !topDoc->HasFocus(ignored)) {
+        doc->SetDocumentAndPageUseCounter(
+            eUseCounter_custom_GetUserMediaUnfocused);
+      }
     }
   }
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   RefPtr<MediaDevices> self(this);
   MediaManager::Get()
@@ -107,26 +110,28 @@ already_AddRefed<Promise> MediaDevices::
           });
   return p.forget();
 }
 
 already_AddRefed<Promise> MediaDevices::EnumerateDevices(CallerType aCallerType,
                                                          ErrorResult& aRv) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (Document* doc = GetOwner()->GetExtantDoc()) {
-    if (!GetOwner()->IsSecureContext()) {
-      doc->SetDocumentAndPageUseCounter(
-          eUseCounter_custom_EnumerateDevicesInsec);
-    }
-    Document* topDoc = doc->GetTopLevelContentDocument();
-    IgnoredErrorResult ignored;
-    if (topDoc && !topDoc->HasFocus(ignored)) {
-      doc->SetDocumentAndPageUseCounter(
-          eUseCounter_custom_EnumerateDevicesUnfocused);
+  if (RefPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+    if (Document* doc = owner->GetExtantDoc()) {
+      if (!owner->IsSecureContext()) {
+        doc->SetDocumentAndPageUseCounter(
+            eUseCounter_custom_EnumerateDevicesInsec);
+      }
+      Document* topDoc = doc->GetTopLevelContentDocument();
+      IgnoredErrorResult ignored;
+      if (topDoc && !topDoc->HasFocus(ignored)) {
+        doc->SetDocumentAndPageUseCounter(
+            eUseCounter_custom_EnumerateDevicesUnfocused);
+      }
     }
   }
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   RefPtr<MediaDevices> self(this);
   MediaManager::Get()
@@ -167,20 +172,22 @@ already_AddRefed<Promise> MediaDevices::
             p->MaybeReject(MakeRefPtr<MediaStreamError>(window, *error));
           });
   return p.forget();
 }
 
 already_AddRefed<Promise> MediaDevices::GetDisplayMedia(
     const DisplayMediaStreamConstraints& aConstraints, CallerType aCallerType,
     ErrorResult& aRv) {
-  if (Document* doc = GetOwner()->GetExtantDoc()) {
-    if (!IsSameOriginWithAllParentDocs(doc)) {
-      doc->SetDocumentAndPageUseCounter(
-          eUseCounter_custom_GetDisplayMediaXOrigin);
+  if (RefPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+    if (Document* doc = owner->GetExtantDoc()) {
+      if (!IsSameOriginWithAllParentDocs(doc)) {
+        doc->SetDocumentAndPageUseCounter(
+            eUseCounter_custom_GetDisplayMediaXOrigin);
+      }
     }
   }
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   RefPtr<MediaDevices> self(this);
   MediaManager::Get()
--- a/dom/media/ipc/RemoteDecoderModule.cpp
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -89,19 +89,31 @@ already_AddRefed<MediaDataDecoder> Remot
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteAudioDecoderChild> child = new RemoteAudioDecoderChild();
   MediaResult result(NS_OK);
-  RefPtr<Runnable> task = NS_NewRunnableFunction(
-      "RemoteDecoderModule::CreateAudioDecoder", [&, child]() {
+  // We can use child as a ref here because this is a sync dispatch. In
+  // the error case for InitIPDL, we can't just let the RefPtr go out of
+  // scope at the end of the method because it will release the
+  // RemoteAudioDecoderChild on the wrong thread.  This will assert in
+  // RemoteDecoderChild's destructor.  Passing the RefPtr by reference
+  // allows us to release the RemoteAudioDecoderChild on the manager
+  // thread during this single dispatch.
+  RefPtr<Runnable> task =
+      NS_NewRunnableFunction("RemoteDecoderModule::CreateAudioDecoder", [&]() {
         result = child->InitIPDL(aParams.AudioConfig(), aParams.mOptions);
+        if (NS_FAILED(result)) {
+          // Release RemoteAudioDecoderChild here, while we're on
+          // manager thread.  Don't just let the RefPtr go out of scope.
+          child = nullptr;
+        }
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
@@ -119,20 +131,32 @@ already_AddRefed<MediaDataDecoder> Remot
   LaunchRDDProcessIfNeeded();
 
   if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteVideoDecoderChild> child = new RemoteVideoDecoderChild();
   MediaResult result(NS_OK);
-  RefPtr<Runnable> task = NS_NewRunnableFunction(
-      "RemoteDecoderModule::CreateVideoDecoder", [&, child]() {
+  // We can use child as a ref here because this is a sync dispatch. In
+  // the error case for InitIPDL, we can't just let the RefPtr go out of
+  // scope at the end of the method because it will release the
+  // RemoteVideoDecoderChild on the wrong thread.  This will assert in
+  // RemoteDecoderChild's destructor.  Passing the RefPtr by reference
+  // allows us to release the RemoteVideoDecoderChild on the manager
+  // thread during this single dispatch.
+  RefPtr<Runnable> task =
+      NS_NewRunnableFunction("RemoteDecoderModule::CreateVideoDecoder", [&]() {
         result = child->InitIPDL(aParams.VideoConfig(), aParams.mRate.mValue,
                                  aParams.mOptions);
+        if (NS_FAILED(result)) {
+          // Release RemoteVideoDecoderChild here, while we're on
+          // manager thread.  Don't just let the RefPtr go out of scope.
+          child = nullptr;
+        }
       });
   SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -602,17 +602,36 @@ void VideoSink::MaybeResolveEndPromise()
       RefPtr<VideoData> frame = VideoQueue().PopFront();
       if (mPendingDroppedCount > 0) {
         mFrameStats.Accumulate({0, 0, 0, 0, 0, 1});
         mPendingDroppedCount--;
       } else {
         mFrameStats.NotifyPresentedFrame();
       }
     }
-    mEndPromiseHolder.ResolveIfExists(true, __func__);
+
+    TimeStamp nowTime;
+    const auto clockTime = mAudioSink->GetPosition(&nowTime);
+    if (clockTime < mVideoFrameEndTime) {
+      VSINK_LOG_V(
+          "Not reach video end time yet, reschedule timer to resolve "
+          "end promise. clockTime=%" PRId64 ", endTime=%" PRId64,
+          clockTime.ToMicroseconds(), mVideoFrameEndTime.ToMicroseconds());
+      int64_t delta = (mVideoFrameEndTime - clockTime).ToMicroseconds() /
+                      mAudioSink->GetPlaybackParams().mPlaybackRate;
+      TimeStamp target = nowTime + TimeDuration::FromMicroseconds(delta);
+      auto resolveEndPromise = [self = RefPtr<VideoSink>(this)]() {
+        self->mEndPromiseHolder.ResolveIfExists(true, __func__);
+        self->mUpdateScheduler.CompleteRequest();
+      };
+      mUpdateScheduler.Ensure(target, std::move(resolveEndPromise),
+                              std::move(resolveEndPromise));
+    } else {
+      mEndPromiseHolder.ResolveIfExists(true, __func__);
+    }
   }
 }
 
 void VideoSink::SetSecondaryVideoContainer(VideoFrameContainer* aSecondary) {
   AssertOwnerThread();
   mSecondaryContainer = aSecondary;
   if (!IsPlaying() && mSecondaryContainer) {
     ImageContainer* mainImageContainer = mContainer->GetImageContainer();
--- a/dom/svg/SVGElement.cpp
+++ b/dom/svg/SVGElement.cpp
@@ -1209,34 +1209,16 @@ void SVGElement::UpdateContentDeclaratio
       continue;
     }
 
     if (attrName->Equals(nsGkAtoms::lang, kNameSpaceID_None) &&
         HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) {
       continue;  // xml:lang has precedence
     }
 
-    if (IsSVGElement(nsGkAtoms::svg)) {
-      // Special case: we don't want <svg> 'width'/'height' mapped into style
-      // if the attribute value isn't a valid <length> according to SVG (which
-      // only supports a subset of the CSS <length> values). We don't enforce
-      // this by checking the attribute value in SVGSVGElement::
-      // IsAttributeMapped since we don't want that method to depend on the
-      // value of the attribute that is being checked. Rather we just prevent
-      // the actual mapping here, as necessary.
-      if (attrName->Atom() == nsGkAtoms::width &&
-          !GetAnimatedLength(nsGkAtoms::width)->HasBaseVal()) {
-        continue;
-      }
-      if (attrName->Atom() == nsGkAtoms::height &&
-          !GetAnimatedLength(nsGkAtoms::height)->HasBaseVal()) {
-        continue;
-      }
-    }
-
     if (lengthAffectsStyle) {
       auto const* length = GetAnimatedLength(attrName->Atom());
 
       if (length && length->HasBaseVal()) {
         // This is an element with geometry property set via SVG attribute,
         // and the attribute is already successfully parsed. We want to go
         // through the optimized path to tell the style system the result
         // directly, rather than let it parse the same thing again.
--- a/dom/webidl/Animatable.webidl
+++ b/dom/webidl/Animatable.webidl
@@ -9,20 +9,20 @@
  * Copyright © 2014 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
   DOMString id = "";
 };
 
-dictionary AnimationFilter {
+dictionary GetAnimationsOptions {
   boolean subtree = false;
 };
 
 [NoInterfaceObject]
 interface Animatable {
   [Throws]
   Animation animate(object? keyframes,
                     optional UnrestrictedDoubleOrKeyframeAnimationOptions options);
   [Func="Document::IsWebAnimationsGetAnimationsEnabled"]
-  sequence<Animation> getAnimations(optional AnimationFilter filter);
+  sequence<Animation> getAnimations(optional GetAnimationsOptions options);
 };
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -226,22 +226,25 @@ XMLHttpRequestMainThread::XMLHttpRequest
       mIsAnon(false),
       mFirstStartRequestSeen(false),
       mInLoadProgressEvent(false),
       mResultJSON(JS::UndefinedValue()),
       mResultArrayBuffer(nullptr),
       mIsMappedArrayBuffer(false),
       mXPCOMifier(nullptr),
       mEventDispatchingSuspended(false),
-      mEofDecoded(false) {
+      mEofDecoded(false),
+      mDelayedDoneNotifier(nullptr) {
   mozilla::HoldJSObjects(this);
 }
 
 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
-  DisconnectDoneNotifier();
+  MOZ_ASSERT(
+      !mDelayedDoneNotifier,
+      "How can we have mDelayedDoneNotifier, which owns us, in destructor?");
 
   mFlagDeleted = true;
 
   if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
       mState == XMLHttpRequest_Binding::LOADING) {
     Abort();
   }
 
@@ -2229,16 +2232,18 @@ void XMLHttpRequestMainThread::MatchChar
     TruncateResponseText();
     mResponseBodyDecodedPos = 0;
     mEofDecoded = false;
     mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
   }
 }
 void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
   if (mDelayedDoneNotifier) {
+    // Disconnect may release the last reference to 'this'.
+    RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
     mDelayedDoneNotifier->Disconnect();
     mDelayedDoneNotifier = nullptr;
   }
 }
 
 void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
   DisconnectDoneNotifier();
 
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -719,17 +719,17 @@ class XMLHttpRequestMainThread final : p
   // Used in lazy decoding to distinguish between having
   // processed all the bytes but not the EOF and having
   // processed all the bytes and the EOF.
   bool mEofDecoded;
 
   // Our parse-end listener, if we are parsing.
   RefPtr<nsXHRParseEndListener> mParseEndListener;
 
-  RefPtr<XMLHttpRequestDoneNotifier> mDelayedDoneNotifier;
+  XMLHttpRequestDoneNotifier* mDelayedDoneNotifier;
   void DisconnectDoneNotifier();
 
   // Any stack information for the point the XHR was opened. This is deleted
   // after the XHR is opened, to avoid retaining references to the worker.
   UniquePtr<SerializedStackHolder> mOriginStack;
 
   static bool sDontWarnAboutSyncXHR;
 };
@@ -790,26 +790,27 @@ class nsXMLHttpRequestXPCOMifier final :
 class XMLHttpRequestDoneNotifier : public Runnable {
  public:
   explicit XMLHttpRequestDoneNotifier(XMLHttpRequestMainThread* aXHR)
       : Runnable("XMLHttpRequestDoneNotifier"), mXHR(aXHR) {}
 
   NS_IMETHOD Run() override {
     if (mXHR) {
       RefPtr<XMLHttpRequestMainThread> xhr = mXHR;
-      mXHR = nullptr;
+      // ChangeStateToDoneInternal ends up calling Disconnect();
       xhr->ChangeStateToDoneInternal();
+      MOZ_ASSERT(!mXHR);
     }
     return NS_OK;
   }
 
   void Disconnect() { mXHR = nullptr; }
 
  private:
-  XMLHttpRequestMainThread* mXHR;
+  RefPtr<XMLHttpRequestMainThread> mXHR;
 };
 
 class nsXHRParseEndListener : public nsIDOMEventListener {
  public:
   NS_DECL_ISUPPORTS
   NS_IMETHOD HandleEvent(Event* event) override {
     if (mXHR) {
       mXHR->OnBodyParseEnd();
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1536,16 +1536,17 @@ nsresult HTMLEditRules::WillInsertText(E
           rv = wsObj.InsertText(*doc, spacesStr, &pointAfterInsertedSpaces);
           if (NS_WARN_IF(!CanHandleEditAction())) {
             return NS_ERROR_EDITOR_DESTROYED;
           }
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
           pos++;
+          MOZ_ASSERT(pointAfterInsertedSpaces.IsSet());
           currentPoint = pointAfterInsertedSpaces;
           pointToInsert = pointAfterInsertedSpaces;
         }
         // is it a return?
         else if (subStr.Equals(newlineStr)) {
           RefPtr<Element> newBRElement =
               wsObj.InsertBreak(MOZ_KnownLive(*SelectionRefPtr()), currentPoint,
                                 nsIEditor::eNone);
@@ -1576,16 +1577,17 @@ nsresult HTMLEditRules::WillInsertText(E
           EditorRawDOMPoint pointAfterInsertedString;
           rv = wsObj.InsertText(*doc, subStr, &pointAfterInsertedString);
           if (NS_WARN_IF(!CanHandleEditAction())) {
             return NS_ERROR_EDITOR_DESTROYED;
           }
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
+          MOZ_ASSERT(pointAfterInsertedString.IsSet());
           currentPoint = pointAfterInsertedString;
           pointToInsert = pointAfterInsertedString;
         }
       }
     }
 
     // After this block, pointToInsert is updated by AutoTrackDOMPoint.
   }
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -363,22 +363,28 @@ nsresult WSRunObject::InsertText(Documen
       } else {
         prevWS = true;
       }
     } else {
       prevWS = false;
     }
   }
 
-  // Ready, aim, fire!
+  // XXX If the point is not editable, InsertTextWithTransaction() returns
+  //     error, but we keep handling it.  But I think that it wastes the
+  //     runtime cost.  So, perhaps, we should return error code which couldn't
+  //     modify it and make each caller of this method decide whether it should
+  //     keep or stop handling the edit action.
   nsresult rv =
       MOZ_KnownLive(mHTMLEditor)
           ->InsertTextWithTransaction(aDocument, theString, pointToInsert,
                                       aPointAfterInsertedString);
   if (NS_WARN_IF(NS_FAILED(rv))) {
+    // XXX Temporarily, set new insertion point to the original point.
+    *aPointAfterInsertedString = pointToInsert;
     return NS_OK;
   }
   return NS_OK;
 }
 
 nsresult WSRunObject::DeleteWSBackward() {
   WSPoint point = GetPreviousCharPoint(mScanStartPoint);
   NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1534394.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+    <script>
+
+      function start () {
+        const sel = document.getSelection()
+        const range_1 = new Range()
+        const noscript = document.getElementById('id_24')
+        const map = document.getElementById('id_26')
+        sel.selectAllChildren(noscript)
+        const range_2 = range_1.cloneRange()
+        range_2.selectNode(map)
+        const frag = range_2.extractContents()
+        range_2.insertNode(noscript)
+        document.documentElement.contentEditable = true
+        document.execCommand('removeFormat', false,)
+        noscript.contentEditable = false
+        document.execCommand('insertText', false, '�\n')
+      }
+
+      window.addEventListener('load', start)
+    </script>
+</head>
+<body>
+<big class="">
+    <map class="" id="id_26">
+        <noscript class="" id="id_24">
+</body>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -103,9 +103,10 @@ needs-focus load 1424450.html
 load 1425091.html
 load 1441619.html
 load 1443664.html
 skip-if(Android) needs-focus load 1444630.html
 load 1446451.html
 asserts(0-2) load 1464251.html # assertion is that mutation event listener modifies content
 pref(layout.accessiblecaret.enabled,true) load 1470926.html
 load 1525481.html
+load 1534394.html
 load 1547898.html
--- a/gfx/layers/FrameMetrics.cpp
+++ b/gfx/layers/FrameMetrics.cpp
@@ -85,49 +85,49 @@ void FrameMetrics::KeepLayoutViewportEnc
   // to go outside the scrollable rect.
   aLayoutViewport = aLayoutViewport.MoveInsideAndClamp(aScrollableRect);
 }
 
 void ScrollMetadata::SetUsesContainerScrolling(bool aValue) {
   mUsesContainerScrolling = aValue;
 }
 
-void ScrollSnapInfo::InitializeScrollSnapType(WritingMode aWritingMode,
-                                              const nsStyleDisplay* aDisplay) {
+void ScrollSnapInfo::InitializeScrollSnapStrictness(
+    WritingMode aWritingMode, const nsStyleDisplay* aDisplay) {
   if (aDisplay->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
     return;
   }
 
-  mScrollSnapTypeX = StyleScrollSnapStrictness::None;
-  mScrollSnapTypeY = StyleScrollSnapStrictness::None;
+  mScrollSnapStrictnessX = StyleScrollSnapStrictness::None;
+  mScrollSnapStrictnessY = StyleScrollSnapStrictness::None;
 
   switch (aDisplay->mScrollSnapType.axis) {
     case StyleScrollSnapAxis::X:
-      mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       break;
     case StyleScrollSnapAxis::Y:
-      mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       break;
     case StyleScrollSnapAxis::Block:
       if (aWritingMode.IsVertical()) {
-        mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       } else {
-        mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       }
       break;
     case StyleScrollSnapAxis::Inline:
       if (aWritingMode.IsVertical()) {
-        mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       } else {
-        mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       }
       break;
     case StyleScrollSnapAxis::Both:
-      mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
-      mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       break;
   }
 }
 
 static OverscrollBehavior ToOverscrollBehavior(
     StyleOverscrollBehavior aBehavior) {
   switch (aBehavior) {
     case StyleOverscrollBehavior::Auto:
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -714,48 +714,49 @@ struct FrameMetrics {
   // Private helpers for IPC purposes
   void SetDoSmoothScroll(bool aValue) { mDoSmoothScroll = aValue; }
 };
 
 struct ScrollSnapInfo {
   ScrollSnapInfo() = default;
 
   bool operator==(const ScrollSnapInfo& aOther) const {
-    return mScrollSnapTypeX == aOther.mScrollSnapTypeX &&
-           mScrollSnapTypeY == aOther.mScrollSnapTypeY &&
+    return mScrollSnapStrictnessX == aOther.mScrollSnapStrictnessX &&
+           mScrollSnapStrictnessY == aOther.mScrollSnapStrictnessY &&
            mScrollSnapIntervalX == aOther.mScrollSnapIntervalX &&
            mScrollSnapIntervalY == aOther.mScrollSnapIntervalY &&
            mScrollSnapDestination == aOther.mScrollSnapDestination &&
            mScrollSnapCoordinates == aOther.mScrollSnapCoordinates &&
            mSnapPositionX == aOther.mSnapPositionX &&
            mSnapPositionY == aOther.mSnapPositionY &&
            mXRangeWiderThanSnapport == aOther.mXRangeWiderThanSnapport &&
            mYRangeWiderThanSnapport == aOther.mYRangeWiderThanSnapport &&
            mSnapportSize == aOther.mSnapportSize;
   }
 
   bool HasScrollSnapping() const {
-    return mScrollSnapTypeY != mozilla::StyleScrollSnapStrictness::None ||
-           mScrollSnapTypeX != mozilla::StyleScrollSnapStrictness::None;
+    return mScrollSnapStrictnessY != mozilla::StyleScrollSnapStrictness::None ||
+           mScrollSnapStrictnessX != mozilla::StyleScrollSnapStrictness::None;
   }
 
   bool HasSnapPositions() const {
     return (!mSnapPositionX.IsEmpty() &&
-            mScrollSnapTypeX != mozilla::StyleScrollSnapStrictness::None) ||
+            mScrollSnapStrictnessX !=
+                mozilla::StyleScrollSnapStrictness::None) ||
            (!mSnapPositionY.IsEmpty() &&
-            mScrollSnapTypeY != mozilla::StyleScrollSnapStrictness::None);
+            mScrollSnapStrictnessY != mozilla::StyleScrollSnapStrictness::None);
   }
 
-  void InitializeScrollSnapType(WritingMode aWritingMode,
-                                const nsStyleDisplay* aDisplay);
+  void InitializeScrollSnapStrictness(WritingMode aWritingMode,
+                                      const nsStyleDisplay* aDisplay);
 
   // The scroll frame's scroll-snap-type.
-  mozilla::StyleScrollSnapStrictness mScrollSnapTypeX =
+  mozilla::StyleScrollSnapStrictness mScrollSnapStrictnessX =
       mozilla::StyleScrollSnapStrictness::None;
-  mozilla::StyleScrollSnapStrictness mScrollSnapTypeY =
+  mozilla::StyleScrollSnapStrictness mScrollSnapStrictnessY =
       mozilla::StyleScrollSnapStrictness::None;
 
   // The intervals derived from the scroll frame's scroll-snap-points.
   Maybe<nscoord> mScrollSnapIntervalX;
   Maybe<nscoord> mScrollSnapIntervalY;
 
   // The scroll frame's scroll-snap-destination, in cooked form (to avoid
   // shipping the raw nsStyleCoord::CalcValue over IPC).
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -655,16 +655,19 @@ void APZCTreeManager::SampleForWebRender
     LayoutDeviceToParentLayerScale zoom;
     if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
       // for now we only support zooming on root content APZCs
       MOZ_ASSERT(apzc->IsRootContent());
       zoom = apzc->GetCurrentPinchZoomScale(
           AsyncPanZoomController::eForCompositing);
       transforms.AppendElement(wr::ToWrTransformProperty(
           *zoomAnimationId, Matrix4x4::Scaling(zoom.scale, zoom.scale, 1.0f)));
+
+      aTxn.UpdateIsTransformPinchZooming(*zoomAnimationId,
+                                         apzc->IsPinchZooming());
     }
 
     // The positive translation means the painted content is supposed to
     // move down (or to the right), and that corresponds to a reduction in
     // the scroll offset. Since we are effectively giving WR the async
     // scroll delta here, we want to negate the translation.
     LayoutDevicePoint asyncScrollDelta = -layerTranslation / zoom;
     aTxn.UpdateScrollPosition(wr::AsPipelineId(apzc->GetGuid().mLayersId),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1590,16 +1590,18 @@ class AsyncPanZoomController {
   bool GetAsyncTransformAppliedToContent() const {
     return mAsyncTransformAppliedToContent;
   }
 
   LayersId GetLayersId() const { return mLayersId; }
 
   wr::RenderRoot GetRenderRoot() const { return mRenderRoot; }
 
+  bool IsPinchZooming() const { return mState == PINCHING; }
+
  private:
   // Extra offset to add to the async scroll position for testing
   CSSPoint mTestAsyncScrollOffset;
   // Extra zoom to include in the aync zoom for testing
   LayerToParentLayerScale mTestAsyncZoom;
   int mTestAttributeAppliers : 8;
   // Flag to track whether or not the APZ transform is not used. This
   // flag is recomputed for every composition frame.
--- a/gfx/layers/apz/test/gtest/TestSnapping.cpp
+++ b/gfx/layers/apz/test/gtest/TestSnapping.cpp
@@ -22,17 +22,17 @@ TEST_F(APZCSnappingTester, Bug1265510) {
       CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
   SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
                             CSSRect(0, 0, 100, 200));
   SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
                             CSSRect(0, 0, 100, 200));
   SetScrollHandoff(layers[1], root);
 
   ScrollSnapInfo snap;
-  snap.mScrollSnapTypeY = StyleScrollSnapStrictness::Mandatory;
+  snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory;
   if (StaticPrefs::layout_css_scroll_snap_v1_enabled()) {
     snap.mSnapPositionY.AppendElement(0 * AppUnitsPerCSSPixel());
     snap.mSnapPositionY.AppendElement(100 * AppUnitsPerCSSPixel());
   } else {
     snap.mScrollSnapIntervalY = Some(100 * AppUnitsPerCSSPixel());
   }
 
   ScrollMetadata metadata = root->GetScrollMetadata(0);
@@ -97,17 +97,17 @@ TEST_F(APZCSnappingTester, Snap_After_Pi
   };
   root =
       CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
   SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
                             CSSRect(0, 0, 100, 200));
 
   // Set up some basic scroll snapping
   ScrollSnapInfo snap;
-  snap.mScrollSnapTypeY = StyleScrollSnapStrictness::Mandatory;
+  snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory;
 
   if (StaticPrefs::layout_css_scroll_snap_v1_enabled()) {
     snap.mSnapPositionY.AppendElement(0 * AppUnitsPerCSSPixel());
     snap.mSnapPositionY.AppendElement(100 * AppUnitsPerCSSPixel());
   } else {
     snap.mScrollSnapIntervalY = Some(100 * AppUnitsPerCSSPixel());
   }
 
--- a/gfx/layers/apz/test/reftest/initial-scale-1-ref.html
+++ b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html><head>
-<meta name="viewport" content="width=device-width">
+<meta name="viewport" content="initial-scale=0.25,width=device-width">
 </head>
 <body>
 This tests that an initial-scale of 0 (i.e. garbage) is overridden<br/>
 with something a little more sane.
 </body>
 </html>
 
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -327,33 +327,33 @@ struct ParamTraits<mozilla::layers::Scro
   }
 };
 
 template <>
 struct ParamTraits<mozilla::layers::ScrollSnapInfo> {
   typedef mozilla::layers::ScrollSnapInfo paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.mScrollSnapTypeX);
-    WriteParam(aMsg, aParam.mScrollSnapTypeY);
+    WriteParam(aMsg, aParam.mScrollSnapStrictnessX);
+    WriteParam(aMsg, aParam.mScrollSnapStrictnessY);
     WriteParam(aMsg, aParam.mScrollSnapIntervalX);
     WriteParam(aMsg, aParam.mScrollSnapIntervalY);
     WriteParam(aMsg, aParam.mScrollSnapDestination);
     WriteParam(aMsg, aParam.mScrollSnapCoordinates);
     WriteParam(aMsg, aParam.mSnapPositionX);
     WriteParam(aMsg, aParam.mSnapPositionY);
     WriteParam(aMsg, aParam.mXRangeWiderThanSnapport);
     WriteParam(aMsg, aParam.mYRangeWiderThanSnapport);
     WriteParam(aMsg, aParam.mSnapportSize);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
-    return (ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeX) &&
-            ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeY) &&
+    return (ReadParam(aMsg, aIter, &aResult->mScrollSnapStrictnessX) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollSnapStrictnessY) &&
             ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalX) &&
             ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalY) &&
             ReadParam(aMsg, aIter, &aResult->mScrollSnapDestination) &&
             ReadParam(aMsg, aIter, &aResult->mScrollSnapCoordinates) &&
             ReadParam(aMsg, aIter, &aResult->mSnapPositionX) &&
             ReadParam(aMsg, aIter, &aResult->mSnapPositionY) &&
             ReadParam(aMsg, aIter, &aResult->mXRangeWiderThanSnapport) &&
             ReadParam(aMsg, aIter, &aResult->mYRangeWiderThanSnapport) &&
--- a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
+++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
@@ -37,56 +37,27 @@
 
 #include <dwrite.h>
 #include <dwrite_1.h>
 #include <dwrite_3.h>
 
 /* Note:
  * In versions 8 and 8.1 of Windows, some calls in DWrite are not thread safe.
  * The DWriteFactoryMutex protects the calls that are problematic.
- *
- * On DWrite 3 or above, which is only available on Windows 10, we don't enable
- * the locking to avoid thread contention.
  */
 static SkSharedMutex DWriteFactoryMutex;
 
-struct MaybeExclusive {
-    MaybeExclusive(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) {
-        if (fEnabled) {
-            DWriteFactoryMutex.acquire();
-        }
-    }
-    ~MaybeExclusive() {
-        if (fEnabled) {
-            DWriteFactoryMutex.release();
-        }
-    }
-    bool fEnabled;
-};
-
-struct MaybeShared {
-    MaybeShared(SkScalerContext_DW* ctx) : fEnabled(!ctx->isDWrite3()) {
-        if (fEnabled) {
-            DWriteFactoryMutex.acquireShared();
-        }
-    }
-    ~MaybeShared() {
-        if (fEnabled) {
-            DWriteFactoryMutex.releaseShared();
-        }
-    }
-    bool fEnabled;
-};
+typedef SkAutoSharedMutexShared Shared;
 
 static bool isLCD(const SkScalerContextRec& rec) {
     return SkMask::kLCD16_Format == rec.fMaskFormat;
 }
 
-static bool is_hinted(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface) {
-    MaybeExclusive l(ctx);
+static bool is_hinted(DWriteFontTypeface* typeface) {
+    SkAutoExclusive l(DWriteFactoryMutex);
     AutoTDWriteTable<SkOTTableMaximumProfile> maxp(typeface->fDWriteFontFace.get());
     if (!maxp.fExists) {
         return false;
     }
     if (maxp.fSize < sizeof(SkOTTableMaximumProfile::Version::TT)) {
         return false;
     }
     if (maxp->version.version != SkOTTableMaximumProfile::Version::TT::VERSION) {
@@ -146,18 +117,18 @@ bool get_gasp_range(DWriteFontTypeface* 
 }
 /** If the rendering mode for the specified 'size' is gridfit, then place
  *  the gridfit range into 'range'. Otherwise, leave 'range' alone.
  */
 static bool is_gridfit_only(GaspRange::Behavior flags) {
     return flags.raw.value == GaspRange::Behavior::Raw::GridfitMask;
 }
 
-static bool has_bitmap_strike(SkScalerContext_DW* ctx, DWriteFontTypeface* typeface, GaspRange range) {
-    MaybeExclusive l(ctx);
+static bool has_bitmap_strike(DWriteFontTypeface* typeface, GaspRange range) {
+    SkAutoExclusive l(DWriteFactoryMutex);
     {
         AutoTDWriteTable<SkOTTableEmbeddedBitmapLocation> eblc(typeface->fDWriteFontFace.get());
         if (!eblc.fExists) {
             return false;
         }
         if (eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation)) {
             return false;
         }
@@ -292,17 +263,17 @@ SkScalerContext_DW::SkScalerContext_DW(s
         // a bitmap strike if the range is gridfit only and contains a bitmap.
         int bitmapPPEM = SkScalarTruncToInt(gdiTextSize);
         GaspRange range(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
         if (get_gasp_range(typeface, bitmapPPEM, &range)) {
             if (!is_gridfit_only(range.fFlags)) {
                 range = GaspRange(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
             }
         }
-        treatLikeBitmap = has_bitmap_strike(this, typeface, range);
+        treatLikeBitmap = has_bitmap_strike(typeface, range);
 
         axisAlignedBitmap = is_axis_aligned(fRec);
     }
 
     GaspRange range(0, 0xFFFF, 0, GaspRange::Behavior());
 
     // If the user requested aliased, do so with aliased compatible metrics.
     if (SkMask::kBW_Format == fRec.fMaskFormat) {
@@ -328,17 +299,17 @@ SkScalerContext_DW::SkScalerContext_DW(s
         fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
         fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
         fTextSizeMeasure = gdiTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
 
     // If the font has a gasp table version 1, use it to determine symmetric rendering.
     } else if ((get_gasp_range(typeface, SkScalarRoundToInt(gdiTextSize), &range) &&
                 range.fVersion >= 1) ||
-               realTextSize > SkIntToScalar(20) || !is_hinted(this, typeface)) {
+               realTextSize > SkIntToScalar(20) || !is_hinted(typeface)) {
         fTextSizeRender = realTextSize;
         fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
         fTextSizeMeasure = realTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
 
         IDWriteRenderingParams* params = sk_get_dwrite_default_rendering_params();
         DWriteFontTypeface* typeface = static_cast<DWriteFontTypeface*>(getTypeface());
         if (params &&
@@ -421,36 +392,36 @@ bool SkScalerContext_DW::generateAdvance
     glyph->fAdvanceY = 0;
 
     uint16_t glyphId = glyph->getGlyphID();
     DWRITE_GLYPH_METRICS gm;
 
     if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
         DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
     {
-        MaybeExclusive l(this);
+        SkAutoExclusive l(DWriteFactoryMutex);
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGdiCompatibleGlyphMetrics(
                  fTextSizeMeasure,
                  1.0f, // pixelsPerDip
                  // This parameter does not act like the lpmat2 parameter to GetGlyphOutlineW.
                  // If it did then GsA here and G_inv below to mapVectors.
                  nullptr,
                  DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode,
                  &glyphId, 1,
                  &gm),
              "Could not get gdi compatible glyph metrics.");
     } else {
-        MaybeExclusive l(this);
+        SkAutoExclusive l(DWriteFactoryMutex);
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm),
              "Could not get design metrics.");
     }
 
     DWRITE_FONT_METRICS dwfm;
     {
-        MaybeShared l(this);
+        Shared l(DWriteFactoryMutex);
         this->getDWriteTypeface()->fDWriteFontFace->GetMetrics(&dwfm);
     }
     SkScalar advanceX = fTextSizeMeasure * gm.advanceWidth / dwfm.designUnitsPerEm;
 
     SkVector advance = { advanceX, 0 };
     if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
         DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
     {
@@ -489,17 +460,17 @@ HRESULT SkScalerContext_DW::getBoundingB
     run.fontEmSize = SkScalarToFloat(fTextSizeRender);
     run.bidiLevel = 0;
     run.glyphIndices = &glyphId;
     run.isSideways = FALSE;
     run.glyphOffsets = &offset;
 
     SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
     {
-        MaybeExclusive l(this);
+        SkAutoExclusive l(DWriteFactoryMutex);
         // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
         if (this->getDWriteTypeface()->fFactory2 &&
                 (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
                  fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
         {
             HRM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(
                     &run,
                     &fXform,
@@ -519,17 +490,17 @@ HRESULT SkScalerContext_DW::getBoundingB
                     fMeasuringMode,
                     0.0f, // baselineOriginX,
                     0.0f, // baselineOriginY,
                     &glyphRunAnalysis),
                 "Could not create glyph run analysis.");
         }
     }
     {
-        MaybeShared l(this);
+        Shared l(DWriteFactoryMutex);
         HRM(glyphRunAnalysis->GetAlphaTextureBounds(textureType, bbox),
             "Could not get texture bounds.");
     }
     return S_OK;
 }
 
 /** GetAlphaTextureBounds succeeds but sometimes returns empty bounds like
  *  { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }
@@ -612,17 +583,17 @@ void SkScalerContext_DW::generateColorMe
         const DWRITE_COLOR_GLYPH_RUN* colorGlyph;
         HRVM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
 
         SkPath path;
         SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
         HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
             "Could not create geometry to path converter.");
         {
-            MaybeExclusive l(this);
+            SkAutoExclusive l(DWriteFactoryMutex);
             HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
                     colorGlyph->glyphRun.fontEmSize,
                     colorGlyph->glyphRun.glyphIndices,
                     colorGlyph->glyphRun.glyphAdvances,
                     colorGlyph->glyphRun.glyphOffsets,
                     colorGlyph->glyphRun.glyphCount,
                     colorGlyph->glyphRun.isSideways,
                     colorGlyph->glyphRun.bidiLevel % 2, //rtl
@@ -971,17 +942,17 @@ const void* SkScalerContext_DW::drawDWMa
     run.fontEmSize = SkScalarToFloat(fTextSizeRender);
     run.bidiLevel = 0;
     run.glyphIndices = &index;
     run.isSideways = FALSE;
     run.glyphOffsets = &offset;
     {
         SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
         {
-            MaybeExclusive l(this);
+            SkAutoExclusive l(DWriteFactoryMutex);
             // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
             if (this->getDWriteTypeface()->fFactory2 &&
                     (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
                      fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
             {
                 HRNM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(&run,
                          &fXform,
                          renderingMode,
@@ -1007,17 +978,17 @@ const void* SkScalerContext_DW::drawDWMa
         //NOTE: this assumes that the glyph has already been measured
         //with an exact same glyph run analysis.
         RECT bbox;
         bbox.left = glyph.fLeft;
         bbox.top = glyph.fTop;
         bbox.right = glyph.fLeft + glyph.fWidth;
         bbox.bottom = glyph.fTop + glyph.fHeight;
         {
-            MaybeShared l(this);
+            Shared l(DWriteFactoryMutex);
             HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType,
                     &bbox,
                     fBits.begin(),
                     sizeNeeded),
                 "Could not draw mask.");
         }
     }
     return fBits.begin();
@@ -1071,17 +1042,17 @@ void SkScalerContext_DW::generateColorGl
         }
         paint.setColor(color);
 
         SkPath path;
         SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
         HRVM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
              "Could not create geometry to path converter.");
         {
-            MaybeExclusive l(this);
+            SkAutoExclusive l(DWriteFactoryMutex);
             HRVM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
                 colorGlyph->glyphRun.fontEmSize,
                 colorGlyph->glyphRun.glyphIndices,
                 colorGlyph->glyphRun.glyphAdvances,
                 colorGlyph->glyphRun.glyphOffsets,
                 colorGlyph->glyphRun.glyphCount,
                 colorGlyph->glyphRun.isSideways,
                 colorGlyph->glyphRun.bidiLevel % 2, //rtl
@@ -1210,17 +1181,17 @@ bool SkScalerContext_DW::generatePath(Sk
 
     path->reset();
 
     SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
     HRBM(SkDWriteGeometrySink::Create(path, &geometryToPath),
          "Could not create geometry to path converter.");
     UINT16 glyphId = SkTo<UINT16>(glyph);
     {
-        MaybeExclusive l(this);
+        SkAutoExclusive l(DWriteFactoryMutex);
         //TODO: convert to<->from DIUs? This would make a difference if hinting.
         //It may not be needed, it appears that DirectWrite only hints at em size.
         HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline(
              SkScalarToFloat(fTextSizeRender),
              &glyphId,
              nullptr, //advances
              nullptr, //offsets
              1, //num glyphs
--- a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h
+++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.h
@@ -21,20 +21,16 @@ class SkDescriptor;
 
 class SkScalerContext_DW : public SkScalerContext {
 public:
     SkScalerContext_DW(sk_sp<DWriteFontTypeface>,
                        const SkScalerContextEffects&,
                        const SkDescriptor*);
     ~SkScalerContext_DW() override;
 
-    // The IDWriteFontFace4 interface is only available in DWrite 3,
-    // so checking if it was found is sufficient to detect DWrite 3.
-    bool isDWrite3() { return bool(getDWriteTypeface()->fDWriteFontFace4); }
-
 protected:
     unsigned generateGlyphCount() override;
     uint16_t generateCharToGlyph(SkUnichar uni) override;
     bool generateAdvance(SkGlyph* glyph) override;
     void generateMetrics(SkGlyph* glyph) override;
     void generateImage(const SkGlyph& glyph) override;
     bool generatePath(SkGlyphID glyph, SkPath* path) override;
     void generateFontMetrics(SkFontMetrics*) override;
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -241,16 +241,21 @@ void TransactionWrapper::UpdateScrollPos
     const wr::LayoutPoint& aScrollPosition) {
   wr_transaction_scroll_layer(mTxn, aPipelineId, aScrollId, aScrollPosition);
 }
 
 void TransactionWrapper::UpdatePinchZoom(float aZoom) {
   wr_transaction_pinch_zoom(mTxn, aZoom);
 }
 
+void TransactionWrapper::UpdateIsTransformPinchZooming(uint64_t aAnimationId,
+                                                       bool aIsZooming) {
+  wr_transaction_set_is_transform_pinch_zooming(mTxn, aAnimationId, aIsZooming);
+}
+
 /*static*/
 already_AddRefed<WebRenderAPI> WebRenderAPI::Create(
     layers::CompositorBridgeParent* aBridge,
     RefPtr<widget::CompositorWidget>&& aWidget, const wr::WrWindowId& aWindowId,
     LayoutDeviceIntSize aSize) {
   MOZ_ASSERT(aBridge);
   MOZ_ASSERT(aWidget);
   static_assert(
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -188,16 +188,17 @@ class TransactionWrapper final {
 
   void AppendTransformProperties(
       const nsTArray<wr::WrTransformProperty>& aTransformArray);
   void UpdateScrollPosition(
       const wr::WrPipelineId& aPipelineId,
       const layers::ScrollableLayerGuid::ViewID& aScrollId,
       const wr::LayoutPoint& aScrollPosition);
   void UpdatePinchZoom(float aZoom);
+  void UpdateIsTransformPinchZooming(uint64_t aAnimationId, bool aIsZooming);
 
  private:
   Transaction* mTxn;
 };
 
 class WebRenderAPI final {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderAPI);
 
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1562,16 +1562,25 @@ pub extern "C" fn wr_transaction_scroll_
 pub extern "C" fn wr_transaction_pinch_zoom(
     txn: &mut Transaction,
     pinch_zoom: f32
 ) {
     txn.set_pinch_zoom(ZoomFactor::new(pinch_zoom));
 }
 
 #[no_mangle]
+pub extern "C" fn wr_transaction_set_is_transform_pinch_zooming(
+    txn: &mut Transaction,
+    animation_id: u64,
+    is_zooming: bool
+) {
+    txn.set_is_transform_pinch_zooming(is_zooming, PropertyBindingId::new(animation_id));
+}
+
+#[no_mangle]
 pub extern "C" fn wr_resource_updates_add_image(
     txn: &mut Transaction,
     image_key: WrImageKey,
     descriptor: &WrImageDescriptor,
     bytes: &mut WrVecU8,
 ) {
     txn.add_image(
         image_key,
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -462,38 +462,41 @@ struct SegmentInstanceData {
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatchBuilder {
     pub batch_lists: Vec<BatchList>,
     screen_size: DeviceIntSize,
     break_advanced_blend_batches: bool,
     render_task_id: RenderTaskId,
+    render_task_address: RenderTaskAddress,
 }
 
 impl AlphaBatchBuilder {
     pub fn new(
         screen_size: DeviceIntSize,
         break_advanced_blend_batches: bool,
         render_task_id: RenderTaskId,
+        render_task_address: RenderTaskAddress,
     ) -> Self {
         let batch_lists = vec![
             BatchList::new(
                 screen_size,
                 Vec::new(),
                 Vec::new(),
                 break_advanced_blend_batches,
             ),
         ];
 
         AlphaBatchBuilder {
             batch_lists,
             screen_size,
             break_advanced_blend_batches,
             render_task_id,
+            render_task_address,
         }
     }
 
     fn push_new_batch_list(
         &mut self,
         regions: Vec<DeviceIntRect>,
         tile_blits: Vec<TileBlit>,
     ) {
@@ -541,50 +544,120 @@ impl AlphaBatchBuilder {
             }
         }
     }
 }
 
 /// Supports (recursively) adding a list of primitives and pictures to an alpha batch
 /// builder. In future, it will support multiple dirty regions / slices, allowing the
 /// contents of a picture to be spliced into multiple batch builders.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchBuilder {
     /// A temporary buffer that is used during glyph fetching, stored here
     /// to reduce memory allocations.
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
+
+    /// The batchers that primitives will be added to as the
+    /// picture tree is traversed.
+    batcher: AlphaBatchBuilder,
 }
 
 impl BatchBuilder {
-    pub fn new() -> Self {
+    pub fn new(
+        batcher: AlphaBatchBuilder,
+    ) -> Self {
         BatchBuilder {
             glyph_fetch_buffer: Vec::new(),
+            batcher,
         }
     }
 
+    pub fn finalize(self) -> AlphaBatchBuilder {
+        self.batcher
+    }
+
+    fn add_brush_instance_to_batches(
+        &mut self,
+        batch_key: BatchKey,
+        bounding_rect: &PictureRect,
+        z_id: ZBufferId,
+        segment_index: i32,
+        edge_flags: EdgeAaSegmentMask,
+        clip_task_address: RenderTaskAddress,
+        brush_flags: BrushFlags,
+        prim_header_index: PrimitiveHeaderIndex,
+        user_data: i32,
+    ) {
+        // TODO(gw): In future, this will be a loop adding the primitive
+        //           to multiple batch list(s), depending on the primitive
+        //           visibility mask.
+
+        let render_task_address = self.batcher.render_task_address;
+
+        let instance = BrushInstance {
+            segment_index,
+            edge_flags,
+            clip_task_address,
+            render_task_address,
+            brush_flags,
+            prim_header_index,
+            user_data,
+        };
+
+        self.batcher.current_batch_list().push_single_instance(
+            batch_key,
+            bounding_rect,
+            z_id,
+            PrimitiveInstanceData::from(instance),
+        );
+    }
+
+    fn add_split_composite_instance_to_batches(
+        &mut self,
+        batch_key: BatchKey,
+        bounding_rect: &PictureRect,
+        z_id: ZBufferId,
+        prim_header_index: PrimitiveHeaderIndex,
+        polygons_address: GpuCacheAddress,
+    ) {
+        // TODO(gw): In future, this will be a loop adding the primitive
+        //           to multiple batch list(s), depending on the primitive
+        //           visibility mask.
+
+        let render_task_address = self.batcher.render_task_address;
+
+        self.batcher.current_batch_list().push_single_instance(
+            batch_key,
+            bounding_rect,
+            z_id,
+            PrimitiveInstanceData::from(SplitCompositeInstance {
+                prim_header_index,
+                render_task_address,
+                polygons_address,
+                z: z_id,
+            }),
+        );
+    }
+
     /// Add a picture to a given batch builder.
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
-        batcher: &mut AlphaBatchBuilder,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         // Add each run in this picture to the batch.
         for prim_instance in &pic.prim_list.prim_instances {
             self.add_prim_to_batch(
                 prim_instance,
-                batcher,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 prim_headers,
                 transforms,
                 root_spatial_node_index,
                 z_generator,
@@ -594,17 +667,16 @@ impl BatchBuilder {
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
     fn add_prim_to_batch(
         &mut self,
         prim_instance: &PrimitiveInstance,
-        batcher: &mut AlphaBatchBuilder,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
@@ -636,17 +708,16 @@ impl BatchBuilder {
 
         let prim_common_data = &ctx.data_stores.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
             prim_common_data.prim_size,
         );
 
         let snap_offsets = prim_info.snap_offsets;
-        let render_task_address = render_tasks.get_task_address(batcher.render_task_id);
 
         if is_chased {
             println!("\tbatch {:?} with bound {:?}", prim_rect, bounding_rect);
         }
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Clear { data_handle } => {
                 let prim_data = &ctx.data_stores.prim[data_handle];
@@ -676,31 +747,26 @@ impl BatchBuilder {
                     textures: BatchTextures::no_texture(),
                 };
 
                 let clip_task_address = ctx.get_prim_clip_task_address(
                     prim_info.clip_task_index,
                     render_tasks,
                 ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: INVALID_SEGMENT_INDEX,
-                    edge_flags: EdgeAaSegmentMask::all(),
-                    clip_task_address,
-                    render_task_address,
-                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                    prim_header_index,
-                    user_data: 0,
-                });
-
-                batcher.current_batch_list().push_single_instance(
+                self.add_brush_instance_to_batches(
                     batch_key,
                     bounding_rect,
                     z_id,
-                    PrimitiveInstanceData::from(instance),
+                    INVALID_SEGMENT_INDEX,
+                    EdgeAaSegmentMask::all(),
+                    clip_task_address,
+                    BrushFlags::PERSPECTIVE_INTERPOLATION,
+                    prim_header_index,
+                    0,
                 );
             }
             PrimitiveInstanceKind::NormalBorder { data_handle, ref cache_handles, .. } => {
                 let prim_data = &ctx.data_stores.normal_border[data_handle];
                 let common_data = &prim_data.common;
                 let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
                 let cache_handles = &ctx.scratch.border_cache_handles[*cache_handles];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
@@ -753,56 +819,75 @@ impl BatchBuilder {
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 let border_data = &prim_data.kind;
                 self.add_segmented_prim_to_batch(
-                    batcher,
                     Some(border_data.brush_segments.as_slice()),
                     common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    render_task_address,
                     prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => {
                 let run = &ctx.prim_store.text_runs[run_index];
                 let subpx_dir = run.used_font.get_subpx_dir();
 
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
                 let prim_data = &ctx.data_stores.text_run[data_handle];
-                let alpha_batch_list = &mut batcher.batch_lists.last_mut().unwrap().alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
                     snap_offsets,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let clip_task_address = ctx.get_prim_clip_task_address(
                     prim_info.clip_task_index,
                     render_tasks,
                 ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                 let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
+                let rasterization_space = match run.raster_space {
+                    RasterSpace::Screen => RasterizationSpace::Screen,
+                    RasterSpace::Local(..) => RasterizationSpace::Local,
+                };
+                let raster_scale = run.raster_space.local_scale().unwrap_or(1.0).max(0.001);
+                let prim_header_index = prim_headers.push(
+                    &prim_header,
+                    z_id,
+                    [
+                        (run.reference_frame_relative_offset.x * 256.0) as i32,
+                        (run.reference_frame_relative_offset.y * 256.0) as i32,
+                        (raster_scale * 65535.0).round() as i32,
+                        clip_task_address.0 as i32,
+                    ],
+                );
+                let base_instance = GlyphInstance::new(
+                    prim_header_index,
+                );
+                let alpha_batch_list = &mut self.batcher.batch_lists.last_mut().unwrap().alpha_batch_list;
+                let render_task_address = render_tasks.get_task_address(
+                    self.batcher.render_task_id,
+                );
 
                 ctx.resource_cache.fetch_glyphs(
                     run.used_font.clone(),
                     &glyph_keys,
                     &mut self.glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, TextureSource::Invalid);
@@ -860,41 +945,23 @@ impl BatchBuilder {
                             GlyphFormat::ColorBitmap => {
                                 (
                                     BlendMode::PremultipliedAlpha,
                                     ShaderColorMode::ColorBitmap,
                                 )
                             }
                         };
 
-                        let raster_scale = run.raster_space.local_scale().unwrap_or(1.0).max(0.001);
-                        let prim_header_index = prim_headers.push(
-                            &prim_header,
-                            z_id,
-                            [
-                                (run.reference_frame_relative_offset.x * 256.0) as i32,
-                                (run.reference_frame_relative_offset.y * 256.0) as i32,
-                                (raster_scale * 65535.0).round() as i32,
-                                clip_task_address.0 as i32,
-                            ],
-                        );
                         let key = BatchKey::new(kind, blend_mode, textures);
-                        let base_instance = GlyphInstance::new(
-                            prim_header_index,
-                        );
                         let batch = alpha_batch_list.set_params_and_get_batch(
                             key,
                             bounding_rect,
                             z_id,
                         );
 
-                        let rasterization_space = match run.raster_space {
-                            RasterSpace::Screen => RasterizationSpace::Screen,
-                            RasterSpace::Local(..) => RasterizationSpace::Local,
-                        };
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run | ((render_task_address.0 as i32) << 16),
                                 glyph.uv_rect_address.as_int(),
                                 (rasterization_space as i32) << 16 |
                                 (subpx_dir as u32 as i32) << 8 |
                                 (color_mode as u32 as i32),
                             ));
@@ -971,31 +1038,26 @@ impl BatchBuilder {
                     textures: textures,
                 };
 
                 let clip_task_address = ctx.get_prim_clip_task_address(
                     prim_info.clip_task_index,
                     render_tasks,
                 ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: INVALID_SEGMENT_INDEX,
-                    edge_flags: EdgeAaSegmentMask::all(),
-                    clip_task_address,
-                    render_task_address,
-                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                    prim_header_index,
-                    user_data: segment_user_data,
-                });
-
-                batcher.current_batch_list().push_single_instance(
+                self.add_brush_instance_to_batches(
                     batch_key,
                     bounding_rect,
                     z_id,
-                    PrimitiveInstanceData::from(instance),
+                    INVALID_SEGMENT_INDEX,
+                    EdgeAaSegmentMask::all(),
+                    clip_task_address,
+                    BrushFlags::PERSPECTIVE_INTERPOLATION,
+                    prim_header_index,
+                    segment_user_data,
                 );
             }
             PrimitiveInstanceKind::Picture { pic_index, segment_instance_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
                 let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
 
                 let prim_header = PrimitiveHeader {
@@ -1069,28 +1131,22 @@ impl BatchBuilder {
                             ]);
 
                             let key = BatchKey::new(
                                 BatchKind::SplitComposite,
                                 BlendMode::PremultipliedAlpha,
                                 BatchTextures::no_texture(),
                             );
 
-                            let instance = SplitCompositeInstance::new(
-                                prim_header_index,
-                                child.gpu_address,
-                                render_task_address,
-                                z_id,
-                            );
-
-                            batcher.current_batch_list().push_single_instance(
+                            self.add_split_composite_instance_to_batches(
                                 key,
                                 &prim_info.clip_chain.pic_clip_rect,
                                 z_id,
-                                PrimitiveInstanceData::from(instance),
+                                prim_header_index,
+                                child.gpu_address,
                             );
                         }
                     }
                     // Ignore the 3D pictures that are not in the root of preserve-3D
                     // hierarchy, since we process them with the root.
                     Picture3DContext::In { root_data: None, .. } => return,
                     // Proceed for non-3D pictures.
                     Picture3DContext::Out => ()
@@ -1121,17 +1177,16 @@ impl BatchBuilder {
                                 let tile_cache = picture.tile_cache.as_ref().unwrap();
 
                                 // If the tile cache is disabled, just recurse into the
                                 // picture like a normal pass-through picture, adding
                                 // any child primitives into the parent surface batches.
                                 if !tile_cache.is_enabled {
                                     self.add_pic_to_batch(
                                         picture,
-                                        batcher,
                                         ctx,
                                         gpu_cache,
                                         render_tasks,
                                         deferred_resolves,
                                         prim_headers,
                                         transforms,
                                         root_spatial_node_index,
                                         z_generator,
@@ -1194,45 +1249,35 @@ impl BatchBuilder {
                                             BlendMode::None,
                                             BatchTextures::color(cache_item.texture_id),
                                         );
 
                                         let uv_rect_address = gpu_cache
                                             .get_address(&cache_item.uv_rect_handle)
                                             .as_int();
 
-                                        let instance = BrushInstance {
-                                            prim_header_index,
-                                            clip_task_address,
-                                            render_task_address,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            user_data: uv_rect_address,
-                                        };
-
-                                        // Instead of retrieving the batch once and adding each tile instance,
-                                        // use this API to get an appropriate batch for each tile, since
-                                        // the batch textures may be different. The batch list internally
-                                        // caches the current batch if the key hasn't changed.
-                                        let batch = batcher.current_batch_list().set_params_and_get_batch(
+                                        self.add_brush_instance_to_batches(
                                             key,
                                             bounding_rect,
                                             z_id,
+                                            INVALID_SEGMENT_INDEX,
+                                            EdgeAaSegmentMask::empty(),
+                                            clip_task_address,
+                                            brush_flags,
+                                            prim_header_index,
+                                            uv_rect_address,
                                         );
-
-                                        batch.push(PrimitiveInstanceData::from(instance));
                                     }
 
                                     // If there is a dirty rect for the tile cache, recurse into the
                                     // main picture primitive list, and draw them first.
                                     if !tile_cache.dirty_region.is_empty() {
                                         let mut tile_blits = Vec::new();
 
-                                        let (target_rect, _) = render_tasks[batcher.render_task_id]
+                                        let (target_rect, _) = render_tasks[self.batcher.render_task_id]
                                             .get_target_rect();
 
                                         for blit in &tile_cache.pending_blits {
                                             tile_blits.push(TileBlit {
                                                 dest_offset: blit.dest_offset,
                                                 size: blit.size,
                                                 target: blit.target.clone(),
                                                 src_offset: DeviceIntPoint::new(
@@ -1248,35 +1293,34 @@ impl BatchBuilder {
                                             .dirty_region
                                             .dirty_rects
                                             .iter()
                                             .map(|dirty_rect| {
                                                 (dirty_rect.world_rect * ctx.global_device_pixel_scale).round().to_i32()
                                             })
                                             .collect();
 
-                                        batcher.push_new_batch_list(
+                                        self.batcher.push_new_batch_list(
                                             batch_regions,
                                             tile_blits,
                                         );
 
                                         self.add_pic_to_batch(
                                             picture,
-                                            batcher,
                                             ctx,
                                             gpu_cache,
                                             render_tasks,
                                             deferred_resolves,
                                             prim_headers,
                                             transforms,
                                             root_spatial_node_index,
                                             z_generator,
                                         );
 
-                                        batcher.push_new_batch_list(
+                                        self.batcher.push_new_batch_list(
                                             Vec::new(),
                                             Vec::new(),
                                         );
                                     }
                                 }
                             }
                             PictureCompositeMode::Filter(ref filter) => {
                                 assert!(filter.is_visible());
@@ -1296,31 +1340,26 @@ impl BatchBuilder {
                                         );
                                         let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
                                             0,
                                         ]);
 
-                                        let instance = BrushInstance {
-                                            prim_header_index,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            render_task_address,
-                                            clip_task_address,
-                                            user_data: uv_rect_address.as_int(),
-                                        };
-
-                                        batcher.current_batch_list().push_single_instance(
+                                        self.add_brush_instance_to_batches(
                                             key,
                                             bounding_rect,
                                             z_id,
-                                            PrimitiveInstanceData::from(instance),
+                                            INVALID_SEGMENT_INDEX,
+                                            EdgeAaSegmentMask::empty(),
+                                            clip_task_address,
+                                            brush_flags,
+                                            prim_header_index,
+                                            uv_rect_address.as_int(),
                                         );
                                     }
                                     Filter::DropShadows(shadows) => {
                                         // Draw an instance per shadow first, following by the content.
 
                                         // The shadows and the content get drawn as a brush image.
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
@@ -1371,57 +1410,47 @@ impl BatchBuilder {
 
                                             let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id, [
                                                 ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                                 RasterizationSpace::Screen as i32,
                                                 get_shader_opacity(1.0),
                                                 0,
                                             ]);
 
-                                            let shadow_instance = BrushInstance {
-                                                prim_header_index: shadow_prim_header_index,
-                                                clip_task_address,
-                                                render_task_address,
-                                                segment_index: INVALID_SEGMENT_INDEX,
-                                                edge_flags: EdgeAaSegmentMask::empty(),
-                                                brush_flags,
-                                                user_data: shadow_uv_rect_address,
-                                            };
-
-                                            batcher.current_batch_list().push_single_instance(
+                                            self.add_brush_instance_to_batches(
                                                 shadow_key,
                                                 bounding_rect,
                                                 z_id,
-                                                PrimitiveInstanceData::from(shadow_instance),
+                                                INVALID_SEGMENT_INDEX,
+                                                EdgeAaSegmentMask::empty(),
+                                                clip_task_address,
+                                                brush_flags,
+                                                shadow_prim_header_index,
+                                                shadow_uv_rect_address,
                                             );
                                         }
                                         let z_id_content = z_generator.next();
 
                                         let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
                                             0,
                                         ]);
 
-                                        let content_instance = BrushInstance {
-                                            prim_header_index: content_prim_header_index,
-                                            clip_task_address,
-                                            render_task_address,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            user_data: content_uv_rect_address,
-                                        };
-
-                                        batcher.current_batch_list().push_single_instance(
+                                        self.add_brush_instance_to_batches(
                                             content_key,
                                             bounding_rect,
                                             z_id_content,
-                                            PrimitiveInstanceData::from(content_instance),
+                                            INVALID_SEGMENT_INDEX,
+                                            EdgeAaSegmentMask::empty(),
+                                            clip_task_address,
+                                            brush_flags,
+                                            content_prim_header_index,
+                                            content_uv_rect_address,
                                         );
                                     }
                                     _ => {
                                         let filter_mode = match filter {
                                             Filter::Identity => 1, // matches `Contrast(1)`
                                             Filter::Blur(..) => 0,
                                             Filter::Contrast(..) => 1,
                                             Filter::Grayscale(..) => 2,
@@ -1477,31 +1506,26 @@ impl BatchBuilder {
 
                                         let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             uv_rect_address.as_int(),
                                             filter_mode,
                                             user_data,
                                             0,
                                         ]);
 
-                                        let instance = BrushInstance {
-                                            prim_header_index,
-                                            clip_task_address,
-                                            render_task_address,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            user_data: 0,
-                                        };
-
-                                        batcher.current_batch_list().push_single_instance(
+                                        self.add_brush_instance_to_batches(
                                             key,
                                             bounding_rect,
                                             z_id,
-                                            PrimitiveInstanceData::from(instance),
+                                            INVALID_SEGMENT_INDEX,
+                                            EdgeAaSegmentMask::empty(),
+                                            clip_task_address,
+                                            brush_flags,
+                                            prim_header_index,
+                                            0,
                                         );
                                     }
                                 }
                             }
                             PictureCompositeMode::ComponentTransferFilter(handle) => {
                                 // This is basically the same as the general filter case above
                                 // except we store a little more data in the filter mode and
                                 // a gpu cache handle in the user data.
@@ -1527,31 +1551,26 @@ impl BatchBuilder {
 
                                 let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                     uv_rect_address.as_int(),
                                     filter_mode,
                                     user_data,
                                     0,
                                 ]);
 
-                                let instance = BrushInstance {
-                                    prim_header_index,
-                                    clip_task_address,
-                                    render_task_address,
-                                    segment_index: INVALID_SEGMENT_INDEX,
-                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                    brush_flags,
-                                    user_data: 0,
-                                };
-
-                                batcher.current_batch_list().push_single_instance(
+                                self.add_brush_instance_to_batches(
                                     key,
                                     bounding_rect,
                                     z_id,
-                                    PrimitiveInstanceData::from(instance),
+                                    INVALID_SEGMENT_INDEX,
+                                    EdgeAaSegmentMask::empty(),
+                                    clip_task_address,
+                                    brush_flags,
+                                    prim_header_index,
+                                    0,
                                 );
                             }
                             PictureCompositeMode::MixBlend(mode) if ctx.use_advanced_blending => {
                                 let (uv_rect_address, textures) = render_tasks.resolve_surface(
                                     surface.expect("bug: surface must be allocated by now"),
                                     gpu_cache,
                                 );
                                 let key = BatchKey::new(
@@ -1563,72 +1582,62 @@ impl BatchBuilder {
                                 );
                                 let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                     ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                     RasterizationSpace::Local as i32,
                                     get_shader_opacity(1.0),
                                     0,
                                 ]);
 
-                                let instance = BrushInstance {
-                                    prim_header_index,
-                                    clip_task_address,
-                                    render_task_address,
-                                    segment_index: INVALID_SEGMENT_INDEX,
-                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                    brush_flags,
-                                    user_data: uv_rect_address.as_int(),
-                                };
-
-                                batcher.current_batch_list().push_single_instance(
+                                self.add_brush_instance_to_batches(
                                     key,
                                     bounding_rect,
                                     z_id,
-                                    PrimitiveInstanceData::from(instance),
+                                    INVALID_SEGMENT_INDEX,
+                                    EdgeAaSegmentMask::empty(),
+                                    clip_task_address,
+                                    brush_flags,
+                                    prim_header_index,
+                                    uv_rect_address.as_int(),
                                 );
                             }
                             PictureCompositeMode::MixBlend(mode) => {
                                 let cache_task_id = surface.expect("bug: surface must be allocated by now");
                                 let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
 
                                 let key = BatchKey::new(
                                     BatchKind::Brush(
                                         BrushBatchKind::MixBlend {
-                                            task_id: batcher.render_task_id,
+                                            task_id: self.batcher.render_task_id,
                                             source_id: cache_task_id,
                                             backdrop_id,
                                         },
                                     ),
                                     BlendMode::PremultipliedAlpha,
                                     BatchTextures::no_texture(),
                                 );
                                 let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
                                 let source_task_address = render_tasks.get_task_address(cache_task_id);
                                 let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                     mode as u32 as i32,
                                     backdrop_task_address.0 as i32,
                                     source_task_address.0 as i32,
                                     0,
                                 ]);
 
-                                let instance = BrushInstance {
-                                    prim_header_index,
-                                    clip_task_address,
-                                    render_task_address,
-                                    segment_index: INVALID_SEGMENT_INDEX,
-                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                    brush_flags,
-                                    user_data: 0,
-                                };
-
-                                batcher.current_batch_list().push_single_instance(
+                                self.add_brush_instance_to_batches(
                                     key,
                                     bounding_rect,
                                     z_id,
-                                    PrimitiveInstanceData::from(instance),
+                                    INVALID_SEGMENT_INDEX,
+                                    EdgeAaSegmentMask::empty(),
+                                    clip_task_address,
+                                    brush_flags,
+                                    prim_header_index,
+                                    0,
                                 );
                             }
                             PictureCompositeMode::Blit(_) => {
                                 let cache_task_id = surface.expect("bug: surface must be allocated by now");
                                 let uv_rect_address = render_tasks[cache_task_id]
                                     .get_texture_address(gpu_cache)
                                     .as_int();
                                 let batch_params = BrushBatchParameters::shared(
@@ -1674,40 +1683,37 @@ impl BatchBuilder {
                                 //           simple, common cases) if the picture content is opaque.
                                 //           That would allow inner segments of pictures to be drawn
                                 //           with blend disabled, which is a big performance win on
                                 //           integrated GPUs.
                                 let opacity = PrimitiveOpacity::translucent();
                                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                                 self.add_segmented_prim_to_batch(
-                                    batcher,
                                     segments,
                                     opacity,
                                     &batch_params,
                                     specified_blend_mode,
                                     non_segmented_blend_mode,
                                     prim_header_index,
                                     bounding_rect,
                                     transform_kind,
                                     render_tasks,
                                     z_id,
-                                    render_task_address,
                                     prim_info.clip_task_index,
                                     ctx,
                                 );
                             }
                         }
                     }
                     None => {
                         // If this picture is being drawn into an existing target (i.e. with
                         // no composition operation), recurse and add to the current batch list.
                         self.add_pic_to_batch(
                             picture,
-                            batcher,
                             ctx,
                             gpu_cache,
                             render_tasks,
                             deferred_resolves,
                             prim_headers,
                             transforms,
                             root_spatial_node_index,
                             z_generator,
@@ -1764,28 +1770,26 @@ impl BatchBuilder {
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 self.add_segmented_prim_to_batch(
-                    batcher,
                     Some(border_data.brush_segments.as_slice()),
                     common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    render_task_address,
                     prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => {
                 let prim_data = &ctx.data_stores.prim[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index);
@@ -1827,28 +1831,26 @@ impl BatchBuilder {
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 self.add_segmented_prim_to_batch(
-                    batcher,
                     segments,
                     opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    render_task_address,
                     prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => {
                 let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind;
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
@@ -1936,28 +1938,26 @@ impl BatchBuilder {
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
                     batch_params.prim_user_data,
                 );
 
                 self.add_segmented_prim_to_batch(
-                    batcher,
                     segments,
                     prim_common_data.opacity,
                     &batch_params,
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    render_task_address,
                     prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
                 let image_data = &ctx.data_stores.image[data_handle].kind;
                 let common_data = &ctx.data_stores.image[data_handle].common;
                 let image_instance = &ctx.prim_store.images[image_instance_index];
@@ -2042,28 +2042,26 @@ impl BatchBuilder {
 
                     let prim_header_index = prim_headers.push(
                         &prim_header,
                         z_id,
                         batch_params.prim_user_data,
                     );
 
                     self.add_segmented_prim_to_batch(
-                        batcher,
                         segments,
                         opacity,
                         &batch_params,
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        render_task_address,
                         prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     const VECS_PER_SPECIFIC_BRUSH: usize = 3;
                     let max_tiles_per_header = (MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_SPECIFIC_BRUSH) / VECS_PER_SEGMENT;
 
                     let clip_task_address = ctx.get_prim_clip_task_address(
@@ -2097,35 +2095,31 @@ impl BatchBuilder {
 
                         for (i, tile) in chunk.iter().enumerate() {
                             if let Some((batch_kind, textures, uv_rect_address)) = get_image_tile_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                                 request.with_tile(tile.tile_offset),
                             ) {
-                                let base_instance = BrushInstance {
-                                    prim_header_index,
-                                    clip_task_address,
-                                    render_task_address,
-                                    segment_index: i as i32,
-                                    edge_flags: tile.edge_flags,
-                                    brush_flags: BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION,
-                                    user_data: uv_rect_address.as_int(),
-                                };
                                 let batch_key = BatchKey {
                                     blend_mode: specified_blend_mode,
                                     kind: BatchKind::Brush(batch_kind),
                                     textures,
                                 };
-                                batcher.current_batch_list().push_single_instance(
+                                self.add_brush_instance_to_batches(
                                     batch_key,
                                     bounding_rect,
                                     z_id,
-                                    base_instance.into(),
+                                    i as i32,
+                                    tile.edge_flags,
+                                    clip_task_address,
+                                    BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION,
+                                    prim_header_index,
+                                    uv_rect_address.as_int(),
                                 );
                             }
                         }
                     }
                 }
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => {
                 let gradient = &ctx.prim_store.linear_gradients[gradient_index];
@@ -2182,31 +2176,26 @@ impl BatchBuilder {
                         textures: textures,
                     };
 
                     let clip_task_address = ctx.get_prim_clip_task_address(
                         prim_info.clip_task_index,
                         render_tasks,
                     ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                    let instance = PrimitiveInstanceData::from(BrushInstance {
-                        segment_index: INVALID_SEGMENT_INDEX,
-                        edge_flags: EdgeAaSegmentMask::all(),
-                        clip_task_address,
-                        render_task_address,
-                        brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                        prim_header_index,
-                        user_data: segment_user_data,
-                    });
-
-                    batcher.current_batch_list().push_single_instance(
+                    self.add_brush_instance_to_batches(
                         batch_key,
                         bounding_rect,
                         z_id,
-                        PrimitiveInstanceData::from(instance),
+                        INVALID_SEGMENT_INDEX,
+                        EdgeAaSegmentMask::all(),
+                        clip_task_address,
+                        BrushFlags::PERSPECTIVE_INTERPOLATION,
+                        prim_header_index,
+                        segment_user_data,
                     );
                 } else if gradient.visible_tiles_range.is_empty() {
                     let batch_params = BrushBatchParameters::shared(
                         BrushBatchKind::LinearGradient,
                         BatchTextures::no_texture(),
                         [
                             prim_data.stops_handle.as_int(gpu_cache),
                             0,
@@ -2226,49 +2215,45 @@ impl BatchBuilder {
 
                     let segments = if prim_data.brush_segments.is_empty() {
                         None
                     } else {
                         Some(prim_data.brush_segments.as_slice())
                     };
 
                     self.add_segmented_prim_to_batch(
-                        batcher,
                         segments,
                         prim_data.opacity,
                         &batch_params,
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        render_task_address,
                         prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[gradient.visible_tiles_range];
 
                     let clip_task_address = ctx.get_prim_clip_task_address(
                         prim_info.clip_task_index,
                         render_tasks,
                     ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                    add_gradient_tiles(
+                    self.add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
                         BrushBatchKind::LinearGradient,
                         specified_blend_mode,
                         bounding_rect,
-                        render_task_address,
                         clip_task_address,
                         gpu_cache,
-                        batcher.current_batch_list(),
                         &prim_header,
                         prim_headers,
                         z_id,
                     );
                 }
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.data_stores.radial_grad[data_handle];
@@ -2314,74 +2299,68 @@ impl BatchBuilder {
 
                     let segments = if prim_data.brush_segments.is_empty() {
                         None
                     } else {
                         Some(prim_data.brush_segments.as_slice())
                     };
 
                     self.add_segmented_prim_to_batch(
-                        batcher,
                         segments,
                         prim_data.opacity,
                         &batch_params,
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        render_task_address,
                         prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
 
                     let clip_task_address = ctx.get_prim_clip_task_address(
                         prim_info.clip_task_index,
                         render_tasks,
                     ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
-                    add_gradient_tiles(
+                    self.add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
                         BrushBatchKind::RadialGradient,
                         specified_blend_mode,
                         bounding_rect,
-                        render_task_address,
                         clip_task_address,
                         gpu_cache,
-                        batcher.current_batch_list(),
                         &prim_header,
                         prim_headers,
                         z_id,
                     );
                 }
             }
         }
     }
 
     /// Add a single segment instance to a batch.
     fn add_segment_to_batch(
         &mut self,
-        batcher: &mut AlphaBatchBuilder,
         segment: &BrushSegment,
         segment_data: &SegmentInstanceData,
         segment_index: i32,
         batch_kind: BrushBatchKind,
         prim_header_index: PrimitiveHeaderIndex,
         alpha_blend_mode: BlendMode,
         bounding_rect: &PictureRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskGraph,
         z_id: ZBufferId,
         prim_opacity: PrimitiveOpacity,
-        render_task_address: RenderTaskAddress,
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
         debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
 
         // Get GPU address of clip task for this segment, or None if
         // the entire segment is clipped out.
         let clip_task_address = match ctx.get_clip_task_address(
@@ -2394,108 +2373,97 @@ impl BatchBuilder {
         };
 
         // If a got a valid (or OPAQUE) clip task address, add the segment.
         let is_inner = segment.edge_flags.is_empty();
         let needs_blending = !prim_opacity.is_opaque ||
                              clip_task_address != OPAQUE_TASK_ADDRESS ||
                              (!is_inner && transform_kind == TransformedRectKind::Complex);
 
-        let instance = PrimitiveInstanceData::from(BrushInstance {
-            segment_index,
-            edge_flags: segment.edge_flags,
-            clip_task_address,
-            render_task_address,
-            brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
-            prim_header_index,
-            user_data: segment_data.user_data,
-        });
-
         let batch_key = BatchKey {
             blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
             kind: BatchKind::Brush(batch_kind),
             textures: segment_data.textures,
         };
 
-        batcher.current_batch_list().push_single_instance(
+        self.add_brush_instance_to_batches(
             batch_key,
             bounding_rect,
             z_id,
-            instance,
+            segment_index,
+            segment.edge_flags,
+            clip_task_address,
+            BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
+            prim_header_index,
+            segment_data.user_data,
         );
     }
 
     /// Add any segment(s) from a brush to batches.
     fn add_segmented_prim_to_batch(
         &mut self,
-        batcher: &mut AlphaBatchBuilder,
         brush_segments: Option<&[BrushSegment]>,
         prim_opacity: PrimitiveOpacity,
         params: &BrushBatchParameters,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         prim_header_index: PrimitiveHeaderIndex,
         bounding_rect: &PictureRect,
         transform_kind: TransformedRectKind,
         render_tasks: &RenderTaskGraph,
         z_id: ZBufferId,
-        render_task_address: RenderTaskAddress,
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
         match (brush_segments, &params.segment_data) {
             (Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => {
                 // In this case, we have both a list of segments, and a list of
                 // per-segment instance data. Zip them together to build batches.
                 debug_assert_eq!(brush_segments.len(), segment_data.len());
                 for (segment_index, (segment, segment_data)) in brush_segments
                     .iter()
                     .zip(segment_data.iter())
                     .enumerate()
                 {
                     self.add_segment_to_batch(
-                        batcher,
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
                         prim_opacity,
-                        render_task_address,
                         clip_task_index,
                         ctx,
                     );
                 }
             }
             (Some(ref brush_segments), SegmentDataKind::Shared(ref segment_data)) => {
                 // A list of segments, but the per-segment data is common
                 // between all segments.
                 for (segment_index, segment) in brush_segments
                     .iter()
                     .enumerate()
                 {
                     self.add_segment_to_batch(
-                        batcher,
                         segment,
                         segment_data,
                         segment_index as i32,
                         params.batch_kind,
                         prim_header_index,
                         alpha_blend_mode,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
                         prim_opacity,
-                        render_task_address,
                         clip_task_index,
                         ctx,
                     );
                 }
             }
             (None, SegmentDataKind::Shared(ref segment_data)) => {
                 // No segments, and thus no per-segment instance data.
                 // Note: the blend mode already takes opacity into account
@@ -2503,95 +2471,86 @@ impl BatchBuilder {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(params.batch_kind),
                     textures: segment_data.textures,
                 };
                 let clip_task_address = ctx.get_prim_clip_task_address(
                     clip_task_index,
                     render_tasks,
                 ).unwrap_or(OPAQUE_TASK_ADDRESS);
-                let instance = PrimitiveInstanceData::from(BrushInstance {
-                    segment_index: INVALID_SEGMENT_INDEX,
-                    edge_flags: EdgeAaSegmentMask::all(),
-                    clip_task_address,
-                    render_task_address,
-                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                    prim_header_index,
-                    user_data: segment_data.user_data,
-                });
-                batcher.current_batch_list().push_single_instance(
+                self.add_brush_instance_to_batches(
                     batch_key,
                     bounding_rect,
                     z_id,
-                    PrimitiveInstanceData::from(instance),
+                    INVALID_SEGMENT_INDEX,
+                    EdgeAaSegmentMask::all(),
+                    clip_task_address,
+                    BrushFlags::PERSPECTIVE_INTERPOLATION,
+                    prim_header_index,
+                    segment_data.user_data,
                 );
             }
             (None, SegmentDataKind::Instanced(..)) => {
                 // We should never hit the case where there are no segments,
                 // but a list of segment instance data.
                 unreachable!();
             }
         }
     }
-}
 
-fn add_gradient_tiles(
-    visible_tiles: &[VisibleGradientTile],
-    stops_handle: &GpuCacheHandle,
-    kind: BrushBatchKind,
-    blend_mode: BlendMode,
-    bounding_rect: &PictureRect,
-    render_task_address: RenderTaskAddress,
-    clip_task_address: RenderTaskAddress,
-    gpu_cache: &GpuCache,
-    batch_list: &mut BatchList,
-    base_prim_header: &PrimitiveHeader,
-    prim_headers: &mut PrimitiveHeaders,
-    z_id: ZBufferId,
-) {
-    let batch = batch_list.set_params_and_get_batch(
-        BatchKey {
+    fn add_gradient_tiles(
+        &mut self,
+        visible_tiles: &[VisibleGradientTile],
+        stops_handle: &GpuCacheHandle,
+        kind: BrushBatchKind,
+        blend_mode: BlendMode,
+        bounding_rect: &PictureRect,
+        clip_task_address: RenderTaskAddress,
+        gpu_cache: &GpuCache,
+        base_prim_header: &PrimitiveHeader,
+        prim_headers: &mut PrimitiveHeaders,
+        z_id: ZBufferId,
+    ) {
+        let key = BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
-        },
-        bounding_rect,
-        z_id,
-    );
+        };
 
-    let user_data = [stops_handle.as_int(gpu_cache), 0, 0, 0];
+        let user_data = [stops_handle.as_int(gpu_cache), 0, 0, 0];
 
-    for tile in visible_tiles {
-        // Adjust the snap offsets for the tile.
-        let snap_offsets = recompute_snap_offsets(
-            tile.local_rect,
-            base_prim_header.local_rect,
-            base_prim_header.snap_offsets,
-        );
+        for tile in visible_tiles {
+            // Adjust the snap offsets for the tile.
+            let snap_offsets = recompute_snap_offsets(
+                tile.local_rect,
+                base_prim_header.local_rect,
+                base_prim_header.snap_offsets,
+            );
 
-        let prim_header = PrimitiveHeader {
-            specific_prim_address: gpu_cache.get_address(&tile.handle),
-            local_rect: tile.local_rect,
-            local_clip_rect: tile.local_clip_rect,
-            snap_offsets,
-            ..*base_prim_header
-        };
-        let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
+            let prim_header = PrimitiveHeader {
+                specific_prim_address: gpu_cache.get_address(&tile.handle),
+                local_rect: tile.local_rect,
+                local_clip_rect: tile.local_clip_rect,
+                snap_offsets,
+                ..*base_prim_header
+            };
+            let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
 
-        batch.push(PrimitiveInstanceData::from(
-            BrushInstance {
-                prim_header_index,
+            self.add_brush_instance_to_batches(
+                key,
+                bounding_rect,
+                z_id,
+                INVALID_SEGMENT_INDEX,
+                EdgeAaSegmentMask::all(),
                 clip_task_address,
-                render_task_address,
-                segment_index: INVALID_SEGMENT_INDEX,
-                edge_flags: EdgeAaSegmentMask::all(),
-                brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                user_data: 0,
-            }
-        ));
+                BrushFlags::PERSPECTIVE_INTERPOLATION,
+                prim_header_index,
+                0,
+            );
+        }
     }
 }
 
 fn get_image_tile_params(
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
     deferred_resolves: &mut Vec<DeferredResolve>,
     request: ImageRequest,
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -204,16 +204,17 @@ impl ClipChainId {
 // and a link to a parent clip chain node, or ClipChainId::NONE.
 #[derive(Clone, Debug, MallocSizeOf)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct ClipChainNode {
     pub handle: ClipDataHandle,
     pub local_pos: LayoutPoint,
     pub spatial_node_index: SpatialNodeIndex,
     pub parent_clip_chain_id: ClipChainId,
+    pub has_complex_clip: bool,
 }
 
 // When a clip node is found to be valid for a
 // clip chain instance, it's stored in an index
 // buffer style structure. This struct contains
 // an index to the node data itself, as well as
 // some flags describing how this clip node instance
 // is positioned.
@@ -558,23 +559,25 @@ impl ClipStore {
     }
 
     pub fn add_clip_chain_node(
         &mut self,
         handle: ClipDataHandle,
         local_pos: LayoutPoint,
         spatial_node_index: SpatialNodeIndex,
         parent_clip_chain_id: ClipChainId,
+        has_complex_clip: bool,
     ) -> ClipChainId {
         let id = ClipChainId(self.clip_chain_nodes.len() as u32);
         self.clip_chain_nodes.push(ClipChainNode {
             handle,
             spatial_node_index,
             local_pos,
             parent_clip_chain_id,
+            has_complex_clip,
         });
         id
     }
 
     pub fn get_instance_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
@@ -862,16 +865,27 @@ impl ClipItemKey {
             fract_offset.into(),
             shadow_rect.size.into(),
             shadow_radius.into(),
             prim_shadow_rect.into(),
             Au::from_f32_px(blur_radius),
             clip_mode,
         )
     }
+
+    pub fn has_complex_clip(&self) -> bool {
+        match *self {
+            ClipItemKey::Rectangle(_, ClipMode::Clip) => false,
+
+            ClipItemKey::Rectangle(_, ClipMode::ClipOut) |
+            ClipItemKey::RoundedRectangle(..) |
+            ClipItemKey::ImageMask(..) |
+            ClipItemKey::BoxShadow(..) => true,
+        }
+    }
 }
 
 impl intern::InternDebug for ClipItemKey {}
 
 impl intern::Internable for ClipIntern {
     type Key = ClipItemKey;
     type StoreData = ClipNode;
     type InternData = ();
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -25,24 +25,26 @@ pub type ScrollStates = FastHashMap<Exte
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 /// A node in the hierarchy of coordinate system
 /// transforms.
 #[derive(Debug)]
 pub struct CoordinateSystem {
     pub transform: LayoutTransform,
+    pub world_transform: LayoutToWorldTransform,
     pub should_flatten: bool,
     pub parent: Option<CoordinateSystemId>,
 }
 
 impl CoordinateSystem {
     fn root() -> Self {
         CoordinateSystem {
             transform: LayoutTransform::identity(),
+            world_transform: LayoutToWorldTransform::identity(),
             should_flatten: false,
             parent: None,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -310,42 +312,53 @@ impl ClipScrollTree {
         // we need to update the associated parameters of a transform in two cases:
         // 1) when the flattening happens, so that we don't lose that original 3D aspects
         // 2) when we reach the end of iteration, so that our result is up to date
 
         while coordinate_system_id != parent.coordinate_system_id {
             let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
 
             if coord_system.should_flatten {
-                transform.m13 = 0.0;
-                transform.m23 = 0.0;
-                transform.m33 = 1.0;
-                transform.m43 = 0.0;
+                transform.flatten_z_output();
             }
 
             coordinate_system_id = coord_system.parent.expect("invalid parent!");
             transform = transform.post_mul(&coord_system.transform);
         }
 
         transform = transform.post_mul(
             &parent.coordinate_system_relative_scale_offset
                 .inverse()
                 .to_transform(),
         );
 
         CoordinateSpaceMapping::Transform(transform)
     }
 
-    /// Calculate the relative transform from `child_index` to the scene root.
+    /// Calculate the relative transform from `index` to the root.
     pub fn get_world_transform(
         &self,
         index: SpatialNodeIndex,
     ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
-        self.get_relative_transform(index, ROOT_SPATIAL_NODE_INDEX)
-            .with_destination::<WorldPixel>()
+        let child = &self.spatial_nodes[index.0 as usize];
+
+        if child.coordinate_system_id.0 == 0 {
+            if index == ROOT_SPATIAL_NODE_INDEX {
+                CoordinateSpaceMapping::Local
+            } else {
+                CoordinateSpaceMapping::ScaleOffset(child.coordinate_system_relative_scale_offset)
+            }
+        } else {
+            let system = &self.coord_systems[child.coordinate_system_id.0 as usize];
+            let transform = child.coordinate_system_relative_scale_offset
+                .to_transform()
+                .post_mul(&system.world_transform);
+
+            CoordinateSpaceMapping::Transform(transform)
+        }
     }
 
     /// Returns true if the spatial node is the same as the parent, or is
     /// a child of the parent.
     pub fn is_same_or_child_of(
         &self,
         spatial_node_index: SpatialNodeIndex,
         parent_spatial_node_index: SpatialNodeIndex,
@@ -502,17 +515,17 @@ impl ClipScrollTree {
                     .map(|child_index| (*child_index, state.clone()))
                 );
             }
         }
     }
 
     pub fn build_transform_palette(&self) -> TransformPalette {
         let mut palette = TransformPalette::new(self.spatial_nodes.len());
-        //TODO: this could be faster by a bit of dynamic programming
+        //Note: getting the world transform of a node is O(1) operation
         for i in 0 .. self.spatial_nodes.len() {
             let index = SpatialNodeIndex(i as u32);
             let world_transform = self.get_world_transform(index).into_transform();
             palette.set_world_transform(index, world_transform);
         }
         palette
     }
 
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1215,35 +1215,41 @@ impl<'a> DisplayListFlattener<'a> {
                     // as a clip chain (one clip item per node), eventually parented
                     // to the parent clip node. For a user defined clip chain, we will
                     // need to walk the linked list of clip chain nodes for each clip
                     // node, accumulating them into one clip chain that is then
                     // parented to the clip chain parent.
 
                     for _ in 0 .. item_clip_node.count {
                         // Get the id of the clip sources entry for that clip chain node.
-                        let (handle, spatial_node_index, local_pos) = {
+                        let (handle, spatial_node_index, local_pos, has_complex_clip) = {
                             let clip_chain = self
                                 .clip_store
                                 .get_clip_chain(clip_node_clip_chain_id);
 
                             clip_node_clip_chain_id = clip_chain.parent_clip_chain_id;
 
-                            (clip_chain.handle, clip_chain.spatial_node_index, clip_chain.local_pos)
+                            (
+                                clip_chain.handle,
+                                clip_chain.spatial_node_index,
+                                clip_chain.local_pos,
+                                clip_chain.has_complex_clip,
+                            )
                         };
 
                         // Add a new clip chain node, which references the same clip sources, and
                         // parent it to the current parent.
                         clip_chain_id = self
                             .clip_store
                             .add_clip_chain_node(
                                 handle,
                                 local_pos,
                                 spatial_node_index,
                                 clip_chain_id,
+                                has_complex_clip,
                             );
                     }
                 }
 
                 // Map the last entry in the clip chain to the supplied ClipId. This makes
                 // this ClipId available as a source to other user defined clip chains.
                 self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id, 0);
             },
@@ -1303,25 +1309,27 @@ impl<'a> DisplayListFlattener<'a> {
         if clip_items.is_empty() {
             parent_clip_chain_id
         } else {
             let mut clip_chain_id = parent_clip_chain_id;
 
             for (local_pos, item) in clip_items {
                 // Intern this clip item, and store the handle
                 // in the clip chain node.
+                let has_complex_clip = item.has_complex_clip();
                 let handle = self.interners
                     .clip
                     .intern(&item, || ());
 
                 clip_chain_id = self.clip_store.add_clip_chain_node(
                     handle,
                     local_pos,
                     spatial_node_index,
                     clip_chain_id,
+                    has_complex_clip,
                 );
             }
 
             clip_chain_id
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
@@ -1580,25 +1588,37 @@ impl<'a> DisplayListFlattener<'a> {
                     Some(sc) => sc.spatial_node_index,
                     None => ROOT_SPATIAL_NODE_INDEX,
                 },
             }
         } else {
             Picture3DContext::Out
         };
 
-        // Force an intermediate surface if the stacking context
-        // has a clip node. In the future, we may decide during
+        // Force an intermediate surface if the stacking context has a
+        // complex clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        let blit_reason = if clip_chain_id == ClipChainId::NONE {
-            BlitReason::empty()
-        } else {
-            BlitReason::CLIP
-        };
+        let mut blit_reason = BlitReason::empty();
+        let mut current_clip_chain_id = clip_chain_id;
+
+        // Walk each clip in this chain, to see whether any of the clips
+        // require that we draw this to an intermediate surface.
+        while current_clip_chain_id != ClipChainId::NONE {
+            let clip_chain_node = &self
+                .clip_store
+                .clip_chain_nodes[current_clip_chain_id.0 as usize];
+
+            if clip_chain_node.has_complex_clip {
+                blit_reason = BlitReason::CLIP;
+                break;
+            }
+
+            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+        }
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
             primitives: Vec::new(),
             pipeline_id,
             is_backface_visible,
             requested_raster_space,
@@ -2016,32 +2036,34 @@ impl<'a> DisplayListFlattener<'a> {
 
         parent_clip_chain_index = self
             .clip_store
             .add_clip_chain_node(
                 handle,
                 clip_region.main.origin,
                 spatial_node,
                 parent_clip_chain_index,
+                false,
             );
         clip_count += 1;
 
         if let Some(ref image_mask) = clip_region.image_mask {
             let handle = self
                 .interners
                 .clip
                 .intern(&ClipItemKey::image_mask(image_mask), || ());
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     image_mask.rect.origin,
                     spatial_node,
                     parent_clip_chain_index,
+                    true,
                 );
             clip_count += 1;
         }
 
         for region in clip_region.complex_clips {
             let handle = self
                 .interners
                 .clip
@@ -2049,16 +2071,17 @@ impl<'a> DisplayListFlattener<'a> {
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     region.rect.origin,
                     spatial_node,
                     parent_clip_chain_index,
+                    true,
                 );
             clip_count += 1;
         }
 
         // Map the supplied ClipId -> clip chain id.
         self.id_to_index_mapper.add_clip_chain(
             new_node_id,
             parent_clip_chain_index,
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -1,14 +1,14 @@
 /* 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 api::{ColorF, DebugFlags, DocumentLayer, FontRenderMode, PremultipliedColorF};
-use api::{PipelineId, RasterSpace};
+use api::{PipelineId};
 use api::units::*;
 use crate::clip::{ClipDataStore, ClipStore, ClipChainStack};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use crate::display_list_flattener::{DisplayListFlattener};
 use crate::gpu_cache::{GpuCache, GpuCacheHandle};
 use crate::gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use crate::hit_test::{HitTester, HitTestingScene};
 #[cfg(feature = "replay")]
@@ -179,17 +179,16 @@ impl<'a> FrameBuildingState<'a> {
 
 /// Immutable context of a picture when processing children.
 #[derive(Debug)]
 pub struct PictureContext {
     pub pic_index: PictureIndex,
     pub apply_local_clip_rect: bool,
     pub is_passthrough: bool,
     pub is_composite: bool,
-    pub raster_space: RasterSpace,
     pub surface_spatial_node_index: SpatialNodeIndex,
     pub raster_spatial_node_index: SpatialNodeIndex,
     /// The surface that this picture will render on.
     pub surface_index: SurfaceIndex,
     pub dirty_region_count: usize,
 }
 
 /// Mutable state of a picture that gets modified when
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -316,32 +316,16 @@ impl GlyphInstance {
 
 pub struct SplitCompositeInstance {
     pub prim_header_index: PrimitiveHeaderIndex,
     pub polygons_address: GpuCacheAddress,
     pub z: ZBufferId,
     pub render_task_address: RenderTaskAddress,
 }
 
-impl SplitCompositeInstance {
-    pub fn new(
-        prim_header_index: PrimitiveHeaderIndex,
-        polygons_address: GpuCacheAddress,
-        render_task_address: RenderTaskAddress,
-        z: ZBufferId,
-    ) -> Self {
-        SplitCompositeInstance {
-            prim_header_index,
-            polygons_address,
-            z,
-            render_task_address,
-        }
-    }
-}
-
 impl From<SplitCompositeInstance> for PrimitiveInstanceData {
     fn from(instance: SplitCompositeInstance) -> Self {
         PrimitiveInstanceData {
             data: [
                 instance.prim_header_index.0,
                 instance.polygons_address.as_int(),
                 instance.z.0,
                 instance.render_task_address.0 as i32,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1640,17 +1640,16 @@ impl<'a> PictureUpdateState<'a> {
             surfaces,
             surface_stack: vec![SurfaceIndex(0)],
             picture_stack: Vec::new(),
             are_raster_roots_assigned: true,
         };
 
         state.update(
             pic_index,
-            ClipChainId::NONE,
             picture_primitives,
             frame_context,
             gpu_cache,
             clip_store,
             clip_data_store,
         );
 
         if !state.are_raster_roots_assigned {
@@ -1704,34 +1703,29 @@ impl<'a> PictureUpdateState<'a> {
     }
 
     /// Update a picture, determining surface configuration,
     /// rasterization roots, and (in future) whether there
     /// are cached surfaces that can be used by this picture.
     fn update(
         &mut self,
         pic_index: PictureIndex,
-        clip_chain_id: ClipChainId,
         picture_primitives: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         gpu_cache: &mut GpuCache,
         clip_store: &ClipStore,
         clip_data_store: &ClipDataStore,
     ) {
         if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
-            clip_chain_id,
             self,
             frame_context,
-            clip_store,
-            clip_data_store,
         ) {
-            for (child_pic_index, clip_chain_id) in &prim_list.pictures {
+            for child_pic_index in &prim_list.pictures {
                 self.update(
                     *child_pic_index,
-                    *clip_chain_id,
                     picture_primitives,
                     frame_context,
                     gpu_cache,
                     clip_store,
                     clip_data_store,
                 );
             }
 
@@ -1763,17 +1757,17 @@ impl<'a> PictureUpdateState<'a> {
                 if !config.establishes_raster_root {
                     surface.raster_spatial_node_index = fallback_raster_spatial_node;
                 }
                 surface.raster_spatial_node_index
             }
             None => fallback_raster_spatial_node,
         };
 
-        for (child_pic_index, _) in &picture.prim_list.pictures {
+        for child_pic_index in &picture.prim_list.pictures {
             self.assign_raster_roots(*child_pic_index, picture_primitives, new_fallback);
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct SurfaceIndex(pub usize);
@@ -1990,17 +1984,17 @@ pub struct PrimitiveClusterIndex(pub u32
 pub struct ClusterIndex(pub u16);
 
 impl ClusterIndex {
     pub const INVALID: ClusterIndex = ClusterIndex(u16::MAX);
 }
 
 /// A list of pictures, stored by the PrimitiveList to enable a
 /// fast traversal of just the pictures.
-pub type PictureList = SmallVec<[(PictureIndex, ClipChainId); 4]>;
+pub type PictureList = SmallVec<[PictureIndex; 4]>;
 
 /// A list of primitive instances that are added to a picture
 /// This ensures we can keep a list of primitives that
 /// are pictures, for a fast initial traversal of the picture
 /// tree without walking the instance list.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct PrimitiveList {
     /// The primitive instances, in render order.
@@ -2039,17 +2033,17 @@ impl PrimitiveList {
 
         // Walk the list of primitive instances and extract any that
         // are pictures.
         for prim_instance in &mut prim_instances {
             // Check if this primitive is a picture. In future we should
             // remove this match and embed this info directly in the primitive instance.
             let is_pic = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
-                    pictures.push((pic_index, prim_instance.clip_chain_id));
+                    pictures.push(pic_index);
                     true
                 }
                 _ => {
                     false
                 }
             };
 
             let prim_data = match prim_instance.kind {
@@ -2233,17 +2227,17 @@ impl PicturePrimitive {
         pt.new_level(format!("{:?}", self_index));
         pt.add_item(format!("prim_count: {:?}", self.prim_list.prim_instances.len()));
         pt.add_item(format!("snapped_local_rect: {:?}", self.snapped_local_rect));
         pt.add_item(format!("unsnapped_local_rect: {:?}", self.unsnapped_local_rect));
         pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
         pt.add_item(format!("raster_config: {:?}", self.raster_config));
         pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
 
-        for (index, _) in &self.prim_list.pictures {
+        for index in &self.prim_list.pictures {
             pictures[index.0].print(pictures, *index, pt);
         }
 
         pt.end_level();
     }
 
     /// Returns true if this picture supports segmented rendering.
     pub fn can_use_segments(&self) -> bool {
@@ -2345,16 +2339,38 @@ impl PicturePrimitive {
             snapped_local_rect: LayoutRect::zero(),
             unsnapped_local_rect: LayoutRect::zero(),
             tile_cache,
             options,
             segments_are_valid: false,
         }
     }
 
+    /// Gets the raster space to use when rendering the picture.
+    /// Usually this would be the requested raster space. However, if the
+    /// picture's spatial node or one of its ancestors is being pinch zoomed
+    /// then we round it. This prevents us rasterizing glyphs for every minor
+    /// change in zoom level, as that would be too expensive.
+    pub fn get_raster_space(&self, clip_scroll_tree: &ClipScrollTree) -> RasterSpace {
+        let spatial_node = &clip_scroll_tree.spatial_nodes[self.spatial_node_index.0 as usize];
+        if spatial_node.is_ancestor_or_self_zooming {
+            let scale_factors = clip_scroll_tree
+                .get_relative_transform(self.spatial_node_index, ROOT_SPATIAL_NODE_INDEX)
+                .scale_factors();
+
+            // Round the scale up to the nearest power of 2, but don't exceed 8.
+            let scale = scale_factors.0.max(scale_factors.1).min(8.0);
+            let rounded_up = 1 << scale.log2().ceil() as u32;
+
+            RasterSpace::Local(rounded_up as f32)
+        } else {
+            self.requested_raster_space
+        }
+    }
+
     pub fn take_context(
         &mut self,
         pic_index: PictureIndex,
         clipped_prim_bounding_rect: WorldRect,
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         parent_surface_index: SurfaceIndex,
         frame_state: &mut FrameBuildingState,
@@ -2747,17 +2763,16 @@ impl PicturePrimitive {
             dirty_region_count += 1;
         }
 
         let context = PictureContext {
             pic_index,
             apply_local_clip_rect: self.apply_local_clip_rect,
             is_composite,
             is_passthrough,
-            raster_space: self.requested_raster_space,
             raster_spatial_node_index,
             surface_spatial_node_index,
             surface_index,
             dirty_region_count,
         };
 
         let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
 
@@ -2900,21 +2915,18 @@ impl PicturePrimitive {
         }
     }
 
     /// Called during initial picture traversal, before we know the
     /// bounding rect of children. It is possible to determine the
     /// surface / raster config now though.
     fn pre_update(
         &mut self,
-        clip_chain_id: ClipChainId,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
-        clip_store: &ClipStore,
-        clip_data_store: &ClipDataStore,
     ) -> Option<PrimitiveList> {
         // Reset raster config in case we early out below.
         self.raster_config = None;
 
         // Resolve animation properties, and early out if the filter
         // properties make this picture invisible.
         if !self.resolve_scene_properties(frame_context.scene_properties) {
             return None;
@@ -2936,62 +2948,16 @@ impl PicturePrimitive {
         // Push information about this pic on stack for children to read.
         state.push_picture(PictureInfo {
             _spatial_node_index: self.spatial_node_index,
         });
 
         // See if this picture actually needs a surface for compositing.
         let actual_composite_mode = match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(ref filter)) if filter.is_noop() => None,
-            Some(PictureCompositeMode::Blit(reason)) if reason == BlitReason::CLIP => {
-                // If the only reason a picture has requested a surface is due to the clip
-                // chain node, we might choose to skip drawing a surface, and instead apply
-                // the clips to each individual primitive. The logic below works out which
-                // option to choose.
-
-                // Assume that we will apply clips to individual items
-                let mut apply_clip_to_picture = false;
-                let mut current_clip_chain_id = clip_chain_id;
-
-                // Walk each clip in this chain, to see whether to allocate a surface and clip
-                // that, or whether to apply clips to each primitive.
-                while current_clip_chain_id != ClipChainId::NONE {
-                    let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
-                    let clip_node = &clip_data_store[clip_chain_node.handle];
-
-                    match clip_node.item {
-                        ClipItem::Rectangle(_, ClipMode::Clip) => {
-                            // Normal rectangle clips can be handled as per-item clips.
-                            // TODO(gw): In future, we might want to consider selecting
-                            //           a surface in some situations here (e.g. if the
-                            //           stacking context is in a different coord system
-                            //           from the clip, and there are enough primitives
-                            //           in the stacking context to justify a surface).
-                        }
-                        ClipItem::Rectangle(_, ClipMode::ClipOut) |
-                        ClipItem::RoundedRectangle(..) |
-                        ClipItem::Image { .. } |
-                        ClipItem::BoxShadow(..) => {
-                            // Any of these clip types will require a surface.
-                            apply_clip_to_picture = true;
-                            break;
-                        }
-                    }
-
-                    current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
-                }
-
-                // If we decided not to use a surfce for clipping, then skip and draw straight
-                // into the parent surface.
-                if apply_clip_to_picture {
-                    Some(PictureCompositeMode::Blit(reason))
-                } else {
-                    None
-                }
-            }
             ref mode => mode.clone(),
         };
 
         if let Some(composite_mode) = actual_composite_mode {
             // Retrieve the positioning node information for the parent surface.
             let (parent_raster_node_index, parent_allows_subpixel_aa)= {
                 let parent_surface = state.current_surface();
                 (parent_surface.raster_spatial_node_index, parent_surface.allow_subpixel_aa)
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1769,17 +1769,19 @@ impl PrimitiveStore {
                     frame_context,
                     frame_state,
                     surface_index,
                 );
 
                 frame_state.tile_cache = Some(tile_cache);
             }
 
-            (prim_list, surface_index, pic.apply_local_clip_rect, pic.requested_raster_space)
+            let raster_space = pic.get_raster_space(frame_context.clip_scroll_tree);
+
+            (prim_list, surface_index, pic.apply_local_clip_rect, raster_space)
         };
 
         let surface = &frame_context.surfaces[surface_index.0 as usize];
 
         let mut map_local_to_surface = surface
             .map_local_to_surface
             .clone();
 
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -481,16 +481,26 @@ impl Document {
                 self.dynamic_properties.add_properties(property_bindings);
             }
             FrameMsg::SetPinchZoom(factor) => {
                 if self.view.pinch_zoom_factor != factor.get() {
                     self.view.pinch_zoom_factor = factor.get();
                     self.frame_is_valid = false;
                 }
             }
+            FrameMsg::SetIsTransformPinchZooming(is_zooming, animation_id) => {
+                let node = self.clip_scroll_tree.spatial_nodes.iter_mut()
+                    .find(|node| node.is_transform_bound_to_property(animation_id));
+                if let Some(node) = node {
+                    if node.is_pinch_zooming != is_zooming {
+                        node.is_pinch_zooming = is_zooming;
+                        self.frame_is_valid = false;
+                    }
+                }
+            }
         }
 
         DocumentOps::nop()
     }
 
     fn build_frame(
         &mut self,
         resource_cache: &mut ResourceCache,
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -1,20 +1,20 @@
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ExternalScrollId, PipelineId, PropertyBinding, ReferenceFrameKind, ScrollClamping, ScrollLocation};
+use api::{ExternalScrollId, PipelineId, PropertyBinding, PropertyBindingId, ReferenceFrameKind, ScrollClamping, ScrollLocation};
 use api::{TransformStyle, ScrollSensitivity, StickyOffsetBounds};
 use api::units::*;
 use crate::clip_scroll_tree::{CoordinateSystem, CoordinateSystemId, SpatialNodeIndex, TransformUpdateState};
 use euclid::SideOffsets2D;
 use crate::scene::SceneProperties;
-use crate::util::{LayoutFastTransform, LayoutToWorldFastTransform, ScaleOffset, TransformedRectKind};
+use crate::util::{LayoutFastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, TransformedRectKind};
 
 #[derive(Clone, Debug)]
 pub enum SpatialNodeType {
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
@@ -61,16 +61,25 @@ pub struct SpatialNode {
 
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
 
     /// The transformation from the coordinate system which established our compatible coordinate
     /// system (same coordinate system id) and us. This can change via scroll offsets and via new
     /// reference frame transforms.
     pub coordinate_system_relative_scale_offset: ScaleOffset,
+
+    /// Whether this specific node is currently being pinch zoomed.
+    /// Should be set when a SetIsTransformPinchZooming FrameMsg is received.
+    pub is_pinch_zooming: bool,
+
+    /// Whether this node or any of its ancestors is being pinch zoomed.
+    /// This is calculated in update(). This will be used to decide whether
+    /// to override corresponding picture's raster space as an optimisation.
+    pub is_ancestor_or_self_zooming: bool,
 }
 
 fn compute_offset_from(
     mut current: Option<SpatialNodeIndex>,
     external_id: ExternalScrollId,
     previous_spatial_nodes: &[SpatialNode],
 ) -> LayoutVector2D {
     let mut offset = LayoutVector2D::zero();
@@ -111,16 +120,18 @@ impl SpatialNode {
             transform_kind: TransformedRectKind::AxisAligned,
             parent: parent_index,
             children: Vec::new(),
             pipeline_id,
             node_type,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_scale_offset: ScaleOffset::identity(),
+            is_pinch_zooming: false,
+            is_ancestor_or_self_zooming: false,
         }
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
@@ -247,16 +258,22 @@ impl SpatialNode {
         if !state.invertible {
             self.mark_uninvertible(state);
             return;
         }
 
         self.update_transform(state, coord_systems, scene_properties, previous_spatial_nodes);
         self.transform_kind = self.world_viewport_transform.kind();
 
+        let is_parent_zooming = match self.parent {
+            Some(parent) => previous_spatial_nodes[parent.0 as usize].is_ancestor_or_self_zooming,
+            _ => false,
+        };
+        self.is_ancestor_or_self_zooming = self.is_pinch_zooming | is_parent_zooming;
+
         // If this node is a reference frame, we check if it has a non-invertible matrix.
         // For non-reference-frames we assume that they will produce only additional
         // translations which should be invertible.
         match self.node_type {
             SpatialNodeType::ReferenceFrame(info) if !info.invertible => {
                 self.mark_uninvertible(state);
                 return;
             }
@@ -340,23 +357,31 @@ impl SpatialNode {
                         // new incompatible coordinate system with which we cannot share clips without masking.
                         self.coordinate_system_relative_scale_offset = ScaleOffset::identity();
 
                         let transform = state.coordinate_system_relative_scale_offset
                             .to_transform()
                             .pre_mul(&relative_transform);
 
                         // Push that new coordinate system and record the new id.
-                        let coord_system = CoordinateSystem {
-                            transform,
-                            should_flatten: match (info.transform_style, info.kind) {
-                                (TransformStyle::Flat, ReferenceFrameKind::Transform) => true,
-                                (_, _) => false,
-                            },
-                            parent: Some(state.current_coordinate_system_id),
+                        let coord_system = {
+                            let parent_system = &coord_systems[state.current_coordinate_system_id.0 as usize];
+                            let mut cur_transform = transform;
+                            if parent_system.should_flatten {
+                                cur_transform.flatten_z_output();
+                            }
+                            CoordinateSystem {
+                                transform,
+                                world_transform: cur_transform.post_mul(&parent_system.world_transform),
+                                should_flatten: match (info.transform_style, info.kind) {
+                                    (TransformStyle::Flat, ReferenceFrameKind::Transform) => true,
+                                    (_, _) => false,
+                                },
+                                parent: Some(state.current_coordinate_system_id),
+                            }
                         };
                         state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
                         coord_systems.push(coord_system);
                     }
                 }
 
                 // Ensure that the current coordinate system ID is propagated to child
                 // nodes, even if we encounter a node that is not invertible. This ensures
@@ -625,16 +650,30 @@ impl SpatialNode {
     }
 
     pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
         match self.node_type {
             SpatialNodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
+
+    /// Returns true for ReferenceFrames whose source_transform is
+    /// bound to the property binding id.
+    pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {
+        if let SpatialNodeType::ReferenceFrame(ref info) = self.node_type {
+            if let PropertyBinding::Binding(key, _) = info.source_transform {
+                id == key.id
+            } else {
+                false
+            }
+        } else {
+            false
+        }
+    }
 }
 
 /// Defines whether we have an implicit scroll frame for a pipeline root,
 /// or an explicitly defined scroll frame from the display list.
 #[derive(Copy, Clone, Debug)]
 pub enum ScrollFrameKind {
     PipelineRoot,
     Explicit,
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -369,20 +369,16 @@ pub struct ColorRenderTarget {
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     alpha_tasks: Vec<RenderTaskId>,
     screen_size: DeviceIntSize,
     // Track the used rect of the render target, so that
     // we can set a scissor rect and only clear to the
     // used portion of the target as an optimization.
     pub used_rect: DeviceIntRect,
-    // This is used to build batches for this render target. In future,
-    // this will be used to support splitting a single picture primitive
-    // list into multiple batch sets.
-    batch_builder: BatchBuilder,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn new(
         screen_size: DeviceIntSize,
         _: bool,
     ) -> Self {
         ColorRenderTarget {
@@ -391,17 +387,16 @@ impl RenderTarget for ColorRenderTarget 
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             scalings: Vec::new(),
             blits: Vec::new(),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             screen_size,
             used_rect: DeviceIntRect::zero(),
-            batch_builder: BatchBuilder::new(),
         }
     }
 
     fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskGraph,
@@ -431,35 +426,46 @@ impl RenderTarget for ColorRenderTarget 
                     let (target_rect, _) = task.get_target_rect();
 
                     let scissor_rect = if pic_task.can_merge {
                         None
                     } else {
                         Some(target_rect)
                     };
 
-                    let mut alpha_batch_builder = AlphaBatchBuilder::new(
+                    // TODO(gw): The type names of AlphaBatchBuilder and BatchBuilder
+                    //           are still confusing. Once more of the picture caching
+                    //           improvement code lands, the AlphaBatchBuilder and
+                    //           AlphaBatchList types will be collapsed into one, which
+                    //           should simplify coming up with better type names.
+                    let alpha_batch_builder = AlphaBatchBuilder::new(
                         self.screen_size,
                         ctx.break_advanced_blend_batches,
                         *task_id,
+                        render_tasks.get_task_address(*task_id),
                     );
 
-                    self.batch_builder.add_pic_to_batch(
+                    let mut batch_builder = BatchBuilder::new(
+                        alpha_batch_builder,
+                    );
+
+                    batch_builder.add_pic_to_batch(
                         pic,
-                        &mut alpha_batch_builder,
                         ctx,
                         gpu_cache,
                         render_tasks,
                         deferred_resolves,
                         prim_headers,
                         transforms,
                         pic_task.root_spatial_node_index,
                         z_generator,
                     );
 
+                    let alpha_batch_builder = batch_builder.finalize();
+
                     alpha_batch_builder.build(
                         &mut self.alpha_batch_containers,
                         &mut merged_batches,
                         target_rect,
                         scissor_rect,
                     );
                 }
                 _ => {
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -270,16 +270,19 @@ pub trait MatrixHelpers<Src, Dst> {
     fn is_simple_translation(&self) -> bool;
     fn is_simple_2d_translation(&self) -> bool;
     /// Return the determinant of the 2D part of the matrix.
     fn determinant_2d(&self) -> f32;
     /// This function returns a point in the `Src` space that projects into zero XY.
     /// It ignores the Z coordinate and is usable for "flattened" transformations,
     /// since they are not generally inversible.
     fn inverse_project_2d_origin(&self) -> Option<TypedPoint2D<f32, Src>>;
+    /// Turn Z transformation into identity. This is useful when crossing "flat"
+    /// transform styled stacking contexts upon traversing the coordinate systems.
+    fn flatten_z_output(&mut self);
 }
 
 impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedTransform3D<f32, Src, Dst> {
     fn preserves_2d_axis_alignment(&self) -> bool {
         if self.m14 != 0.0 || self.m24 != 0.0 {
             return false;
         }
 
@@ -387,16 +390,23 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f
         if det != 0.0 {
             let x = (self.m21 * self.m42 - self.m41 * self.m22) / det;
             let y = (self.m12 * self.m41 - self.m11 * self.m42) / det;
             Some(TypedPoint2D::new(x, y))
         } else {
             None
         }
     }
+
+    fn flatten_z_output(&mut self) {
+        self.m13 = 0.0;
+        self.m23 = 0.0;
+        self.m33 = 1.0;
+        self.m43 = 0.0;
+    }
 }
 
 pub trait RectHelpers<U>
 where
     Self: Sized,
 {
     fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
     fn is_well_formed_and_nonempty(&self) -> bool;
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -239,16 +239,20 @@ impl Transaction {
     pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
         self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom));
     }
 
     pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
         self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom));
     }
 
+    pub fn set_is_transform_pinch_zooming(&mut self, is_zooming: bool, animation_id: PropertyBindingId) {
+        self.frame_ops.push(FrameMsg::SetIsTransformPinchZooming(is_zooming, animation_id));
+    }
+
     pub fn set_pan(&mut self, pan: DeviceIntPoint) {
         self.frame_ops.push(FrameMsg::SetPan(pan));
     }
 
     /// Generate a new frame. When it's done and a RenderNotifier has been set
     /// in `webrender::Renderer`, [new_frame_ready()][notifier] gets called.
     /// Note that the notifier is called even if the frame generation was a
     /// no-op; the arguments passed to `new_frame_ready` will provide information
@@ -612,16 +616,17 @@ pub enum FrameMsg {
     HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetPan(DeviceIntPoint),
     Scroll(ScrollLocation, WorldPoint),
     ScrollNodeWithId(LayoutPoint, di::ExternalScrollId, ScrollClamping),
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     UpdateDynamicProperties(DynamicProperties),
     AppendDynamicProperties(DynamicProperties),
     SetPinchZoom(ZoomFactor),
+    SetIsTransformPinchZooming(bool, PropertyBindingId),
 }
 
 impl fmt::Debug for SceneMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
             SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
             SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
@@ -640,16 +645,17 @@ impl fmt::Debug for FrameMsg {
             FrameMsg::HitTest(..) => "FrameMsg::HitTest",
             FrameMsg::SetPan(..) => "FrameMsg::SetPan",
             FrameMsg::Scroll(..) => "FrameMsg::Scroll",
             FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
             FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
             FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
             FrameMsg::AppendDynamicProperties(..) => "FrameMsg::AppendDynamicProperties",
             FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom",
+            FrameMsg::SetIsTransformPinchZooming(..) => "FrameMsg::SetIsTransformPinchZooming",
         })
     }
 }
 
 bitflags!{
     /// Bit flags for WR stages to store in a capture.
     // Note: capturing `FRAME` without `SCENE` is not currently supported.
     #[derive(Deserialize, Serialize)]
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -409,16 +409,21 @@ JSObject& ModuleNamespaceObject::exports
 
 IndirectBindingMap& ModuleNamespaceObject::bindings() {
   Value value = GetProxyReservedSlot(this, BindingsSlot);
   auto bindings = static_cast<IndirectBindingMap*>(value.toPrivate());
   MOZ_ASSERT(bindings);
   return *bindings;
 }
 
+bool ModuleNamespaceObject::hasBindings() const {
+  // Import bindings may not be present if we hit OOM in initialization.
+  return !GetProxyReservedSlot(this, BindingsSlot).isUndefined();
+}
+
 bool ModuleNamespaceObject::addBinding(JSContext* cx, HandleAtom exportedName,
                                        HandleModuleObject targetModule,
                                        HandleAtom localName) {
   RootedModuleEnvironmentObject environment(
       cx, &targetModule->initialEnvironment());
   RootedId exportedNameId(cx, AtomToId(exportedName));
   RootedId localNameId(cx, AtomToId(localName));
   return bindings().put(cx, exportedNameId, environment, localNameId);
@@ -661,23 +666,29 @@ bool ModuleNamespaceObject::ProxyHandler
   props.infallibleAppend(SYMBOL_TO_JSID(cx->wellKnownSymbols().toStringTag));
 
   return true;
 }
 
 void ModuleNamespaceObject::ProxyHandler::trace(JSTracer* trc,
                                                 JSObject* proxy) const {
   auto& self = proxy->as<ModuleNamespaceObject>();
-  self.bindings().trace(trc);
+
+  if (self.hasBindings()) {
+    self.bindings().trace(trc);
+  }
 }
 
 void ModuleNamespaceObject::ProxyHandler::finalize(JSFreeOp* fop,
                                                    JSObject* proxy) const {
   auto& self = proxy->as<ModuleNamespaceObject>();
-  js_delete(&self.bindings());
+
+  if (self.hasBindings()) {
+    js_delete(&self.bindings());
+  }
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // FunctionDeclaration
 
 FunctionDeclaration::FunctionDeclaration(HandleAtom name, HandleFunction fun)
     : name(name), fun(fun) {}
 
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -193,16 +193,18 @@ class ModuleNamespaceObject : public Pro
              HandleValue receiver, ObjectOpResult& result) const override;
 
     void trace(JSTracer* trc, JSObject* proxy) const override;
     void finalize(JSFreeOp* fop, JSObject* proxy) const override;
 
     static const char family;
   };
 
+  bool hasBindings() const;
+
  public:
   static const ProxyHandler proxyHandler;
 };
 
 typedef Rooted<ModuleNamespaceObject*> RootedModuleNamespaceObject;
 typedef Handle<ModuleNamespaceObject*> HandleModuleNamespaceObject;
 
 struct FunctionDeclaration {
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -3460,16 +3460,17 @@ void GCRuntime::decommitAllWithoutUnlock
   for (ChunkPool::Iter chunk(availableChunks(lock)); !chunk.done();
        chunk.next()) {
     chunk->decommitAllArenasWithoutUnlocking(lock);
   }
   MOZ_ASSERT(availableChunks(lock).verify());
 }
 
 void GCRuntime::startDecommit() {
+  gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::DECOMMIT);
   MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
   MOZ_ASSERT(!decommitTask.isRunning());
 
   // If we are allocating heavily enough to trigger "high frequency" GC, then
   // skip decommit so that we do not compete with the mutator. However if we're
   // doing a shrinking GC we always decommit to release as much memory as
   // possible.
   if (schedulingState.inHighFrequencyGCMode() && !cleanUpEverything) {
--- a/js/src/gc/GenerateStatsPhases.py
+++ b/js/src/gc/GenerateStatsPhases.py
@@ -54,16 +54,17 @@
 import re
 import collections
 
 
 class PhaseKind():
     def __init__(self, name, descr, bucket, children=[]):
         self.name = name
         self.descr = descr
+        # For telemetry
         self.bucket = bucket
         self.children = children
 
 
 # The root marking phase appears in several places in the graph.
 MarkRootsPhaseKind = PhaseKind("MARK_ROOTS", "Mark Roots", 48, [
     PhaseKind("MARK_CCWS", "Mark Cross Compartment Wrappers", 50),
     PhaseKind("MARK_STACK", "Mark C and JS stacks", 51),
@@ -153,16 +154,17 @@ PhaseKindGraphRoots = [
     PhaseKind("COMPACT", "Compact", 40, [
         PhaseKind("COMPACT_MOVE", "Compact Move", 41),
         PhaseKind("COMPACT_UPDATE", "Compact Update", 42, [
             MarkRootsPhaseKind,
             PhaseKind("COMPACT_UPDATE_CELLS", "Compact Update Cells", 43),
             JoinParallelTasksPhaseKind
         ]),
     ]),
+    PhaseKind("DECOMMIT", "Decommit", 72),
     PhaseKind("GC_END", "End Callback", 44),
     PhaseKind("MINOR_GC", "All Minor GCs", 45, [
         MarkRootsPhaseKind,
     ]),
     PhaseKind("EVICT_NURSERY", "Minor GCs to Evict Nursery", 46, [
         MarkRootsPhaseKind,
     ]),
     PhaseKind("TRACE_HEAP", "Trace Heap", 47, [
@@ -249,18 +251,20 @@ for phaseKind in AllPhaseKinds:
     phases = PhasesForPhaseKind[phaseKind]
     if len(phases) == 1:
         phases[0].name = "%s" % phaseKind.name
     else:
         for index, phase in enumerate(phases):
             phase.name = "%s_%d" % (phaseKind.name, index + 1)
 
 # Find the maximum phase nesting.
+MaxPhaseNesting = max(phase.depth for phase in AllPhases) + 1
 
-MaxPhaseNesting = max(phase.depth for phase in AllPhases) + 1
+# And the maximum bucket number.
+MaxBucket = max(kind.bucket for kind in AllPhaseKinds)
 
 # Generate code.
 
 
 def writeList(out, items):
     if items:
         out.write(",\n".join("  " + item for item in items) + "\n")
 
@@ -332,8 +336,15 @@ def generateCpp(out):
                    name(firstChild),
                    name(phase.nextSibling),
                    name(phase.nextInPhaseKind),
                    phaseKind.name,
                    phase.depth,
                    phaseKind.descr,
                    phase.path))
     out.write("};\n")
+
+    #
+    # Print in a comment the next available phase kind number.
+    #
+    out.write("// The next available phase kind number is: %d\n" %
+            (MaxBucket + 1))
+
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -1117,16 +1117,28 @@ bool ICMonitoredFallbackStub::addMonitor
   ICTypeMonitor_Fallback* typeMonitorFallback =
       getFallbackMonitorStub(cx, frame->script());
   if (!typeMonitorFallback) {
     return false;
   }
   return typeMonitorFallback->addMonitorStubForValue(cx, frame, types, val);
 }
 
+static MOZ_MUST_USE bool TypeMonitorResult(JSContext* cx,
+                                           ICMonitoredFallbackStub* stub,
+                                           BaselineFrame* frame,
+                                           HandleScript script, jsbytecode* pc,
+                                           HandleValue val) {
+  AutoSweepTypeScript sweep(script);
+  StackTypeSet* types = script->types()->bytecodeTypes(sweep, script, pc);
+  TypeScript::MonitorBytecodeType(cx, script, pc, types, val);
+
+  return stub->addMonitorStubForValue(cx, frame, types, val);
+}
+
 bool ICCacheIR_Updated::initUpdatingChain(JSContext* cx, ICStubSpace* space) {
   MOZ_ASSERT(firstUpdateStub_ == nullptr);
 
   FallbackStubAllocator alloc(cx, *space);
   auto* stub =
       alloc.newStub<ICTypeUpdate_Fallback>(BaselineICFallbackKind::TypeUpdate);
   if (!stub) {
     return false;
@@ -1497,36 +1509,39 @@ bool DoTypeMonitorFallback(JSContext* cx
     // In derived class constructors (including nested arrows/eval), the
     // |this| argument or GETALIASEDVAR can return the magic TDZ value.
     MOZ_ASSERT(value.isMagic(JS_UNINITIALIZED_LEXICAL));
     MOZ_ASSERT(frame->isFunctionFrame() || frame->isEvalFrame());
     MOZ_ASSERT(stub->monitorsThis() || *GetNextPc(pc) == JSOP_CHECKTHIS ||
                *GetNextPc(pc) == JSOP_CHECKTHISREINIT ||
                *GetNextPc(pc) == JSOP_CHECKRETURN);
     if (stub->monitorsThis()) {
-      TypeScript::SetThis(cx, script, TypeSet::UnknownType());
+      TypeScript::MonitorThisType(cx, script, TypeSet::UnknownType());
     } else {
-      TypeScript::Monitor(cx, script, pc, TypeSet::UnknownType());
+      TypeScript::MonitorBytecodeType(cx, script, pc, TypeSet::UnknownType());
     }
     return true;
   }
 
+  TypeScript* typeScript = script->types();
+  AutoSweepTypeScript sweep(script);
+
   StackTypeSet* types;
   uint32_t argument;
   if (stub->monitorsArgument(&argument)) {
     MOZ_ASSERT(pc == script->code());
-    types = TypeScript::ArgTypes(script, argument);
-    TypeScript::SetArgument(cx, script, argument, value);
+    types = typeScript->argTypes(sweep, script, argument);
+    TypeScript::MonitorArgType(cx, script, argument, value);
   } else if (stub->monitorsThis()) {
     MOZ_ASSERT(pc == script->code());
-    types = TypeScript::ThisTypes(script);
-    TypeScript::SetThis(cx, script, value);
+    types = typeScript->thisTypes(sweep, script);
+    TypeScript::MonitorThisType(cx, script, value);
   } else {
-    types = TypeScript::BytecodeTypes(script, pc);
-    TypeScript::Monitor(cx, script, pc, types, value);
+    types = typeScript->bytecodeTypes(sweep, script, pc);
+    TypeScript::MonitorBytecodeType(cx, script, pc, types, value);
   }
 
   return stub->addMonitorStubForValue(cx, frame, types, value);
 }
 
 bool FallbackICCodeCompiler::emit_TypeMonitor() {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
@@ -2081,17 +2096,16 @@ static bool TryAttachGetPropStub(const c
 
 bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame,
                        ICGetElem_Fallback* stub, HandleValue lhs,
                        HandleValue rhs, MutableHandleValue res) {
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(frame->script());
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
 
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "GetElem(%s)", CodeName[op]);
 
   MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
 
   // Don't pass lhs directly, we need it when generating stubs.
   RootedValue lhsCopy(cx, lhs);
@@ -2099,33 +2113,33 @@ bool DoGetElemFallback(JSContext* cx, Ba
   bool isOptimizedArgs = false;
   if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
     // Handle optimized arguments[i] access.
     if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res,
                                    &isOptimizedArgs)) {
       return false;
     }
     if (isOptimizedArgs) {
-      TypeScript::Monitor(cx, script, pc, types, res);
+      if (!TypeMonitorResult(cx, stub, frame, script, pc, res)) {
+        return false;
+      }
     }
   }
 
   bool attached = TryAttachGetPropStub("GetElem", cx, frame, stub,
                                        CacheKind::GetElem, lhs, rhs, lhs);
 
   if (!isOptimizedArgs) {
     if (!GetElementOperation(cx, op, lhsCopy, rhs, res)) {
       return false;
     }
-    TypeScript::Monitor(cx, script, pc, types, res);
-  }
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
-    return false;
+
+    if (!TypeMonitorResult(cx, stub, frame, script, pc, res)) {
+      return false;
+    }
   }
 
   if (attached) {
     return true;
   }
 
   // GetElem operations which could access negative indexes generally can't
   // be optimized without the potential for bailouts, as we can't statically
@@ -2148,36 +2162,33 @@ bool DoGetElemFallback(JSContext* cx, Ba
 bool DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame,
                             ICGetElem_Fallback* stub, HandleValue lhs,
                             HandleValue rhs, HandleValue receiver,
                             MutableHandleValue res) {
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(frame->script());
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
 
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "GetElemSuper(%s)", CodeName[op]);
 
   MOZ_ASSERT(op == JSOP_GETELEM_SUPER);
 
   bool attached =
       TryAttachGetPropStub("GetElemSuper", cx, frame, stub,
                            CacheKind::GetElemSuper, lhs, rhs, receiver);
 
   // |lhs| is [[HomeObject]].[[Prototype]] which must be Object
   RootedObject lhsObj(cx, &lhs.toObject());
   if (!GetObjectElementOperation(cx, op, lhsObj, receiver, rhs, res)) {
     return false;
   }
-  TypeScript::Monitor(cx, script, pc, types, res);
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
+
+  if (!TypeMonitorResult(cx, stub, frame, script, pc, res)) {
     return false;
   }
 
   if (attached) {
     return true;
   }
 
   // GetElem operations which could access negative indexes generally can't
@@ -2678,25 +2689,17 @@ bool DoGetNameFallback(JSContext* cx, Ba
       return false;
     }
   } else {
     if (!GetEnvironmentName<GetNameMode::Normal>(cx, envChain, name, res)) {
       return false;
     }
   }
 
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
-  TypeScript::Monitor(cx, script, pc, types, res);
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
-    return false;
-  }
-
-  return true;
+  return TypeMonitorResult(cx, stub, frame, script, pc, res);
 }
 
 bool FallbackICCodeCompiler::emit_GetName() {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
   EmitRestoreTailCallReg(masm);
 
   masm.push(R0.scratchReg());
@@ -2771,17 +2774,17 @@ bool DoGetIntrinsicFallback(JSContext* c
   if (!GetIntrinsicOperation(cx, script, pc, res)) {
     return false;
   }
 
   // An intrinsic operation will always produce the same result, so only
   // needs to be monitored once. Attach a stub to load the resulting constant
   // directly.
 
-  TypeScript::Monitor(cx, script, pc, res);
+  TypeScript::MonitorBytecodeType(cx, script, pc, res);
 
   TryAttachStub<GetIntrinsicIRGenerator>("GetIntrinsic", cx, frame, stub,
                                          BaselineCacheIRStubKind::Regular, res);
 
   return true;
 }
 
 bool FallbackICCodeCompiler::emit_GetIntrinsic() {
@@ -2850,24 +2853,17 @@ bool DoGetPropFallback(JSContext* cx, Ba
 
   TryAttachGetPropStub("GetProp", cx, frame, stub, CacheKind::GetProp, val,
                        idVal, val);
 
   if (!ComputeGetPropResult(cx, frame, op, name, val, res)) {
     return false;
   }
 
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
-  TypeScript::Monitor(cx, script, pc, types, res);
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
-    return false;
-  }
-  return true;
+  return TypeMonitorResult(cx, stub, frame, script, pc, res);
 }
 
 bool DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame,
                             ICGetProp_Fallback* stub, HandleValue receiver,
                             MutableHandleValue val, MutableHandleValue res) {
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
@@ -2883,25 +2879,17 @@ bool DoGetPropSuperFallback(JSContext* c
                        val, idVal, receiver);
 
   // |val| is [[HomeObject]].[[Prototype]] which must be Object
   RootedObject valObj(cx, &val.toObject());
   if (!GetProperty(cx, valObj, receiver, name, res)) {
     return false;
   }
 
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
-  TypeScript::Monitor(cx, script, pc, types, res);
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
-    return false;
-  }
-
-  return true;
+  return TypeMonitorResult(cx, stub, frame, script, pc, res);
 }
 
 bool FallbackICCodeCompiler::emitGetProp(bool hasReceiver) {
   MOZ_ASSERT(R0 == JSReturnOperand);
 
   EmitRestoreTailCallReg(masm);
 
   // Super property getters use a |this| that differs from base object
@@ -3762,21 +3750,17 @@ bool DoCallFallback(JSContext* cx, Basel
 
     if (!CallFromStack(cx, callArgs)) {
       return false;
     }
 
     res.set(callArgs.rval());
   }
 
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
-  TypeScript::Monitor(cx, script, pc, types, res);
-
-  // Add a type monitor stub for the resulting value.
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
+  if (!TypeMonitorResult(cx, stub, frame, script, pc, res)) {
     return false;
   }
 
   // Try to transition again in case we called this IC recursively.
   if (stub->state().maybeTransition()) {
     stub->discardStubs(cx);
   }
   canAttachStub = stub->state().canAttachStub();
@@ -3887,23 +3871,17 @@ bool DoSpreadCallFallback(JSContext* cx,
     }
   }
 
   if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget,
                            res)) {
     return false;
   }
 
-  // Add a type monitor stub for the resulting value.
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
-  if (!stub->addMonitorStubForValue(cx, frame, types, res)) {
-    return false;
-  }
-
-  return true;
+  return TypeMonitorResult(cx, stub, frame, script, pc, res);
 }
 
 void ICStubCompilerBase::pushCallArguments(MacroAssembler& masm,
                                            AllocatableGeneralRegisterSet regs,
                                            Register argcReg, bool isJitCall,
                                            bool isConstructing) {
   MOZ_ASSERT(!regs.has(argcReg));
 
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -4549,17 +4549,17 @@ bool jit::AnalyzeNewScriptDefiniteProper
     if (status == Method_Error) {
       return false;
     }
     if (status != Method_Compiled) {
       return true;
     }
   }
 
-  TypeScript::SetThis(cx, script, TypeSet::ObjectType(group));
+  TypeScript::MonitorThisType(cx, script, TypeSet::ObjectType(group));
 
   MIRGraph graph(&temp);
   InlineScriptTree* inlineScriptTree =
       InlineScriptTree::New(&temp, nullptr, nullptr, script);
   if (!inlineScriptTree) {
     return false;
   }
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4045,18 +4045,19 @@ IonBuilder::InliningResult IonBuilder::i
   callInfo.popCallStack(current);
   current->push(callInfo.fun());
 
   JSScript* calleeScript = target->nonLazyScript();
   BaselineInspector inspector(calleeScript);
 
   // Improve type information of |this| when not set.
   if (callInfo.constructing() && !callInfo.thisArg()->resultTypeSet()) {
-    StackTypeSet* types = TypeScript::ThisTypes(calleeScript);
-    if (types && !types->unknown()) {
+    AutoSweepTypeScript sweep(calleeScript);
+    StackTypeSet* types = calleeScript->types()->thisTypes(sweep, calleeScript);
+    if (!types->unknown()) {
       TemporaryTypeSet* clonedTypes = types->clone(alloc_->lifoAlloc());
       if (!clonedTypes) {
         return abort(AbortReason::Alloc);
       }
       MTypeBarrier* barrier =
           MTypeBarrier::New(alloc(), callInfo.thisArg(), clonedTypes);
       current->add(barrier);
       if (barrier->type() == MIRType::Undefined) {
@@ -5325,18 +5326,25 @@ MDefinition* IonBuilder::createThisScrip
 
   TypeSet::ObjectKey* templateObjectKey =
       TypeSet::ObjectKey::get(templateObject->group());
   if (templateObjectKey->hasFlags(constraints(),
                                   OBJECT_FLAG_NEW_SCRIPT_CLEARED)) {
     return nullptr;
   }
 
-  StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
-  if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
+  JSScript* targetScript = target->nonLazyScript();
+  TypeScript* typeScript = targetScript->types();
+  if (!typeScript) {
+    return nullptr;
+  }
+
+  AutoSweepTypeScript sweep(targetScript);
+  StackTypeSet* thisTypes = typeScript->thisTypes(sweep, targetScript);
+  if (!thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
     return nullptr;
   }
 
   // Generate an inline path to create a new |this| object with
   // the given singleton prototype.
   MConstant* templateConst =
       MConstant::NewConstraintlessObject(alloc(), templateObject);
   MCreateThisWithTemplate* createThis = MCreateThisWithTemplate::New(
@@ -5388,18 +5396,25 @@ MDefinition* IonBuilder::createThisScrip
 
   TypeSet::ObjectKey* templateObjectKey =
       TypeSet::ObjectKey::get(templateObject->group());
   if (templateObjectKey->hasFlags(constraints(),
                                   OBJECT_FLAG_NEW_SCRIPT_CLEARED)) {
     return nullptr;
   }
 
-  StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
-  if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
+  JSScript* targetScript = target->nonLazyScript();
+  TypeScript* typeScript = targetScript->types();
+  if (!typeScript) {
+    return nullptr;
+  }
+
+  AutoSweepTypeScript sweep(targetScript);
+  StackTypeSet* thisTypes = typeScript->thisTypes(sweep, targetScript);
+  if (!thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
     return nullptr;
   }
 
   // Shape guard.
   callee = addShapeGuard(callee, target->lastProperty(), Bailout_ShapeGuard);
 
   // Guard callee.prototype == proto.
   MOZ_ASSERT(shape->numFixedSlots() == 0, "Must be a dynamic slot");
@@ -6075,31 +6090,36 @@ bool IonBuilder::testNeedsArgumentCheck(
     return false;
   }
 
   if (!target->hasScript()) {
     return true;
   }
 
   JSScript* targetScript = target->nonLazyScript();
-
+  TypeScript* typeScript = targetScript->types();
+  if (!typeScript) {
+    return true;
+  }
+
+  AutoSweepTypeScript sweep(targetScript);
   if (!ArgumentTypesMatch(callInfo.thisArg(),
-                          TypeScript::ThisTypes(targetScript))) {
+                          typeScript->thisTypes(sweep, targetScript))) {
     return true;
   }
   uint32_t expected_args = Min<uint32_t>(callInfo.argc(), target->nargs());
   for (size_t i = 0; i < expected_args; i++) {
     if (!ArgumentTypesMatch(callInfo.getArg(i),
-                            TypeScript::ArgTypes(targetScript, i))) {
+                            typeScript->argTypes(sweep, targetScript, i))) {
       return true;
     }
   }
   for (size_t i = callInfo.argc(); i < target->nargs(); i++) {
-    if (!TypeScript::ArgTypes(targetScript, i)
-             ->mightBeMIRType(MIRType::Undefined)) {
+    StackTypeSet* types = typeScript->argTypes(sweep, targetScript, i);
+    if (!types->mightBeMIRType(MIRType::Undefined)) {
       return true;
     }
   }
 
   return false;
 }
 
 AbortReasonOr<MCall*> IonBuilder::makeCallHelper(
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -223,17 +223,17 @@ bool IonGetPropertyIC::update(JSContext*
     if (!GetElementOperation(cx, JSOp(*ic->pc()), val, idVal, res)) {
       return false;
     }
   }
 
   if (!ic->idempotent()) {
     // Monitor changes to cache entry.
     if (!ic->monitoredResult()) {
-      TypeScript::Monitor(cx, ic->script(), ic->pc(), res);
+      TypeScript::MonitorBytecodeType(cx, ic->script(), ic->pc(), res);
     }
   }
 
   return true;
 }
 
 /* static */
 bool IonGetPropSuperIC::update(JSContext* cx, HandleScript outerScript,
@@ -258,17 +258,17 @@ bool IonGetPropSuperIC::update(JSContext
     return false;
   }
 
   if (!GetProperty(cx, obj, receiver, id, res)) {
     return false;
   }
 
   // Monitor changes to cache entry.
-  TypeScript::Monitor(cx, ic->script(), ic->pc(), res);
+  TypeScript::MonitorBytecodeType(cx, ic->script(), ic->pc(), res);
   return true;
 }
 
 /* static */
 bool IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript,
                               IonSetPropertyIC* ic, HandleObject obj,
                               HandleValue idVal, HandleValue rhs) {
   using DeferType = SetPropIRGenerator::DeferType;
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -457,17 +457,19 @@ bool ArrayPopDense(JSContext* cx, Handle
   if (!js::array_pop(cx, 0, argv.begin())) {
     return false;
   }
 
   // If the result is |undefined|, the array was probably empty and we
   // have to monitor the return value.
   rval.set(argv[0]);
   if (rval.isUndefined()) {
-    TypeScript::Monitor(cx, rval);
+    jsbytecode* pc;
+    JSScript* script = cx->currentScript(&pc);
+    TypeScript::MonitorBytecodeType(cx, script, pc, rval);
   }
   return true;
 }
 
 bool ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v,
                     uint32_t* length) {
   *length = arr->length();
   DenseElementResult result = arr->setOrExtendDenseElements(
@@ -521,17 +523,19 @@ bool ArrayShiftDense(JSContext* cx, Hand
   if (!js::array_shift(cx, 0, argv.begin())) {
     return false;
   }
 
   // If the result is |undefined|, the array was probably empty and we
   // have to monitor the return value.
   rval.set(argv[0]);
   if (rval.isUndefined()) {
-    TypeScript::Monitor(cx, rval);
+    jsbytecode* pc;
+    JSScript* script = cx->currentScript(&pc);
+    TypeScript::MonitorBytecodeType(cx, script, pc, rval);
   }
   return true;
 }
 
 JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep) {
   JS::AutoValueArray<3> argv(cx);
   argv[0].setUndefined();
   argv[1].setObject(*array);
@@ -680,17 +684,19 @@ bool GetIntrinsicValue(JSContext* cx, Ha
     return false;
   }
 
   // This function is called when we try to compile a cold getintrinsic
   // op. MCallGetIntrinsicValue has an AliasSet of None for optimization
   // purposes, as its side effect is not observable from JS. We are
   // guaranteed to bail out after this function, but because of its AliasSet,
   // type info will not be reflowed. Manually monitor here.
-  TypeScript::Monitor(cx, rval);
+  jsbytecode* pc;
+  JSScript* script = cx->currentScript(&pc);
+  TypeScript::MonitorBytecodeType(cx, script, pc, rval);
 
   return true;
 }
 
 bool CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget,
                 MutableHandleValue rval) {
   rval.set(MagicValue(JS_IS_CONSTRUCTING));
 
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -509,17 +509,17 @@ static bool MappedArgSetter(JSContext* c
     return false;
   }
 
   if (JSID_IS_INT(id)) {
     unsigned arg = unsigned(JSID_TO_INT(id));
     if (arg < argsobj->initialLength() && !argsobj->isElementDeleted(arg)) {
       argsobj->setElement(cx, arg, v);
       if (arg < script->functionNonDelazifying()->nargs()) {
-        TypeScript::SetArgument(cx, script, arg, v);
+        TypeScript::MonitorArgType(cx, script, arg, v);
       }
       return result.succeed();
     }
   } else {
     MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().length) ||
                JSID_IS_ATOM(id, cx->names().callee));
   }
 
@@ -725,17 +725,17 @@ bool MappedArgumentsObject::obj_definePr
       if (desc.hasValue()) {
         RootedFunction callee(cx, &argsobj->callee());
         RootedScript script(cx, JSFunction::getOrCreateScript(cx, callee));
         if (!script) {
           return false;
         }
         argsobj->setElement(cx, arg, desc.value());
         if (arg < script->functionNonDelazifying()->nargs()) {
-          TypeScript::SetArgument(cx, script, arg, desc.value());
+          TypeScript::MonitorArgType(cx, script, arg, desc.value());
         }
       }
       if (desc.hasWritable() && !desc.writable()) {
         if (!argsobj->markElementDeleted(cx, arg)) {
           return false;
         }
       }
     }
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -1569,17 +1569,17 @@ class DebugEnvironmentProxyHandler : pub
           /* The unaliased value has been lost to the debugger. */
           if (action == GET) {
             *accessResult = ACCESS_LOST;
             return true;
           }
         }
 
         if (action == SET) {
-          TypeScript::SetArgument(cx, script, i, vp);
+          TypeScript::MonitorArgType(cx, script, i, vp);
         }
       }
 
       // It is possible that an optimized out value flows to this
       // location due to Debugger.Frame.prototype.eval operating on a
       // live bailed-out Baseline frame. In that case, treat the access
       // as lost.
       if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT) {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2114,21 +2114,21 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
 
       jit_return:
 
         MOZ_ASSERT(CodeSpec[*REGS.pc].format & JOF_INVOKE);
         MOZ_ASSERT(cx->realm() == script->realm());
 
         /* Resume execution in the calling frame. */
         if (MOZ_LIKELY(interpReturnOK)) {
-          TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
-
           if (JSOp(*REGS.pc) == JSOP_RESUME) {
             ADVANCE_AND_DISPATCH(JSOP_RESUME_LENGTH);
           }
+
+          TypeScript::MonitorBytecodeType(cx, script, REGS.pc, REGS.sp[-1]);
           MOZ_ASSERT(CodeSpec[*REGS.pc].length == JSOP_CALL_LENGTH);
           ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH);
         }
 
         goto error;
       } else {
         // Stack should be empty for the outer frame, unless we executed the
         // first |await| expression in an async function.
@@ -2768,46 +2768,46 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     CASE(JSOP_GETPROP)
     CASE(JSOP_LENGTH)
     CASE(JSOP_CALLPROP) {
       MutableHandleValue lval = REGS.stackHandleAt(-1);
       if (!GetPropertyOperation(cx, REGS.fp(), script, REGS.pc, lval, lval)) {
         goto error;
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, lval);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, lval);
       cx->debugOnlyCheck(lval);
     }
     END_CASE(JSOP_GETPROP)
 
     CASE(JSOP_GETPROP_SUPER) {
       ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-2]);
       ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-1].toObject());
       MutableHandleValue rref = REGS.stackHandleAt(-2);
 
       if (!GetProperty(cx, obj, receiver, script->getName(REGS.pc), rref)) {
         goto error;
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, rref);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, rref);
       cx->debugOnlyCheck(rref);
 
       REGS.sp--;
     }
     END_CASE(JSOP_GETPROP_SUPER)
 
     CASE(JSOP_GETBOUNDNAME) {
       ReservedRooted<JSObject*> env(&rootObject0, &REGS.sp[-1].toObject());
       ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
       MutableHandleValue rval = REGS.stackHandleAt(-1);
       if (!GetNameBoundInEnvironment(cx, env, id, rval)) {
         goto error;
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, rval);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, rval);
       cx->debugOnlyCheck(rval);
     }
     END_CASE(JSOP_GETBOUNDNAME)
 
     CASE(JSOP_SETINTRINSIC) {
       HandleValue value = REGS.stackHandleAt(-1);
 
       if (!SetIntrinsicOperation(cx, script, REGS.pc, value)) {
@@ -2890,17 +2890,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       }
 
       if (!done) {
         if (!GetElementOperation(cx, JSOp(*REGS.pc), lval, rval, res)) {
           goto error;
         }
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, res);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, res);
       REGS.sp--;
     }
     END_CASE(JSOP_GETELEM)
 
     CASE(JSOP_GETELEM_SUPER) {
       ReservedRooted<Value> receiver(&rootValue1, REGS.sp[-3]);
       ReservedRooted<Value> rval(&rootValue0, REGS.sp[-2]);
       ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-1].toObject());
@@ -2910,17 +2910,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       // Since we have asserted that obj has to be an object, it cannot be
       // either optimized arguments, or indeed any primitive. This simplifies
       // our task some.
       if (!GetObjectElementOperation(cx, JSOp(*REGS.pc), obj, receiver, rval,
                                      res)) {
         goto error;
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, res);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, res);
       REGS.sp -= 2;
     }
     END_CASE(JSOP_GETELEM_SUPER)
 
     CASE(JSOP_SETELEM)
     CASE(JSOP_STRICTSETELEM) {
       static_assert(JSOP_SETELEM_LENGTH == JSOP_STRICTSETELEM_LENGTH,
                     "setelem and strictsetelem must be the same size");
@@ -2975,17 +2975,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         }
       } else {
         if (!CallFromStack(cx, args)) {
           goto error;
         }
       }
 
       REGS.sp = args.spAfterCall();
-      TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, REGS.sp[-1]);
     }
     END_CASE(JSOP_EVAL)
 
     CASE(JSOP_SPREADNEW)
     CASE(JSOP_SPREADCALL)
     CASE(JSOP_SPREADSUPERCALL) {
       if (REGS.fp()->hasPushedGeckoProfilerFrame()) {
         cx->geckoProfiler().updatePC(cx, script, REGS.pc);
@@ -3082,17 +3082,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
             ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, args.thisv(), nullptr);
             goto error;
           }
           if (!CallFromStack(cx, args)) {
             goto error;
           }
         }
         Value* newsp = args.spAfterCall();
-        TypeScript::Monitor(cx, script, REGS.pc, newsp[-1]);
+        TypeScript::MonitorBytecodeType(cx, script, REGS.pc, newsp[-1]);
         REGS.sp = newsp;
         ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH);
       }
 
       {
         MOZ_ASSERT(maybeFun);
         ReservedRooted<JSFunction*> fun(&rootFunction0, maybeFun);
         ReservedRooted<JSScript*> funScript(
@@ -3232,42 +3232,42 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     CASE(JSOP_GETGNAME)
     CASE(JSOP_GETNAME) {
       ReservedRooted<Value> rval(&rootValue0);
       if (!GetNameOperation(cx, REGS.fp(), REGS.pc, &rval)) {
         goto error;
       }
 
       PUSH_COPY(rval);
-      TypeScript::Monitor(cx, script, REGS.pc, rval);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, rval);
       static_assert(JSOP_GETNAME_LENGTH == JSOP_GETGNAME_LENGTH,
                     "We're sharing the END_CASE so the lengths better match");
     }
     END_CASE(JSOP_GETNAME)
 
     CASE(JSOP_GETIMPORT) {
       PUSH_NULL();
       MutableHandleValue rval = REGS.stackHandleAt(-1);
       HandleObject envChain = REGS.fp()->environmentChain();
       if (!GetImportOperation(cx, envChain, script, REGS.pc, rval)) {
         goto error;
       }
 
-      TypeScript::Monitor(cx, script, REGS.pc, rval);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, rval);
     }
     END_CASE(JSOP_GETIMPORT)
 
     CASE(JSOP_GETINTRINSIC) {
       ReservedRooted<Value> rval(&rootValue0);
       if (!GetIntrinsicOperation(cx, script, REGS.pc, &rval)) {
         goto error;
       }
 
       PUSH_COPY(rval);
-      TypeScript::Monitor(cx, script, REGS.pc, rval);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, rval);
     }
     END_CASE(JSOP_GETINTRINSIC)
 
     CASE(JSOP_UINT16) { PUSH_INT32((int32_t)GET_UINT16(REGS.pc)); }
     END_CASE(JSOP_UINT16)
 
     CASE(JSOP_UINT24)
     CASE(JSOP_RESUMEINDEX) { PUSH_INT32((int32_t)GET_UINT24(REGS.pc)); }
@@ -3419,17 +3419,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         PropertyName* name = EnvironmentCoordinateNameSlow(script, REGS.pc);
         MOZ_ASSERT(name == cx->names().dotThis);
         JSOp next = JSOp(*GetNextPc(REGS.pc));
         MOZ_ASSERT(next == JSOP_CHECKTHIS || next == JSOP_CHECKRETURN ||
                    next == JSOP_CHECKTHISREINIT);
       }
 #endif
       PUSH_COPY(val);
-      TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
+      TypeScript::MonitorBytecodeType(cx, script, REGS.pc, REGS.sp[-1]);
     }
     END_CASE(JSOP_GETALIASEDVAR)
 
     CASE(JSOP_SETALIASEDVAR) {
       EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc);
       EnvironmentObject& obj = REGS.fp()->aliasedEnvironment(ec);
       SetAliasedVarOperation(cx, script, REGS.pc, obj, ec, REGS.sp[-1],
                              CheckTDZ);
@@ -5100,17 +5100,17 @@ bool js::SpreadCallOperation(JSContext* 
                  "bad spread opcode");
 
       if (!Call(cx, callee, thisv, args, res)) {
         return false;
       }
     }
   }
 
-  TypeScript::Monitor(cx, script, pc, res);
+  TypeScript::MonitorBytecodeType(cx, script, pc, res);
   return true;
 }
 
 bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, bool* optimized) {
   // Optimize spread call by skipping spread operation when following
   // conditions are met:
   //   * the argument is an array
   //   * the array has no hole
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -1151,17 +1151,17 @@ JSObject* js::CreateThisForFunctionWithP
   }
 
   if (res) {
     MOZ_ASSERT(res->nonCCWRealm() == callee->realm());
     JSScript* script = JSFunction::getOrCreateScript(cx, callee);
     if (!script) {
       return nullptr;
     }
-    TypeScript::SetThis(cx, script, TypeSet::ObjectType(res));
+    TypeScript::MonitorThisType(cx, script, TypeSet::ObjectType(res));
   }
 
   return res;
 }
 
 bool js::GetPrototypeFromConstructor(JSContext* cx, HandleObject newTarget,
                                      JSProtoKey intrinsicDefaultProto,
                                      MutableHandleObject proto) {
@@ -1220,17 +1220,17 @@ JSObject* js::CreateThisForFunction(JSCo
 
   if (obj && newKind == SingletonObject) {
     RootedPlainObject nobj(cx, &obj->as<PlainObject>());
 
     /* Reshape the singleton before passing it as the 'this' value. */
     NativeObject::clear(cx, nobj);
 
     JSScript* calleeScript = callee->nonLazyScript();
-    TypeScript::SetThis(cx, calleeScript, TypeSet::ObjectType(nobj));
+    TypeScript::MonitorThisType(cx, calleeScript, TypeSet::ObjectType(nobj));
 
     return nobj;
   }
 
   return obj;
 }
 
 /* static */
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -1518,17 +1518,17 @@ bool ObjectGroup::setAllocationSiteObjec
   if (singleton) {
     MOZ_ASSERT(obj->isSingleton());
 
     /*
      * Inference does not account for types of run-once initializer
      * objects, as these may not be created until after the script
      * has been analyzed.
      */
-    TypeScript::Monitor(cx, script, pc, ObjectValue(*obj));
+    TypeScript::MonitorBytecodeType(cx, script, pc, ObjectValue(*obj));
   } else {
     ObjectGroup* group = allocationSiteGroup(cx, script, pc, key);
     if (!group) {
       return false;
     }
     obj->setGroup(group);
   }
 
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -581,50 +581,35 @@ inline void MarkObjectStateChange(JSCont
   }
 
   AutoSweepObjectGroup sweep(obj->group());
   if (!obj->group()->unknownProperties(sweep)) {
     obj->group()->markStateChange(sweep, cx);
   }
 }
 
-/* Interface helpers for JSScript*. */
-extern void TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                              TypeSet::Type type);
-extern void TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                              StackTypeSet* types, TypeSet::Type type);
-extern void TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                              const Value& rval);
-
 /////////////////////////////////////////////////////////////////////
 // Script interface functions
 /////////////////////////////////////////////////////////////////////
 
-/* static */ inline StackTypeSet* TypeScript::ThisTypes(JSScript* script) {
-  if (TypeScript* types = script->types()) {
-    AutoSweepTypeScript sweep(script);
-    return types->typeArray(sweep) + script->numBytecodeTypeSets();
-  }
-  return nullptr;
+inline StackTypeSet* TypeScript::thisTypes(const AutoSweepTypeScript& sweep,
+                                           JSScript* script) {
+  return typeArray(sweep) + script->numBytecodeTypeSets();
 }
 
 /*
  * Note: for non-escaping arguments, argTypes reflect only the initial type of
  * the variable (e.g. passed values for argTypes, or undefined for localTypes)
  * and not types from subsequent assignments.
  */
 
-/* static */ inline StackTypeSet* TypeScript::ArgTypes(JSScript* script,
-                                                       unsigned i) {
+inline StackTypeSet* TypeScript::argTypes(const AutoSweepTypeScript& sweep,
+                                          JSScript* script, unsigned i) {
   MOZ_ASSERT(i < script->functionNonDelazifying()->nargs());
-  if (TypeScript* types = script->types()) {
-    AutoSweepTypeScript sweep(script);
-    return types->typeArray(sweep) + script->numBytecodeTypeSets() + 1 + i;
-  }
-  return nullptr;
+  return typeArray(sweep) + script->numBytecodeTypeSets() + 1 /* this */ + i;
 }
 
 template <typename TYPESET>
 /* static */ inline TYPESET* TypeScript::BytecodeTypes(JSScript* script,
                                                        jsbytecode* pc,
                                                        uint32_t* bytecodeMap,
                                                        uint32_t* hint,
                                                        TYPESET* typeArray) {
@@ -655,55 +640,30 @@ template <typename TYPESET>
     MOZ_ASSERT(numBytecodeTypeSets == JSScript::MaxBytecodeTypeSets);
     loc = numBytecodeTypeSets - 1;
   }
 
   *hint = mozilla::AssertedCast<uint32_t>(loc);
   return typeArray + *hint;
 }
 
-/* static */ inline StackTypeSet* TypeScript::BytecodeTypes(JSScript* script,
-                                                            jsbytecode* pc) {
+inline StackTypeSet* TypeScript::bytecodeTypes(const AutoSweepTypeScript& sweep,
+                                               JSScript* script,
+                                               jsbytecode* pc) {
   MOZ_ASSERT(CurrentThreadCanAccessZone(script->zone()));
-  TypeScript* types = script->types();
-  if (!types) {
-    return nullptr;
-  }
-  AutoSweepTypeScript sweep(script);
-  uint32_t* hint = types->bytecodeTypeMapHint();
-  return BytecodeTypes(script, pc, types->bytecodeTypeMap(), hint,
-                       types->typeArray(sweep));
-}
-
-/* static */ inline void TypeScript::Monitor(JSContext* cx, JSScript* script,
-                                             jsbytecode* pc,
-                                             const js::Value& rval) {
-  TypeMonitorResult(cx, script, pc, rval);
+  return BytecodeTypes(script, pc, bytecodeTypeMap(), bytecodeTypeMapHint(),
+                       typeArray(sweep));
 }
 
-/* static */ inline void TypeScript::Monitor(JSContext* cx, JSScript* script,
-                                             jsbytecode* pc,
-                                             TypeSet::Type type) {
-  TypeMonitorResult(cx, script, pc, type);
-}
-
-/* static */ inline void TypeScript::Monitor(JSContext* cx,
-                                             const js::Value& rval) {
-  jsbytecode* pc;
-  RootedScript script(cx, cx->currentScript(&pc));
-  Monitor(cx, script, pc, rval);
-}
-
-/* static */ inline void TypeScript::Monitor(JSContext* cx, JSScript* script,
-                                             jsbytecode* pc,
-                                             StackTypeSet* types,
-                                             const js::Value& rval) {
+/* static */ inline void TypeScript::MonitorBytecodeType(
+    JSContext* cx, JSScript* script, jsbytecode* pc, StackTypeSet* types,
+    const js::Value& rval) {
   TypeSet::Type type = TypeSet::GetValueType(rval);
   if (!types->hasType(type)) {
-    TypeMonitorResult(cx, script, pc, types, type);
+    MonitorBytecodeTypeSlow(cx, script, pc, types, type);
   }
 }
 
 /* static */ inline void TypeScript::MonitorAssign(JSContext* cx,
                                                    HandleObject obj, jsid id) {
   if (!obj->isSingleton()) {
     /*
      * Mark as unknown any object which has had dynamic assignments to
@@ -724,64 +684,72 @@ template <typename TYPESET>
     ObjectGroup* group = obj->group();
     if (group->basePropertyCountDontCheckGeneration() < 128) {
       return;
     }
     MarkObjectGroupUnknownProperties(cx, group);
   }
 }
 
-/* static */ inline void TypeScript::SetThis(JSContext* cx, JSScript* script,
-                                             TypeSet::Type type) {
+/* static */ inline void TypeScript::MonitorThisType(JSContext* cx,
+                                                     JSScript* script,
+                                                     TypeSet::Type type) {
   cx->check(script, type);
 
+  TypeScript* typeScript = script->types();
+  if (!typeScript) {
+    return;
+  }
+
   AutoSweepTypeScript sweep(script);
-  StackTypeSet* types = ThisTypes(script);
-  if (!types) {
-    return;
-  }
+  StackTypeSet* types = typeScript->thisTypes(sweep, script);
 
   if (!types->hasType(type)) {
     AutoEnterAnalysis enter(cx);
 
     InferSpew(ISpewOps, "externalType: setThis %p: %s", script,
               TypeSet::TypeString(type).get());
     types->addType(sweep, cx, type);
   }
 }
 
-/* static */ inline void TypeScript::SetThis(JSContext* cx, JSScript* script,
-                                             const js::Value& value) {
-  SetThis(cx, script, TypeSet::GetValueType(value));
+/* static */ inline void TypeScript::MonitorThisType(JSContext* cx,
+                                                     JSScript* script,
+                                                     const js::Value& value) {
+  MonitorThisType(cx, script, TypeSet::GetValueType(value));
 }
 
-/* static */ inline void TypeScript::SetArgument(JSContext* cx,
-                                                 JSScript* script, unsigned arg,
-                                                 TypeSet::Type type) {
+/* static */ inline void TypeScript::MonitorArgType(JSContext* cx,
+                                                    JSScript* script,
+                                                    unsigned arg,
+                                                    TypeSet::Type type) {
   cx->check(script->compartment(), type);
 
-  AutoSweepTypeScript sweep(script);
-  StackTypeSet* types = ArgTypes(script, arg);
-  if (!types) {
+  TypeScript* typeScript = script->types();
+  if (!typeScript) {
     return;
   }
 
+  AutoSweepTypeScript sweep(script);
+  StackTypeSet* types = typeScript->argTypes(sweep, script, arg);
+
   if (!types->hasType(type)) {
     AutoEnterAnalysis enter(cx);
 
     InferSpew(ISpewOps, "externalType: setArg %p %u: %s", script, arg,
               TypeSet::TypeString(type).get());
     types->addType(sweep, cx, type);
   }
 }
 
-/* static */ inline void TypeScript::SetArgument(JSContext* cx,
-                                                 JSScript* script, unsigned arg,
-                                                 const js::Value& value) {
-  SetArgument(cx, script, arg, TypeSet::GetValueType(value));
+/* static */ inline void TypeScript::MonitorArgType(JSContext* cx,
+                                                    JSScript* script,
+                                                    unsigned arg,
+                                                    const js::Value& value) {
+  MonitorArgType(cx, script, arg, TypeSet::GetValueType(value));
 }
 
 inline AutoKeepTypeScripts::AutoKeepTypeScripts(JSContext* cx)
     : zone_(cx->zone()->types), prev_(zone_.keepTypeScripts) {
   zone_.keepTypeScripts = true;
 }
 
 inline AutoKeepTypeScripts::~AutoKeepTypeScripts() {
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1203,21 +1203,27 @@ bool TypeScript::FreezeTypeSets(Compiler
   }
 
   for (size_t i = 0; i < count; i++) {
     if (!existing[i].cloneIntoUninitialized(alloc, &types[i])) {
       return false;
     }
   }
 
-  *pThisTypes = types + (ThisTypes(script) - existing);
-  *pArgTypes = (script->functionNonDelazifying() &&
-                script->functionNonDelazifying()->nargs())
-                   ? (types + (ArgTypes(script, 0) - existing))
-                   : nullptr;
+  size_t thisTypesIndex = typeScript->thisTypes(sweep, script) - existing;
+  *pThisTypes = types + thisTypesIndex;
+
+  if (script->functionNonDelazifying() &&
+      script->functionNonDelazifying()->nargs() > 0) {
+    size_t firstArgIndex = typeScript->argTypes(sweep, script, 0) - existing;
+    *pArgTypes = types + firstArgIndex;
+  } else {
+    *pArgTypes = nullptr;
+  }
+
   *pBytecodeTypes = types;
 
   constraints->freezeScript(script, *pThisTypes, *pArgTypes, *pBytecodeTypes);
   return true;
 }
 
 namespace {
 
@@ -1513,26 +1519,27 @@ bool js::FinishCompilation(JSContext* cx
     // debuggee mid-compilation (e.g., via setting a breakpoint). If so,
     // throw away the compilation.
     if (entry.script->isDebuggee()) {
       succeeded = false;
       break;
     }
 
     AutoSweepTypeScript sweep(entry.script);
+    TypeScript* typeScript = entry.script->types();
     if (!CheckFrozenTypeSet(sweep, cx, entry.thisTypes,
-                            TypeScript::ThisTypes(entry.script))) {
+                            typeScript->thisTypes(sweep, entry.script))) {
       succeeded = false;
     }
     unsigned nargs = entry.script->functionNonDelazifying()
                          ? entry.script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t i = 0; i < nargs; i++) {
       if (!CheckFrozenTypeSet(sweep, cx, &entry.argTypes[i],
-                              TypeScript::ArgTypes(entry.script, i))) {
+                              typeScript->argTypes(sweep, entry.script, i))) {
         succeeded = false;
       }
     }
     for (size_t i = 0; i < entry.script->numBytecodeTypeSets(); i++) {
       if (!CheckFrozenTypeSet(sweep, cx, &entry.bytecodeTypes[i],
                               &types->typeArray(sweep)[i])) {
         succeeded = false;
       }
@@ -1603,28 +1610,30 @@ void js::FinishDefinitePropertiesAnalysi
   // Assert no new types have been added to the StackTypeSets. Do this before
   // calling CheckDefinitePropertiesTypeSet, as it may add new types to the
   // StackTypeSets and break these invariants if a script is inlined more
   // than once. See also CheckDefinitePropertiesTypeSet.
   for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
     const CompilerConstraintList::FrozenScript& entry =
         constraints->frozenScript(i);
     JSScript* script = entry.script;
-    MOZ_ASSERT(script->types());
-
-    MOZ_ASSERT(TypeScript::ThisTypes(script)->isSubset(entry.thisTypes));
+    TypeScript* typeScript = script->types();
+    MOZ_ASSERT(typeScript);
+
+    AutoSweepTypeScript sweep(script);
+    MOZ_ASSERT(typeScript->thisTypes(sweep, script)->isSubset(entry.thisTypes));
 
     unsigned nargs = entry.script->functionNonDelazifying()
                          ? entry.script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t j = 0; j < nargs; j++) {
-      MOZ_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j]));
+      StackTypeSet* argTypes = typeScript->argTypes(sweep, script, j);
+      MOZ_ASSERT(argTypes->isSubset(&entry.argTypes[j]));
     }
 
-    AutoSweepTypeScript sweep(script);
     for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
       MOZ_ASSERT(script->types()->typeArray(sweep)[j].isSubset(
           &entry.bytecodeTypes[j]));
     }
   }
 #endif
 
   for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
@@ -1633,24 +1642,24 @@ void js::FinishDefinitePropertiesAnalysi
     JSScript* script = entry.script;
     TypeScript* types = script->types();
     if (!types) {
       MOZ_CRASH();
     }
 
     AutoSweepTypeScript sweep(script);
     CheckDefinitePropertiesTypeSet(sweep, cx, entry.thisTypes,
-                                   TypeScript::ThisTypes(script));
+                                   types->thisTypes(sweep, script));
 
     unsigned nargs = script->functionNonDelazifying()
                          ? script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t j = 0; j < nargs; j++) {
-      CheckDefinitePropertiesTypeSet(sweep, cx, &entry.argTypes[j],
-                                     TypeScript::ArgTypes(script, j));
+      StackTypeSet* argTypes = types->argTypes(sweep, script, j);
+      CheckDefinitePropertiesTypeSet(sweep, cx, &entry.argTypes[j], argTypes);
     }
 
     for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
       CheckDefinitePropertiesTypeSet(sweep, cx, &entry.bytecodeTypes[j],
                                      &types->typeArray(sweep)[j]);
     }
   }
 }
@@ -3367,32 +3376,32 @@ bool js::AddClearDefiniteFunctionUsesInS
 /////////////////////////////////////////////////////////////////////
 
 void js::TypeMonitorCallSlow(JSContext* cx, JSObject* callee,
                              const CallArgs& args, bool constructing) {
   unsigned nargs = callee->as<JSFunction>().nargs();
   JSScript* script = callee->as<JSFunction>().nonLazyScript();
 
   if (!constructing) {
-    TypeScript::SetThis(cx, script, args.thisv());
+    TypeScript::MonitorThisType(cx, script, args.thisv());
   }
 
   /*
    * Add constraints going up to the minimum of the actual and formal count.
    * If there are more actuals than formals the later values can only be
    * accessed through the arguments object, which is monitored.
    */
   unsigned arg = 0;
   for (; arg < args.length() && arg < nargs; arg++) {
-    TypeScript::SetArgument(cx, script, arg, args[arg]);
+    TypeScript::MonitorArgType(cx, script, arg, args[arg]);
   }
 
   /* Watch for fewer actuals than formals to the call. */
   for (; arg < nargs; arg++) {
-    TypeScript::SetArgument(cx, script, arg, UndefinedValue());
+    TypeScript::MonitorArgType(cx, script, arg, UndefinedValue());
   }
 }
 
 static void FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap) {
   uint32_t added = 0;
   for (jsbytecode* pc = script->code(); pc < script->codeEnd();
        pc += GetBytecodeLength(pc)) {
     JSOp op = JSOp(*pc);
@@ -3401,62 +3410,62 @@ static void FillBytecodeTypeMap(JSScript
       if (added == script->numBytecodeTypeSets()) {
         break;
       }
     }
   }
   MOZ_ASSERT(added == script->numBytecodeTypeSets());
 }
 
-void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                           TypeSet::Type type) {
+/* static */
+void TypeScript::MonitorBytecodeType(JSContext* cx, JSScript* script,
+                                     jsbytecode* pc, TypeSet::Type type) {
   cx->check(script, type);
 
   AutoEnterAnalysis enter(cx);
 
   AutoSweepTypeScript sweep(script);
-  StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
+  StackTypeSet* types = script->types()->bytecodeTypes(sweep, script, pc);
   if (types->hasType(type)) {
     return;
   }
 
   InferSpew(ISpewOps, "bytecodeType: %p %05zu: %s", script,
             script->pcToOffset(pc), TypeSet::TypeString(type).get());
   types->addType(sweep, cx, type);
 }
 
-void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                           StackTypeSet* types, TypeSet::Type type) {
+/* static */
+void TypeScript::MonitorBytecodeTypeSlow(JSContext* cx, JSScript* script,
+                                         jsbytecode* pc, StackTypeSet* types,
+                                         TypeSet::Type type) {
   cx->check(script, type);
 
   AutoEnterAnalysis enter(cx);
 
   AutoSweepTypeScript sweep(script);
 
-  MOZ_ASSERT(types == TypeScript::BytecodeTypes(script, pc));
+  MOZ_ASSERT(types == script->types()->bytecodeTypes(sweep, script, pc));
   MOZ_ASSERT(!types->hasType(type));
 
   InferSpew(ISpewOps, "bytecodeType: %p %05zu: %s", script,
             script->pcToOffset(pc), TypeSet::TypeString(type).get());
   types->addType(sweep, cx, type);
 }
 
-void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
-                           const js::Value& rval) {
-  // Allow the non-TYPESET scenario to simplify stubs used in compound
-  // opcodes.
-  if (!(CodeSpec[*pc].format & JOF_TYPESET)) {
-    return;
-  }
+/* static */
+void TypeScript::MonitorBytecodeType(JSContext* cx, JSScript* script,
+                                     jsbytecode* pc, const js::Value& rval) {
+  MOZ_ASSERT(CodeSpec[*pc].format & JOF_TYPESET);
 
   if (!script->types()) {
     return;
   }
 
-  TypeMonitorResult(cx, script, pc, TypeSet::GetValueType(rval));
+  MonitorBytecodeType(cx, script, pc, TypeSet::GetValueType(rval));
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeScript
 /////////////////////////////////////////////////////////////////////
 
 static size_t NumTypeSets(JSScript* script) {
   size_t num = script->numBytecodeTypeSets() + 1 /* this */;
@@ -3540,29 +3549,30 @@ bool JSScript::makeTypes(JSContext* cx) 
   MOZ_ASSERT(!types_);
   types_ = new (typeScript) TypeScript(this, std::move(icScript), numTypeSets);
 
   // We have a TypeScript so we can set the script's jitCodeRaw_ pointer to the
   // Baseline Interpreter code.
   updateJitCodeRaw(cx->runtime());
 
 #ifdef DEBUG
+  AutoSweepTypeScript sweep(this);
   StackTypeSet* typeArray = typeScript->typeArrayDontCheckGeneration();
   for (unsigned i = 0; i < numBytecodeTypeSets(); i++) {
     InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u %p",
               InferSpewColor(&typeArray[i]), &typeArray[i],
               InferSpewColorReset(), i, this);
   }
-  TypeSet* thisTypes = TypeScript::ThisTypes(this);
+  StackTypeSet* thisTypes = typeScript->thisTypes(sweep, this);
   InferSpew(ISpewOps, "typeSet: %sT%p%s this %p", InferSpewColor(thisTypes),
             thisTypes, InferSpewColorReset(), this);
   unsigned nargs =
       functionNonDelazifying() ? functionNonDelazifying()->nargs() : 0;
   for (unsigned i = 0; i < nargs; i++) {
-    TypeSet* types = TypeScript::ArgTypes(this, i);
+    StackTypeSet* types = typeScript->argTypes(sweep, this, i);
     InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u %p", InferSpewColor(types),
               types, InferSpewColorReset(), i, this);
   }
 #endif
 
   return true;
 }
 
@@ -4648,40 +4658,40 @@ void TypeScript::printTypes(JSContext* c
 
   if (script->functionNonDelazifying()) {
     if (JSAtom* name = script->functionNonDelazifying()->explicitName()) {
       name->dumpCharsNoNewline(out);
     }
   }
 
   fprintf(stderr, "\n    this:");
-  TypeScript::ThisTypes(script)->print();
+  thisTypes(sweep, script)->print();
 
   for (unsigned i = 0; script->functionNonDelazifying() &&
                        i < script->functionNonDelazifying()->nargs();
        i++) {
     fprintf(stderr, "\n    arg%u:", i);
-    TypeScript::ArgTypes(script, i)->print();
+    argTypes(sweep, script, i)->print();
   }
   fprintf(stderr, "\n");
 
   for (jsbytecode* pc = script->code(); pc < script->codeEnd();
        pc += GetBytecodeLength(pc)) {
     {
       fprintf(stderr, "%p:", script.get());
       Sprinter sprinter(cx);
       if (!sprinter.init()) {
         return;
       }
       Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
       fprintf(stderr, "%s", sprinter.string());
     }
 
     if (CodeSpec[*pc].format & JOF_TYPESET) {
-      StackTypeSet* types = TypeScript::BytecodeTypes(script, pc);
+      StackTypeSet* types = bytecodeTypes(sweep, script, pc);
       fprintf(stderr, "  typeset %u:", unsigned(types - typeArray(sweep)));
       types->print();
       fprintf(stderr, "\n");
     }
   }
 
   fprintf(stderr, "\n");
 }
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -302,68 +302,75 @@ class TypeScript {
     return typeArrayDontCheckGeneration();
   }
 
   uint32_t* bytecodeTypeMap() {
     MOZ_ASSERT(numTypeSets_ > 0);
     return reinterpret_cast<uint32_t*>(typeArray_ + numTypeSets_);
   }
 
-  static inline StackTypeSet* ThisTypes(JSScript* script);
-  static inline StackTypeSet* ArgTypes(JSScript* script, unsigned i);
+  inline StackTypeSet* thisTypes(const AutoSweepTypeScript& sweep,
+                                 JSScript* script);
+  inline StackTypeSet* argTypes(const AutoSweepTypeScript& sweep,
+                                JSScript* script, unsigned i);
 
   /* Get the type set for values observed at an opcode. */
-  static inline StackTypeSet* BytecodeTypes(JSScript* script, jsbytecode* pc);
+  inline StackTypeSet* bytecodeTypes(const AutoSweepTypeScript& sweep,
+                                     JSScript* script, jsbytecode* pc);
 
   template <typename TYPESET>
   static inline TYPESET* BytecodeTypes(JSScript* script, jsbytecode* pc,
                                        uint32_t* bytecodeMap, uint32_t* hint,
                                        TYPESET* typeArray);
 
   /*
    * Monitor a bytecode pushing any value. This must be called for any opcode
    * which is JOF_TYPESET, and where either the script has not been analyzed
    * by type inference or where the pc has type barriers. For simplicity, we
    * always monitor JOF_TYPESET opcodes in the interpreter and stub calls,
    * and only look at barriers when generating JIT code for the script.
    */
-  static inline void Monitor(JSContext* cx, JSScript* script, jsbytecode* pc,
-                             const js::Value& val);
-  static inline void Monitor(JSContext* cx, JSScript* script, jsbytecode* pc,
-                             TypeSet::Type type);
-  static inline void Monitor(JSContext* cx, const js::Value& rval);
+  static void MonitorBytecodeType(JSContext* cx, JSScript* script,
+                                  jsbytecode* pc, const js::Value& val);
+  static void MonitorBytecodeType(JSContext* cx, JSScript* script,
+                                  jsbytecode* pc, TypeSet::Type type);
 
-  static inline void Monitor(JSContext* cx, JSScript* script, jsbytecode* pc,
-                             StackTypeSet* types, const js::Value& val);
+  static inline void MonitorBytecodeType(JSContext* cx, JSScript* script,
+                                         jsbytecode* pc, StackTypeSet* types,
+                                         const js::Value& val);
 
+ private:
+  static void MonitorBytecodeTypeSlow(JSContext* cx, JSScript* script,
+                                      jsbytecode* pc, StackTypeSet* types,
+                                      TypeSet::Type type);
+
+ public:
   /* Monitor an assignment at a SETELEM on a non-integer identifier. */
   static inline void MonitorAssign(JSContext* cx, HandleObject obj, jsid id);
 
   /* Add a type for a variable in a script. */
-  static inline void SetThis(JSContext* cx, JSScript* script,
-                             TypeSet::Type type);
-  static inline void SetThis(JSContext* cx, JSScript* script,
-                             const js::Value& value);
-  static inline void SetArgument(JSContext* cx, JSScript* script, unsigned arg,
-                                 TypeSet::Type type);
-  static inline void SetArgument(JSContext* cx, JSScript* script, unsigned arg,
-                                 const js::Value& value);
+  static inline void MonitorThisType(JSContext* cx, JSScript* script,
+                                     TypeSet::Type type);
+  static inline void MonitorThisType(JSContext* cx, JSScript* script,
+                                     const js::Value& value);
+  static inline void MonitorArgType(JSContext* cx, JSScript* script,
+                                    unsigned arg, TypeSet::Type type);
+  static inline void MonitorArgType(JSContext* cx, JSScript* script,
+                                    unsigned arg, const js::Value& value);
 
   /*
    * Freeze all the stack type sets in a script, for a compilation. Returns
    * copies of the type sets which will be checked against the actual ones
    * under FinishCompilation, to detect any type changes.
    */
   static bool FreezeTypeSets(CompilerConstraintList* constraints,
                              JSScript* script, TemporaryTypeSet** pThisTypes,
                              TemporaryTypeSet** pArgTypes,
                              TemporaryTypeSet** pBytecodeTypes);
 
-  static void Purge(JSContext* cx, HandleScript script);
-
   void destroy(Zone* zone);
 
   size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
     // Note: icScript_ size is reported in jit::AddSizeOfBaselineData.
     return mallocSizeOf(this);
   }
 
   static constexpr size_t offsetOfICScript() {
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -190,17 +190,21 @@ bool Instance::callImport(JSContext* cx,
   // Ensure the argument types are included in the argument TypeSets stored in
   // the TypeScript. This is necessary for Ion, because the import will use
   // the skip-arg-checks entry point.
   //
   // Note that the TypeScript is never discarded while the script has a
   // BaselineScript, so if those checks hold now they must hold at least until
   // the BaselineScript is discarded and when that happens the import is
   // patched back.
-  if (!TypeScript::ThisTypes(script)->hasType(TypeSet::UndefinedType())) {
+  AutoSweepTypeScript sweep(script);
+  TypeScript* typeScript = script->types();
+
+  StackTypeSet* thisTypes = typeScript->thisTypes(sweep, script);
+  if (!thisTypes->hasType(TypeSet::UndefinedType())) {
     return true;
   }
 
   // Functions with anyref in signature don't have a jit exit at the moment.
   if (fi.funcType().temporarilyUnsupportedAnyRef()) {
     return true;
   }
 
@@ -223,26 +227,29 @@ bool Instance::callImport(JSContext* cx,
       case ValType::FuncRef:
       case ValType::AnyRef:
         MOZ_CRASH("case guarded above");
       case ValType::I64:
         MOZ_CRASH("NYI");
       case ValType::NullRef:
         MOZ_CRASH("NullRef not expressible");
     }
-    if (!TypeScript::ArgTypes(script, i)->hasType(type)) {
+
+    StackTypeSet* argTypes = typeScript->argTypes(sweep, script, i);
+    if (!argTypes->hasType(type)) {
       return true;
     }
   }
 
   // These arguments will be filled with undefined at runtime by the
   // arguments rectifier: check that the imported function can handle
   // undefined there.
   for (uint32_t i = importArgs.length(); i < importFun->nargs(); i++) {
-    if (!TypeScript::ArgTypes(script, i)->hasType(TypeSet::UndefinedType())) {
+    StackTypeSet* argTypes = typeScript->argTypes(sweep, script, i);
+    if (!argTypes->hasType(TypeSet::UndefinedType())) {
       return true;
     }
   }
 
   // Let's optimize it!
   if (!script->baselineScript()->addDependentWasmImport(cx, *this,
                                                         funcImportIndex)) {
     return false;
--- a/layout/base/ScrollStyles.cpp
+++ b/layout/base/ScrollStyles.cpp
@@ -5,66 +5,66 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ScrollStyles.h"
 #include "mozilla/WritingModes.h"
 #include "nsStyleStruct.h"  // for nsStyleDisplay & nsStyleBackground::Position
 
 namespace mozilla {
 
-void ScrollStyles::InitializeScrollSnapType(WritingMode aWritingMode,
-                                            const nsStyleDisplay* aDisplay) {
-  mScrollSnapTypeX = StyleScrollSnapStrictness::None;
-  mScrollSnapTypeY = StyleScrollSnapStrictness::None;
+void ScrollStyles::InitializeScrollSnapStrictness(
+    WritingMode aWritingMode, const nsStyleDisplay* aDisplay) {
+  mScrollSnapStrictnessX = StyleScrollSnapStrictness::None;
+  mScrollSnapStrictnessY = StyleScrollSnapStrictness::None;
 
   if (aDisplay->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
     return;
   }
 
   switch (aDisplay->mScrollSnapType.axis) {
     case StyleScrollSnapAxis::X:
-      mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       break;
     case StyleScrollSnapAxis::Y:
-      mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       break;
     case StyleScrollSnapAxis::Block:
       if (aWritingMode.IsVertical()) {
-        mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       } else {
-        mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       }
       break;
     case StyleScrollSnapAxis::Inline:
       if (aWritingMode.IsVertical()) {
-        mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       } else {
-        mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
+        mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
       }
       break;
     case StyleScrollSnapAxis::Both:
-      mScrollSnapTypeX = aDisplay->mScrollSnapType.strictness;
-      mScrollSnapTypeY = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness;
+      mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness;
       break;
   }
 }
 
 ScrollStyles::ScrollStyles(WritingMode aWritingMode, StyleOverflow aH,
                            StyleOverflow aV, const nsStyleDisplay* aDisplay)
     : mHorizontal(aH),
       mVertical(aV),
       mScrollBehavior(aDisplay->mScrollBehavior),
       mOverscrollBehaviorX(aDisplay->mOverscrollBehaviorX),
       mOverscrollBehaviorY(aDisplay->mOverscrollBehaviorY) {
-  InitializeScrollSnapType(aWritingMode, aDisplay);
+  InitializeScrollSnapStrictness(aWritingMode, aDisplay);
 }
 
 ScrollStyles::ScrollStyles(WritingMode aWritingMode,
                            const nsStyleDisplay* aDisplay)
     : mHorizontal(aDisplay->mOverflowX),
       mVertical(aDisplay->mOverflowY),
       mScrollBehavior(aDisplay->mScrollBehavior),
       mOverscrollBehaviorX(aDisplay->mOverscrollBehaviorX),
       mOverscrollBehaviorY(aDisplay->mOverscrollBehaviorY) {
-  InitializeScrollSnapType(aWritingMode, aDisplay);
+  InitializeScrollSnapStrictness(aWritingMode, aDisplay);
 }
 
 }  // namespace mozilla
--- a/layout/base/ScrollStyles.h
+++ b/layout/base/ScrollStyles.h
@@ -21,41 +21,41 @@ struct ScrollStyles {
   // Always one of Scroll, Hidden, or Auto
   StyleOverflow mHorizontal;
   StyleOverflow mVertical;
   // Always one of NS_STYLE_SCROLL_BEHAVIOR_AUTO or
   // NS_STYLE_SCROLL_BEHAVIOR_SMOOTH
   uint8_t mScrollBehavior;
   StyleOverscrollBehavior mOverscrollBehaviorX;
   StyleOverscrollBehavior mOverscrollBehaviorY;
-  StyleScrollSnapStrictness mScrollSnapTypeX;
-  StyleScrollSnapStrictness mScrollSnapTypeY;
+  StyleScrollSnapStrictness mScrollSnapStrictnessX;
+  StyleScrollSnapStrictness mScrollSnapStrictnessY;
 
   ScrollStyles(StyleOverflow aH, StyleOverflow aV)
       : mHorizontal(aH),
         mVertical(aV),
         mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO),
         mOverscrollBehaviorX(StyleOverscrollBehavior::Auto),
         mOverscrollBehaviorY(StyleOverscrollBehavior::Auto),
-        mScrollSnapTypeX(StyleScrollSnapStrictness::None),
-        mScrollSnapTypeY(StyleScrollSnapStrictness::None) {}
+        mScrollSnapStrictnessX(StyleScrollSnapStrictness::None),
+        mScrollSnapStrictnessY(StyleScrollSnapStrictness::None) {}
 
   ScrollStyles(WritingMode aWritingMode, const nsStyleDisplay* aDisplay);
   ScrollStyles(WritingMode aWritingMode, StyleOverflow aH, StyleOverflow aV,
                const nsStyleDisplay* aDisplay);
-  void InitializeScrollSnapType(WritingMode aWritingMode,
-                                const nsStyleDisplay* aDisplay);
+  void InitializeScrollSnapStrictness(WritingMode aWritingMode,
+                                      const nsStyleDisplay* aDisplay);
   bool operator==(const ScrollStyles& aStyles) const {
     return aStyles.mHorizontal == mHorizontal &&
            aStyles.mVertical == mVertical &&
            aStyles.mScrollBehavior == mScrollBehavior &&
            aStyles.mOverscrollBehaviorX == mOverscrollBehaviorX &&
            aStyles.mOverscrollBehaviorY == mOverscrollBehaviorY &&
-           aStyles.mScrollSnapTypeX == mScrollSnapTypeX &&
-           aStyles.mScrollSnapTypeY == mScrollSnapTypeY;
+           aStyles.mScrollSnapStrictnessX == mScrollSnapStrictnessX &&
+           aStyles.mScrollSnapStrictnessY == mScrollSnapStrictnessY;
   }
   bool operator!=(const ScrollStyles& aStyles) const {
     return !(*this == aStyles);
   }
   bool IsHiddenInBothDirections() const {
     return mHorizontal == StyleOverflow::Hidden &&
            mVertical == StyleOverflow::Hidden;
   }
--- a/layout/generic/ScrollSnap.cpp
+++ b/layout/generic/ScrollSnap.cpp
@@ -270,18 +270,18 @@ static void ProcessScrollSnapCoordinates
     aCalcSnapPoint.AddHorizontalEdge(snapCoords.y);
   }
 }
 
 Maybe<nsPoint> ScrollSnapUtils::GetSnapPointForDestination(
     const ScrollSnapInfo& aSnapInfo, nsIScrollableFrame::ScrollUnit aUnit,
     const nsRect& aScrollRange, const nsPoint& aStartPos,
     const nsPoint& aDestination) {
-  if (aSnapInfo.mScrollSnapTypeY == StyleScrollSnapStrictness::None &&
-      aSnapInfo.mScrollSnapTypeX == StyleScrollSnapStrictness::None) {
+  if (aSnapInfo.mScrollSnapStrictnessY == StyleScrollSnapStrictness::None &&
+      aSnapInfo.mScrollSnapStrictnessX == StyleScrollSnapStrictness::None) {
     return Nothing();
   }
 
   if (StaticPrefs::layout_css_scroll_snap_v1_enabled() &&
       !aSnapInfo.HasSnapPositions()) {
     return Nothing();
   }
 
@@ -330,23 +330,25 @@ Maybe<nsPoint> ScrollSnapUtils::GetSnapP
     ProcessScrollSnapCoordinates(calcSnapPoints,
                                  aSnapInfo.mScrollSnapCoordinates, destPos);
   }
 
   bool snapped = false;
   nsPoint finalPos = calcSnapPoints.GetBestEdge();
   nscoord proximityThreshold = gfxPrefs::ScrollSnapProximityThreshold();
   proximityThreshold = nsPresContext::CSSPixelsToAppUnits(proximityThreshold);
-  if (aSnapInfo.mScrollSnapTypeY == StyleScrollSnapStrictness::Proximity &&
+  if (aSnapInfo.mScrollSnapStrictnessY ==
+          StyleScrollSnapStrictness::Proximity &&
       std::abs(aDestination.y - finalPos.y) > proximityThreshold) {
     finalPos.y = aDestination.y;
   } else {
     snapped = true;
   }
-  if (aSnapInfo.mScrollSnapTypeX == StyleScrollSnapStrictness::Proximity &&
+  if (aSnapInfo.mScrollSnapStrictnessX ==
+          StyleScrollSnapStrictness::Proximity &&
       std::abs(aDestination.x - finalPos.x) > proximityThreshold) {
     finalPos.x = aDestination.x;
   } else {
     snapped = true;
   }
   return snapped ? Some(finalPos) : Nothing();
 }
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -5385,18 +5385,18 @@ bool ScrollFrameHelper::NeedsScrollSnap(
     if (!scrollSnapFrame) {
       return false;
     }
     return scrollSnapFrame->StyleDisplay()->mScrollSnapType.strictness !=
            StyleScrollSnapStrictness::None;
   }
 
   ScrollStyles styles = GetScrollStylesFromFrame();
-  return styles.mScrollSnapTypeY != StyleScrollSnapStrictness::None ||
-         styles.mScrollSnapTypeX != StyleScrollSnapStrictness::None;
+  return styles.mScrollSnapStrictnessY != StyleScrollSnapStrictness::None ||
+         styles.mScrollSnapStrictnessX != StyleScrollSnapStrictness::None;
 }
 
 bool ScrollFrameHelper::IsScrollbarOnRight() const {
   nsPresContext* presContext = mOuter->PresContext();
 
   // The position of the scrollbar in top-level windows depends on the pref
   // layout.scrollbar.side. For non-top-level elements, it depends only on the
   // directionaliy of the element (equivalent to a value of "1" for the pref).
@@ -6833,17 +6833,17 @@ layers::ScrollSnapInfo ScrollFrameHelper
 
   const nsStyleDisplay* disp = scrollSnapFrame->StyleDisplay();
   if (disp->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) {
     // We won't be snapping, short-circuit the computation.
     return result;
   }
 
   WritingMode writingMode = GetFrameForDir()->GetWritingMode();
-  result.InitializeScrollSnapType(writingMode, disp);
+  result.InitializeScrollSnapStrictness(writingMode, disp);
 
   nsRect snapport = GetScrollPortRect();
   nsMargin scrollPadding = GetScrollPadding();
 
   Maybe<nsRect> snapportOnDestination;
   if (aDestination) {
     snapport.MoveTo(aDestination.value());
     snapport.Deflate(scrollPadding);
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1873,17 +1873,17 @@ fails-if(webrender) == 1059498-2.html 10
 fails-if(webrender) == 1059498-3.html 1059498-1-ref.html # WebRender: see bug 1499113
 == 1062108-1.html 1062108-1-ref.html
 == 1062792-1.html 1062792-1-ref.html
 == 1062963-floatmanager-reflow.html 1062963-floatmanager-reflow-ref.html
 == 1066554-1.html 1066554-1-ref.html
 == 1069716-1.html 1069716-1-ref.html
 == 1078262-1.html about:blank
 test-pref(layout.testing.overlay-scrollbars.always-visible,false) == 1081072-1.html 1081072-1-ref.html
-fuzzy-if(webrender,64-64,407-409) == 1081185-1.html 1081185-1-ref.html
+fuzzy-if(webrender,64-64,407-486) == 1081185-1.html 1081185-1-ref.html
 == 1097437-1.html 1097437-1-ref.html
 == 1103258-1.html 1103258-1-ref.html # assertion crash test with layers culling test
 == 1105137-1.html 1105137-1-ref.html
 fuzzy-if(d2d,0-36,0-304) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&d2d,0-139,0-701) == 1116480-1-fakeitalic-overflow.html 1116480-1-fakeitalic-overflow-ref.html
 == 1111753-1.html about:blank
 == 1114526-1.html 1114526-1-ref.html
 fuzzy-if(skiaContent,0-1,0-800000) == 1119117-1a.html 1119117-1-ref.html
 fuzzy-if(skiaContent,0-1,0-800000) == 1119117-1b.html 1119117-1-ref.html
--- a/layout/reftests/meta-viewport/initial-scale-0.html
+++ b/layout/reftests/meta-viewport/initial-scale-0.html
@@ -10,17 +10,17 @@ html, body {
 #container {
   min-width: 1600px; /* this value should be greater than viewport width */
   min-height: 3000px;
   position: relative;
 }
 #inner {
   position: absolute;
   top: 0;
-  right: 0;
+  left: 0;
   width: 100px;
   height: 100px;
   background: green;
 }
 </style>
 <div id="container">
   <div id="inner"></div>
 </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/meta-viewport/initial-scale-10-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta name="viewport" content="initial-scale=10,width=device-width">
+<style>
+html, body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+  scrollbar-width: none;
+}
+#container {
+  position: relative;
+}
+#inner {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 10px;
+  height: 10px;
+  background: green;
+}
+</style>
+<div id="container">
+  <div id="inner"></div>
+</div>
--- a/layout/reftests/meta-viewport/initial-scale-100.html
+++ b/layout/reftests/meta-viewport/initial-scale-100.html
@@ -3,24 +3,22 @@
 <style>
 html, body {
   margin: 0;
   width: 100%;
   height: 100%;
   scrollbar-width: none;
 }
 #container {
-  min-width: 1600px; /* this value should be greater than viewport width */
-  min-height: 3000px;
   position: relative;
 }
 #inner {
   position: absolute;
   top: 0;
-  right: 0;
-  width: 100px;
-  height: 100px;
+  left: 0;
+  width: 10px;
+  height: 10px;
   background: green;
 }
 </style>
 <div id="container">
   <div id="inner"></div>
 </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/meta-viewport/negative-initial-and-maximum-scale.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta name="viewport" content="initial-scale=-1,maximum-scale=-1,width=device-width">
+<style>
+html, body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+  scrollbar-width: none;
+}
+#container {
+  min-width: 1600px; /* this value should be double of viewport width */
+  min-height: 3000px;
+  position: relative;
+}
+#inner {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div id="container">
+  <div id="inner"></div>
+</div>
--- a/layout/reftests/meta-viewport/reftest.list
+++ b/layout/reftests/meta-viewport/reftest.list
@@ -1,18 +1,19 @@
 default-preferences pref(dom.meta-viewport.enabled,true) pref(apz.allow_zooming,true)
 
 # These tests assume that viewport width in reftest is 800px.
 == box-shadow.html initial-scale-0_5-ref.html
-== initial-scale-0.html initial-scale-0_5-ref.html
-== initial-scale-100.html initial-scale-0_5-ref.html
+== initial-scale-0.html initial-scale-0_25-ref.html
+== initial-scale-100.html initial-scale-10-ref.html
 == no-viewport.html initial-scale-0_5-ref.html
 == viewport-width.html initial-scale-0_5-ref.html
 == initial-scale-1.html no-zoom-ref.html
 == minimum-scale.html no-zoom-ref.html
+== negative-initial-and-maximum-scale.html initial-scale-0_5-ref.html
 == no-scalable-with-minimum-scale.html no-scalable-with-minimum-scale-ref.html
 == clamped-by-default-minimum-scale.html initial-scale-0_25-ref.html
 
 skip-if(!Android) == position-fixed-on-minimum-scale-size.html position-fixed-on-minimum-scale-size-ref.html
 == position-fixed-out-of-view.html about:blank
 
 # Skip below tests on Windows (bug 1516322) on Webrender (bug 1520096)
 skip-if(winWidget||webrender) == overflow-region.html overflow-region-ref.html
--- a/layout/reftests/table-background/reftest.list
+++ b/layout/reftests/table-background/reftest.list
@@ -42,18 +42,18 @@ asserts-if(gtkWidget,0-6) != backgr_bord
 == border-collapse-table.html border-collapse-table-ref.html
 fuzzy-if(d2d,0-1,0-1083) fuzzy-if(skiaContent,0-1,0-2200) == border-collapse-opacity-table-cell.html border-collapse-opacity-table-cell-ref.html
 fuzzy-if(d2d,0-1,0-33174) fuzzy-if(skiaContent,0-1,0-16863) == border-collapse-opacity-table-column-group.html border-collapse-opacity-table-column-group-ref.html
 fuzzy-if(d2d,0-1,0-11058) fuzzy-if(skiaContent,0-1,0-5625) == border-collapse-opacity-table-column.html border-collapse-opacity-table-column-ref.html
 fuzzy-if(d2d,0-1,0-24606) fuzzy-if(skiaContent,0-1,0-17000) == border-collapse-opacity-table-row-group.html border-collapse-opacity-table-row-group-ref.html
 fuzzy-if(d2d,0-1,0-11000) fuzzy-if(skiaContent,0-1,0-11000) == border-collapse-opacity-table-row.html border-collapse-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,0-1,0-60000) == border-collapse-opacity-table.html border-collapse-opacity-table-ref.html
 fuzzy-if(d2d,0-1,0-2478) fuzzy-if(skiaContent,0-1,0-2500) == border-separate-opacity-table-cell.html border-separate-opacity-table-cell-ref.html
-fuzzy-if(d2d,0-1,0-38000) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html
-fuzzy-if(d2d,0-1,0-13000) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html
+fuzzy-if(d2d,0-1,0-38000) fuzzy-if(webrender,0-1,0-4078) == border-separate-opacity-table-column-group.html border-separate-opacity-table-column-group-ref.html
+fuzzy-if(d2d,0-1,0-13000) fuzzy-if(webrender,0-1,0-1329) == border-separate-opacity-table-column.html border-separate-opacity-table-column-ref.html
 fuzzy-if(d2d,0-1,0-37170) fuzzy-if(skiaContent,0-1,0-38000) == border-separate-opacity-table-row-group.html border-separate-opacity-table-row-group-ref.html
 fuzzy-if(d2d,0-1,0-12390) fuzzy-if(skiaContent,0-1,0-13000) == border-separate-opacity-table-row.html border-separate-opacity-table-row-ref.html
 fuzzy-if(d2d||skiaContent,0-1,0-95000) == border-separate-opacity-table.html border-separate-opacity-table-ref.html
 != scrollable-rowgroup-collapse-background.html scrollable-rowgroup-collapse-notref.html
 != scrollable-rowgroup-collapse-border.html scrollable-rowgroup-collapse-notref.html
 != scrollable-rowgroup-separate-background.html scrollable-rowgroup-separate-notref.html
 == scrollable-rowgroup-separate-border.html scrollable-rowgroup-separate-notref.html # scrolling rowgroups were removed in bug 28800
 == empty-cells-default-1.html empty-cells-default-1-ref.html
--- a/mobile/android/app/moz.build
+++ b/mobile/android/app/moz.build
@@ -44,18 +44,23 @@ for var in ('MOZ_ANDROID_GCM', ):
 for var in ('MOZ_ANDROID_GCM_SENDERID', ):
     if CONFIG[var]:
         DEFINES[var] = CONFIG[var]
 
 if CONFIG['MOZ_PKG_SPECIAL']:
     DEFINES['MOZ_PKG_SPECIAL'] = CONFIG['MOZ_PKG_SPECIAL']
 
 JS_PREFERENCE_PP_FILES += [
+     'mobile.js',
+]
+
+# Equivalent to JS_PREFERENCE_PP_FILES[CONFIG['ANDROID_CPU_ARCH']],
+# which isn't supported out of the box.
+FINAL_TARGET_PP_FILES.defaults.pref[CONFIG['ANDROID_CPU_ARCH']] += [
      'geckoview-prefs.js',
-     'mobile.js',
 ]
 
 FINAL_TARGET_PP_FILES += [
   'ua-update.json.in',
 ]
 
 if CONFIG['MOZ_ANDROID_GOOGLE_VR']:
     FINAL_TARGET_FILES += [
--- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js
@@ -223,27 +223,32 @@ class GeckoViewContentChild extends Geck
                 // restore() will return false, and thus abort restoration for the
                 // current |frame| and its descendants, if |data.url| is given but
                 // doesn't match the loaded document's URL.
                 return SessionStoreUtils.restoreFormData(frame.document, data);
               });
             }
           }, {capture: true, mozSystemGroup: true, once: true});
 
-          addEventListener("pageshow", _ => {
-            const scrolldata = this._savedState.scrolldata;
-            if (scrolldata) {
-              this.Utils.restoreFrameTreeData(content, scrolldata, (frame, data) => {
-                if (data.scroll) {
-                  SessionStoreUtils.restoreScrollPosition(frame, data);
-                }
-              });
+          let scrollRestore = _ => {
+            if (content.location != "about:blank") {
+              const scrolldata = this._savedState.scrolldata;
+              if (scrolldata) {
+                this.Utils.restoreFrameTreeData(content, scrolldata, (frame, data) => {
+                  if (data.scroll) {
+                    SessionStoreUtils.restoreScrollPosition(frame, data);
+                  }
+                });
+              }
+              delete this._savedState;
+              removeEventListener("pageshow", scrollRestore);
             }
-            delete this._savedState;
-          }, {capture: true, mozSystemGroup: true, once: true});
+          };
+
+          addEventListener("pageshow", scrollRestore, {capture: true, mozSystemGroup: true});
 
           if (!this.progressFilter) {
             this.progressFilter =
               Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
               .createInstance(Ci.nsIWebProgress);
             this.flags = Ci.nsIWebProgress.NOTIFY_LOCATION;
           }
 
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -65,16 +65,17 @@ var ModuleManager = {
       "GeckoView:UpdateSettings",
     ]);
 
     this.messageManager.addMessageListener("GeckoView:ContentModuleLoaded",
                                            this);
 
     this.forEach(module => {
       module.onInit();
+      module.loadInitFrameScript();
     });
 
     window.addEventListener("unload", () => {
       this.forEach(module => {
         module.enabled = false;
         module.onDestroy();
       });
 
@@ -157,16 +158,24 @@ var ModuleManager = {
     this.forEach(module => {
       if (module.impl) {
         module.impl.onInitBrowser();
       }
     });
 
     parent.appendChild(this.browser);
 
+    this.messageManager.addMessageListener("GeckoView:ContentModuleLoaded",
+                                           this);
+
+    this.forEach(module => {
+      // We're attaching a new browser so we have to reload the frame scripts
+      module.loadInitFrameScript();
+    });
+
     disabledModules.forEach(module => {
       module.enabled = true;
     });
 
     this.browser.focus();
     return true;
   },
 
@@ -259,72 +268,84 @@ class ModuleInfo {
     this._contentModuleLoaded = false;
     this._enabled = false;
     // Only enable once we performed initialization.
     this._enabledOnInit = enabled;
 
     // For init, load resource _before_ initializing browser to support the
     // onInitBrowser() override. However, load content module after initializing
     // browser, because we don't have a message manager before then.
-    this._loadPhase({
-      resource: onInit && onInit.resource,
-    });
-    this._onInitPhase = {
-      frameScript: onInit && onInit.frameScript,
-    };
+    this._loadResource(onInit);
+
+    this._onInitPhase = onInit;
     this._onEnablePhase = onEnable;
   }
 
   onInit() {
     if (this._impl) {
       this._impl.onInit();
       this._impl.onSettingsUpdate();
     }
-    this._loadPhase(this._onInitPhase);
 
     this.enabled = this._enabledOnInit;
   }
 
+  /**
+   * Loads the onInit frame script
+   */
+  loadInitFrameScript() {
+    this._loadFrameScript(this._onInitPhase);
+  }
+
   onDestroy() {
     if (this._impl) {
       this._impl.onDestroy();
     }
   }
 
-  // Called before the browser is removed
+  /**
+   * Called before the browser is removed
+   */
   onDestroyBrowser() {
-    if (this.impl) {
-      this.impl.onDestroyBrowser();
+    if (this._impl) {
+      this._impl.onDestroyBrowser();
     }
     this._contentModuleLoaded = false;
   }
 
   /**
-   * Load resources according to a phase object that contains possible keys,
+   * Load resource according to a phase object that contains possible keys,
    *
    * "resource": specify the JSM resource to load for this module.
    * "frameScript": specify a content JS frame script to load for this module.
    */
-  _loadPhase(aPhase) {
-    if (!aPhase) {
+  _loadResource(aPhase) {
+    if (!aPhase || !aPhase.resource || this._impl) {
       return;
     }
 
-    if (aPhase.resource && !this._impl) {
-      const exports = ChromeUtils.import(aPhase.resource);
-      this._impl = new exports[this._name](this);
+    const exports = ChromeUtils.import(aPhase.resource);
+    this._impl = new exports[this._name](this);
+  }
+
+  /**
+   * Load frameScript according to a phase object that contains possible keys,
+   *
+   * "frameScript": specify a content JS frame script to load for this module.
+   */
+  _loadFrameScript(aPhase) {
+    if (!aPhase || !aPhase.frameScript || this._contentModuleLoaded) {
+      return;
     }
 
-    if (aPhase.frameScript && !this._contentModuleLoaded) {
-      if (this._impl) {
-        this._impl.onLoadContentModule();
-      }
-      this._manager.messageManager.loadFrameScript(aPhase.frameScript, true);
-      this._contentModuleLoaded = true;
+    if (this._impl) {
+      this._impl.onLoadContentModule();
     }
+    this._manager.messageManager.loadFrameScript(aPhase.frameScript, true);
+    this._contentModuleLoaded = true;
   }
 
   get manager() {
     return this._manager;
   }
 
   get name() {
     return this._name;
@@ -345,17 +366,18 @@ class ModuleInfo {
 
     if (!aEnabled && this._impl) {
       this._impl.onDisable();
     }
 
     this._enabled = aEnabled;
 
     if (aEnabled) {
-      this._loadPhase(this._onEnablePhase);
+      this._loadResource(this._onEnablePhase);
+      this._loadFrameScript(this._onEnablePhase);
       if (this._impl) {
         this._impl.onEnable();
         this._impl.onSettingsUpdate();
       }
     }
 
     this._updateContentModuleState(/* includeSettings */ aEnabled);
   }
--- a/mobile/android/components/extensions/ext-tabs.js
+++ b/mobile/android/components/extensions/ext-tabs.js
@@ -325,17 +325,20 @@ this.tabs = class extends ExtensionAPI {
 
           if (updateProperties.url !== null) {
             let url = context.uri.resolve(updateProperties.url);
 
             if (!context.checkLoadURL(url, {dontReportErrors: true})) {
               return Promise.reject({message: `Illegal URL: ${url}`});
             }
 
-            nativeTab.browser.loadURI(url);
+            let options = {
+              triggeringPrincipal: context.principal,
+            };
+            nativeTab.browser.loadURI(url, options);
           }
 
           if (updateProperties.active !== null) {
             if (updateProperties.active) {
               BrowserApp.selectTab(nativeTab);
             } else {
               // Not sure what to do here? Which tab should we select?
             }
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -53,16 +53,24 @@ GeckoViewStartup.prototype = {
           ged: [
             "GeckoView:RegisterWebExtension",
             "GeckoView:UnregisterWebExtension",
             "GeckoView:WebExtension:PortDisconnect",
             "GeckoView:WebExtension:PortMessageFromApp",
           ],
         });
 
+        GeckoViewUtils.addLazyGetter(this, "GeckoViewStorageController", {
+          module: "resource://gre/modules/GeckoViewStorageController.jsm",
+          ged: [
+            "GeckoView:ClearData",
+            "GeckoView:ClearHostData",
+          ],
+        });
+
         GeckoViewUtils.addLazyPrefObserver({
           name: "geckoview.console.enabled",
           default: false,
         }, {
           handler: _ => this.GeckoViewConsole,
         });
 
         // Handle invalid form submission. If we don't hook up to this,
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -72,16 +72,17 @@ import org.mozilla.geckoview.MediaElemen
 import org.mozilla.geckoview.OverscrollEdgeEffect;
 import org.mozilla.geckoview.PanZoomController;
 import org.mozilla.geckoview.RuntimeSettings;
 import org.mozilla.geckoview.RuntimeTelemetry;
 import org.mozilla.geckoview.ScreenLength;
 import org.mozilla.geckoview.SessionAccessibility;
 import org.mozilla.geckoview.SessionFinder;
 import org.mozilla.geckoview.SessionTextInput;
+import org.mozilla.geckoview.StorageController;
 import org.mozilla.geckoview.WebExtension;
 import org.mozilla.geckoview.WebMessage;
 import org.mozilla.geckoview.WebRequest;
 import org.mozilla.geckoview.WebRequestError;
 import org.mozilla.geckoview.WebResponse;
 
 package org.mozilla.geckoview {
 
@@ -270,16 +271,17 @@ package org.mozilla.geckoview {
     method @UiThread public void attachTo(@NonNull Context);
     method @UiThread public void configurationChanged(@NonNull Configuration);
     method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context);
     method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context, @NonNull GeckoRuntimeSettings);
     method @UiThread @NonNull public static synchronized GeckoRuntime getDefault(@NonNull Context);
     method @UiThread @Nullable public GeckoRuntime.Delegate getDelegate();
     method @UiThread @Nullable public File getProfileDir();
     method @AnyThread @NonNull public GeckoRuntimeSettings getSettings();
+    method @UiThread @NonNull public StorageController getStorageController();
     method @UiThread @NonNull public RuntimeTelemetry getTelemetry();
     method @UiThread public void orientationChanged();
     method @UiThread public void orientationChanged(int);
     method @AnyThread public void readFromParcel(@NonNull Parcel);
     method @UiThread @NonNull public GeckoResult<Void> registerWebExtension(@NonNull WebExtension);
     method @UiThread public void setDelegate(@Nullable GeckoRuntime.Delegate);
     method @AnyThread public void shutdown();
     method @UiThread @NonNull public GeckoResult<Void> unregisterWebExtension(@NonNull WebExtension);
@@ -1007,16 +1009,36 @@ package org.mozilla.geckoview {
     method @UiThread public boolean onKeyMultiple(int, int, @NonNull KeyEvent);
     method @UiThread public boolean onKeyPreIme(int, @NonNull KeyEvent);
     method @UiThread public boolean onKeyUp(int, @NonNull KeyEvent);
     method @UiThread public void onProvideAutofillVirtualStructure(@NonNull ViewStructure, int);
     method @UiThread public void setDelegate(@Nullable GeckoSession.TextInputDelegate);
     method @UiThread public synchronized void setView(@Nullable View);
   }
 
+  public final class StorageController {
+    ctor public StorageController();
+    method @AnyThread @NonNull public GeckoResult<Void> clearData(long);
+    method @AnyThread @NonNull public GeckoResult<Void> clearDataFromHost(@NonNull String, long);
+  }
+
+  public static class StorageController.ClearFlags {
+    ctor public ClearFlags();
+    field public static final long ALL = 512L;
+    field public static final long ALL_CACHES = 6L;
+    field public static final long AUTH_SESSIONS = 32L;
+    field public static final long COOKIES = 1L;
+    field public static final long DOM_STORAGES = 16L;
+    field public static final long IMAGE_CACHE = 4L;
+    field public static final long NETWORK_CACHE = 2L;
+    field public static final long PERMISSIONS = 64L;
+    field public static final long SITE_DATA = 471L;
+    field public static final long SITE_SETTINGS = 192L;
+  }
+
   public class WebExtension {
     ctor public WebExtension(@NonNull String, @NonNull String, long);
     ctor public WebExtension(@NonNull String);
     method @UiThread public void setMessageDelegate(@Nullable WebExtension.MessageDelegate, @NonNull String);
     field public final long flags;
     field @NonNull public final String id;
     field @NonNull public final String location;
   }
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/manifest.json
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/manifest.json
@@ -7,18 +7,15 @@
   },
   "content_scripts": [
     {
       "matches": ["*://*.example.com/*"],
       "js": ["tabs.js"],
       "run_at": "document_idle"
     }
   ],
-  "web_accessible_resources": [
-    "tab.html"
-  ],
   "permissions": [
     "geckoViewAddons",
     "nativeMessaging",
     "tabs",
     "<all_urls>"
   ]
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/tab-script.js
@@ -0,0 +1,2 @@
+// Let's test privileged APIs
+browser.runtime.sendNativeMessage("browser", "HELLO_FROM_PAGE");
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/tab.html
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/tab.html
@@ -1,1 +1,2 @@
-<h1>Hello World!</h1>
\ No newline at end of file
+<h1>Hello World!</h1>
+<script src=tab-script.js></script>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -1,14 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
+import android.os.Handler
+import android.os.Looper
+import android.support.test.InstrumentationRegistry
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.ContentBlocking
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.WebRequestError
 
@@ -18,23 +21,43 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.test.util.HttpBin
+import java.net.URI
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class NavigationDelegateTest : BaseSessionTest() {
+    companion object {
+        val TEST_ENDPOINT: String = "http://localhost:4242"
+    }
+
+    lateinit var server: HttpBin
+
+    @Before
+    fun setup() {
+        server = HttpBin(InstrumentationRegistry.getTargetContext(), URI.create(TEST_ENDPOINT))
+        server.start()
+    }
+
+    @After
+    fun cleanup() {
+        server.stop()
+    }
 
     fun testLoadErrorWithErrorPage(testUri: String, expectedCategory: Int,
                                    expectedError: Int,
                                    errorPageUrl: String?) {
         sessionRule.delegateDuringNextWait(
                 object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
                     @AssertCalled(count = 1, order = [1])
                     override fun onLoadRequest(session: GeckoSession,
@@ -574,17 +597,17 @@ class NavigationDelegateTest : BaseSessi
 
         if (sessionRule.env.isMultiprocess) {
             assertThat("Snapshots should not be null",
                        result?.get("content"), notNullValue())
         }
     }
 
     @Test fun load() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession,
                                        request: LoadRequest):
                                        GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
@@ -768,17 +791,17 @@ class NavigationDelegateTest : BaseSessi
         loadDataHelper("/assets/www/images/test.gif", "image/gif")
     }
 
     @Test fun loadData_noMimeType() {
         loadDataHelper("/assets/www/images/test.gif")
     }
 
     @Test fun reload() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession,
@@ -810,20 +833,20 @@ class NavigationDelegateTest : BaseSessi
             @AssertCalled(false)
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 return null
             }
         })
     }
 
     @Test fun goBackAndForward() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
-        sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO2_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
         })
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt
@@ -460,17 +460,34 @@ class WebExtensionTest : BaseSessionTest
             testIframeTopLevel()
         } finally {
             httpBin.stop()
         }
     }
 
     @Test
     fun loadWebExtensionPage() {
-        val extension = WebExtension("resource://android/assets/web_extensions/extension-page-update/")
+        val result = GeckoResult<String>()
+        var extension: WebExtension? = null;
+
+        val messageDelegate = object : WebExtension.MessageDelegate {
+            override fun onMessage(message: Any,
+                                   sender: WebExtension.MessageSender): GeckoResult<Any>? {
+                Assert.assertEquals(extension, sender.webExtension)
+                Assert.assertEquals(WebExtension.MessageSender.ENV_TYPE_EXTENSION,
+                        sender.environmentType)
+                result.complete(message as String)
+
+                return null
+            }
+        }
+
+        extension = WebExtension("resource://android/assets/web_extensions/extension-page-update/")
+        extension.setMessageDelegate(messageDelegate, "browser")
+
         sessionRule.waitForResult(sessionRule.runtime.registerWebExtension(extension))
 
         mainSession.loadUri("http://example.com");
 
         mainSession.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ProgressDelegate {
             @GeckoSessionTestRule.AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("Url should load example.com first",
@@ -499,16 +516,20 @@ class WebExtensionTest : BaseSessionTest
                 }
             }
         })
 
         // Make sure the page loaded successfully
         sessionRule.waitForResult(pageStop)
 
         assertThat("Url should load WebExtension page", page, endsWith("/tab.html"))
+
+        assertThat("WebExtension page should have access to privileged APIs",
+            sessionRule.waitForResult(result), equalTo("HELLO_FROM_PAGE"))
+
         sessionRule.waitForResult(sessionRule.runtime.unregisterWebExtension(extension))
     }
 
     @Test
     fun badFileType() {
         testRegisterError("resource://android/bad/location/error",
                 "does not point to a folder or an .xpi")
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -155,16 +155,17 @@ public final class GeckoRuntime implemen
 
         return sDefaultRuntime;
     }
 
     private GeckoRuntimeSettings mSettings;
     private Delegate mDelegate;
     private RuntimeTelemetry mTelemetry;
     private WebExtensionEventDispatcher mWebExtensionDispatcher;
+    private StorageController mStorageController;
 
     /**
      * Attach the runtime to the given context.
      *
      * @param context The new context to attach to.
      */
     @UiThread
     public void attachTo(final @NonNull Context context) {
@@ -558,16 +559,34 @@ public final class GeckoRuntime implemen
      *                       {@link android.content.res.Configuration}.
      */
     @UiThread
     public void orientationChanged(final int newOrientation) {
         ThreadUtils.assertOnUiThread();
         GeckoScreenOrientation.getInstance().update(newOrientation);
     }
 
+
+    /**
+     * Get the storage controller for this runtime.
+     * The storage controller can be used to manage persistent storage data
+     * accumulated by {@link GeckoSession}.
+     *
+     * @return The {@link StorageController} for this instance.
+     */
+    @UiThread
+    public @NonNull StorageController getS