merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 08 Jun 2017 14:43:56 +0200
changeset 411067 6491fb29e7fcc9e02cc179ae2856f426a3552385
parent 411034 f223e1fd2044a026c740434df95f37a7f7accf48 (current diff)
parent 411066 f46c3d67b5eb210dd769c68b9e3d34841a756824 (diff)
child 411068 750b3c0ffcee05ad02039195ffa7441bfa887268
child 411090 280dedc5b8c803a9c8ac42a90c38062123da12a7
child 411124 a08ea7f330370f90091ef1ee4b3a814bc2112755
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
layout/generic/nsIFrame.h
layout/painting/FrameLayerBuilder.cpp
layout/svg/nsSVGMaskFrameNEON.cpp
layout/svg/nsSVGMaskFrameNEON.h
modules/libpref/init/all.js
--- a/accessible/ipc/win/handler/moz.build
+++ b/accessible/ipc/win/handler/moz.build
@@ -44,16 +44,17 @@ OS_LIBS += [
 
 RCINCLUDE = 'AccessibleHandler.rc'
 
 # Since we are defining our own COM entry points (DllRegisterServer et al),
 # but we still want to be able to delegate some work to the generated code,
 # we add the prefix "Proxy" to all of the generated counterparts.
 DEFINES['ENTRY_PREFIX'] = 'Proxy'
 DEFINES['REGISTER_PROXY_DLL'] = True
+LIBRARY_DEFINES['MOZ_MSCOM_REMARSHAL_NO_HANDLER'] = True
 
 # We want to generate distinct UUIDs on a per-channel basis, so we need
 # finer granularity than the standard preprocessor definitions offer.
 # These defines allow us to separate local builds from automated builds,
 # as well as separate beta from release.
 if CONFIG['MOZ_UPDATE_CHANNEL'] == 'default':
   DEFINES['USE_LOCAL_UUID'] = True
 elif CONFIG['MOZ_UPDATE_CHANNEL'] == 'beta':
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -535,17 +535,18 @@ BrowserGlue.prototype = {
     os.addObserver(this, "restart-in-safe-mode");
     os.addObserver(this, "flash-plugin-hang");
     os.addObserver(this, "xpi-signature-changed");
     os.addObserver(this, "sync-ui-state:update");
 
     this._flashHangCount = 0;
     this._firstWindowReady = new Promise(resolve => this._firstWindowLoaded = resolve);
 
-    if (AppConstants.platform == "macosx") {
+    if (AppConstants.platform == "macosx" ||
+        (AppConstants.platform == "win" && AppConstants.RELEASE_OR_BETA)) {
       // Handles prompting to inform about incompatibilites when accessibility
       // and e10s are active together.
       E10SAccessibilityCheck.init();
     }
   },
 
   // cleanup (called on application shutdown)
   _dispose: function BG__dispose() {
--- a/devtools/client/netmonitor/src/components/toolbar.js
+++ b/devtools/client/netmonitor/src/components/toolbar.js
@@ -7,23 +7,24 @@
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
-const { FILTER_SEARCH_DELAY, FILTER_FLAGS } = require("../constants");
+const { FILTER_SEARCH_DELAY } = require("../constants");
 const {
   getDisplayedRequestsSummary,
   getRequestFilterTypes,
   isNetworkDetailsToggleButtonDisabled,
 } = require("../selectors/index");
 
+const { autocompleteProvider } = require("../utils/filter-text-utils");
 const { L10N } = require("../utils/l10n");
 
 // Components
 const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
 
 const { button, div, span } = DOM;
 
 const COLLPASE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
@@ -87,21 +88,16 @@ const Toolbar = createClass({
           "aria-pressed": checked,
           "data-key": type,
         },
           L10N.getStr(`netmonitor.toolbar.filter.${type}`)
         )
       );
     });
 
-    // Setup autocomplete list
-    let negativeAutocompleteList = FILTER_FLAGS.map((item) => `-${item}`);
-    let autocompleteList = [...FILTER_FLAGS, ...negativeAutocompleteList]
-      .map((item) => `${item}:`);
-
     return (
       span({ className: "devtools-toolbar devtools-toolbar-container" },
         span({ className: "devtools-toolbar-group" },
           button({
             className: "devtools-button devtools-clear-icon requests-list-clear-button",
             title: TOOLBAR_CLEAR,
             onClick: clearRequests,
           }),
@@ -109,17 +105,17 @@ const Toolbar = createClass({
         ),
         span({ className: "devtools-toolbar-group" },
           SearchBox({
             delay: FILTER_SEARCH_DELAY,
             keyShortcut: SEARCH_KEY_SHORTCUT,
             placeholder: SEARCH_PLACE_HOLDER,
             type: "filter",
             onChange: setRequestFilterText,
-            autocompleteList,
+            autocompleteProvider,
           }),
           button({
             className: toggleButtonClassName.join(" "),
             title: networkDetailsOpen ? COLLPASE_DETAILS_PANE : EXPAND_DETAILS_PANE,
             disabled: networkDetailsToggleDisabled,
             tabIndex: "0",
             onClick: toggleNetworkDetails,
           }),
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -237,11 +237,55 @@ function isFreetextMatch(item, text) {
 
   for (let flagFilter of filters.flags) {
     match = match && isFlagFilterMatch(item, flagFilter);
   }
 
   return match;
 }
 
+/**
+ * Generates an autocomplete list for the search-box for network monitor
+ *
+ * It expects an entire string of the searchbox ie "is:cached pr".
+ * The string is then tokenized into "is:cached" and "pr"
+ *
+ * @param {string} filter - The entire search string of the search box
+ * @return {Array} - The output is an array of objects as below
+ * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
+ * `value` is used to update the search-box input box for given item
+ * `displayValue` is used to render the autocomplete list
+ */
+function autocompleteProvider(filter) {
+  if (!filter) {
+    return [];
+  }
+
+  let negativeAutocompleteList = FILTER_FLAGS.map((item) => `-${item}`);
+  let baseList = [...FILTER_FLAGS, ...negativeAutocompleteList]
+    .map((item) => `${item}:`);
+
+  // The last token is used to filter the base autocomplete list
+  let tokens = filter.split(/\s+/g);
+  let lastToken = tokens[tokens.length - 1];
+  let previousTokens = tokens.slice(0, tokens.length - 1);
+
+  // Autocomplete list is not generated for empty lastToken
+  if (!lastToken) {
+    return [];
+  }
+
+  return baseList
+    .filter((item) => {
+      return item.toLowerCase().startsWith(lastToken.toLowerCase())
+        && item.toLowerCase() !== lastToken.toLowerCase();
+    })
+    .sort()
+    .map(item => ({
+      value: [...previousTokens, item].join(" "),
+      displayValue: item,
+    }));
+}
+
 module.exports = {
   isFreetextMatch,
+  autocompleteProvider,
 };
--- a/devtools/client/shared/components/autocomplete-popup.js
+++ b/devtools/client/shared/components/autocomplete-popup.js
@@ -5,17 +5,25 @@
 "use strict";
 
 const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
 module.exports = createClass({
   displayName: "AutocompletePopup",
 
   propTypes: {
-    list: PropTypes.array.isRequired,
+    /**
+     * autocompleteProvider takes search-box's entire input text as `filter` argument
+     * ie. "is:cached pr"
+     * returned value is array of objects like below
+     * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
+     * `value` is used to update the search-box input box for given item
+     * `displayValue` is used to render the autocomplete list
+     */
+    autocompleteProvider: PropTypes.func.isRequired,
     filter: PropTypes.string.isRequired,
     onItemSelected: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     return this.computeState(this.props);
   },
 
@@ -27,96 +35,93 @@ module.exports = createClass({
   },
 
   componentDidUpdate() {
     if (this.refs.selected) {
       this.refs.selected.scrollIntoView(false);
     }
   },
 
-  computeState({ filter, list }) {
-    let filteredList = list.filter((item) => {
-      return item.toLowerCase().startsWith(filter.toLowerCase())
-        && item.toLowerCase() !== filter.toLowerCase();
-    }).sort();
-    let selectedIndex = filteredList.length == 1 ? 0 : -1;
+  computeState({ autocompleteProvider, filter }) {
+    let list = autocompleteProvider(filter);
+    let selectedIndex = list.length == 1 ? 0 : -1;
 
-    return { filteredList, selectedIndex };
+    return { list, selectedIndex };
   },
 
   /**
    * Use this method to select the top-most item
    * This method is public, called outside of the autocomplete-popup component.
    */
   jumpToTop() {
     this.setState({ selectedIndex: 0 });
   },
 
   /**
    * Use this method to select the bottom-most item
    * This method is public.
    */
   jumpToBottom() {
-    let selectedIndex = this.state.filteredList.length - 1;
-    this.setState({ selectedIndex });
+    this.setState({ selectedIndex: this.state.list.length - 1 });
   },
 
   /**
    * Increment the selected index with the provided increment value. Will cycle to the
    * beginning/end of the list if the index exceeds the list boundaries.
    * This method is public.
    *
    * @param {number} increment - No. of hops in the direction
    */
   jumpBy(increment = 1) {
-    let { filteredList, selectedIndex } = this.state;
+    let { list, selectedIndex } = this.state;
     let nextIndex = selectedIndex + increment;
     if (increment > 0) {
       // Positive cycling
-      nextIndex = nextIndex > filteredList.length - 1 ? 0 : nextIndex;
+      nextIndex = nextIndex > list.length - 1 ? 0 : nextIndex;
     } else if (increment < 0) {
       // Inverse cycling
-      nextIndex = nextIndex < 0 ? filteredList.length - 1 : nextIndex;
+      nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex;
     }
     this.setState({selectedIndex: nextIndex});
   },
 
   /**
    * Submit the currently selected item to the onItemSelected callback
    * This method is public.
    */
   select() {
     if (this.refs.selected) {
-      this.props.onItemSelected(this.refs.selected.textContent);
+      this.props.onItemSelected(this.refs.selected.dataset.value);
     }
   },
 
   onMouseDown(e) {
     e.preventDefault();
     this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
   },
 
   render() {
-    let { filteredList } = this.state;
+    let { list } = this.state;
 
-    return filteredList.length > 0 && dom.div(
+    return list.length > 0 && dom.div(
       { className: "devtools-autocomplete-popup devtools-monospace" },
       dom.ul(
         { className: "devtools-autocomplete-listbox" },
-        filteredList.map((item, i) => {
+        list.map((item, i) => {
           let isSelected = this.state.selectedIndex == i;
           let itemClassList = ["autocomplete-item"];
 
           if (isSelected) {
             itemClassList.push("autocomplete-selected");
           }
           return dom.li({
             key: i,
             "data-index": i,
+            "data-value": item.value,
             className: itemClassList.join(" "),
             ref: isSelected ? "selected" : null,
             onMouseDown: this.onMouseDown,
-          }, item);
+          }, item.displayValue);
         })
       )
     );
   }
 });
--- a/devtools/client/shared/components/search-box.js
+++ b/devtools/client/shared/components/search-box.js
@@ -17,23 +17,17 @@ module.exports = createClass({
   displayName: "SearchBox",
 
   propTypes: {
     delay: PropTypes.number,
     keyShortcut: PropTypes.string,
     onChange: PropTypes.func,
     placeholder: PropTypes.string,
     type: PropTypes.string,
-    autocompleteList: PropTypes.array,
-  },
-
-  getDefaultProps() {
-    return {
-      autocompleteList: [],
-    };
+    autocompleteProvider: PropTypes.func,
   },
 
   getInitialState() {
     return {
       value: "",
       focused: false,
     };
   },
@@ -61,16 +55,17 @@ module.exports = createClass({
     if (this.searchTimeout) {
       clearTimeout(this.searchTimeout);
     }
   },
 
   onChange() {
     if (this.state.value !== this.refs.input.value) {
       this.setState({
+        focused: true,
         value: this.refs.input.value,
       });
     }
 
     if (!this.props.delay) {
       this.props.onChange(this.state.value);
       return;
     }
@@ -97,20 +92,18 @@ module.exports = createClass({
     this.setState({ focused: true });
   },
 
   onBlur() {
     this.setState({ focused: false });
   },
 
   onKeyDown(e) {
-    let { autocompleteList } = this.props;
     let { autocomplete } = this.refs;
-
-    if (autocompleteList.length == 0) {
+    if (!autocomplete || autocomplete.state.list.length <= 0) {
       return;
     }
 
     switch (e.key) {
       case "ArrowDown":
         autocomplete.jumpBy(1);
         break;
       case "ArrowUp":
@@ -139,23 +132,22 @@ module.exports = createClass({
         break;
     }
   },
 
   render() {
     let {
       type = "search",
       placeholder,
-      autocompleteList
+      autocompleteProvider,
     } = this.props;
     let { value } = this.state;
     let divClassList = ["devtools-searchbox", "has-clear-btn"];
     let inputClassList = [`devtools-${type}input`];
-    let showAutocomplete =
-      autocompleteList.length > 0 && this.state.focused && value !== "";
+    let showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
 
     if (value !== "") {
       inputClassList.push("filled");
     }
     return dom.div(
       { className: divClassList.join(" ") },
       dom.input({
         className: inputClassList.join(" "),
@@ -168,17 +160,17 @@ module.exports = createClass({
         value,
       }),
       dom.button({
         className: "devtools-searchinput-clear",
         hidden: value == "",
         onClick: this.onClearButtonClick
       }),
       showAutocomplete && AutocompletePopup({
-        list: autocompleteList,
+        autocompleteProvider,
         filter: value,
         ref: "autocomplete",
         onItemSelected: (itemValue) => {
           this.setState({ value: itemValue });
           this.onChange();
         }
       })
     );
--- a/devtools/client/shared/components/test/mochitest/test_searchbox-with-autocomplete.html
+++ b/devtools/client/shared/components/test/mochitest/test_searchbox-with-autocomplete.html
@@ -38,30 +38,54 @@ window.onload = async function () {
   }
 
   let React = browserRequire("devtools/client/shared/vendor/react");
   let SearchBox = React.createFactory(
     browserRequire("devtools/client/shared/components/search-box")
   );
   const { component, $ } = await createComponentTest(SearchBox, {
     type: "search",
-    autocompleteList: [
-      "foo",
-      "BAR",
-      "baZ",
-      "abc",
-      "pqr",
-      "xyz",
-      "ABC",
-      "a1",
-      "a2",
-      "a3",
-      "a4",
-      "a5",
-    ],
+    autocompleteProvider: (filter) => {
+      let baseList = [
+        "foo",
+        "BAR",
+        "baZ",
+        "abc",
+        "pqr",
+        "xyz",
+        "ABC",
+        "a1",
+        "a2",
+        "a3",
+        "a4",
+        "a5",
+      ];
+      if (!filter) {
+        return [];
+      }
+
+      let tokens = filter.split(/\s+/g);
+      let lastToken = tokens[tokens.length - 1];
+      let previousTokens = tokens.slice(0, tokens.length - 1);
+
+      if (!lastToken) {
+        return [];
+      }
+
+      return baseList
+        .filter((item) => {
+          return item.toLowerCase().startsWith(lastToken.toLowerCase())
+            && item.toLowerCase() !== lastToken.toLowerCase();
+        })
+        .sort()
+        .map(item => ({
+          value: [...previousTokens, item].join(" "),
+          displayValue: item,
+        }));
+    },
     onChange: () => null,
   });
   const { refs } = component;
 
   async function testSearchBoxWithAutocomplete() {
     ok(!$(".devtools-autocomplete-popup"), "Autocomplete list not visible");
 
     $(".devtools-searchinput").focus();
@@ -145,16 +169,18 @@ window.onload = async function () {
     // Enter key selection
     synthesizeKey("VK_UP", {});
     await forceRender(component);
     synthesizeKey("VK_RETURN", {});
     is(component.state.value, "abc", "Enter selection");
     ok(!$(".devtools-autocomplete-popup"), "Enter/Return hides the popup");
 
     // Escape should remove the autocomplete component
+    synthesizeKey("VK_BACK_SPACE", {});
+    await forceRender(component);
     synthesizeKey("VK_ESCAPE", {});
     await forceRender(component);
     ok(!$(".devtools-autocomplete-popup"),
       "Autocomplete list removed from DOM on Escape");
   }
 
   async function testMouseEventsWithAutocomplete() {
     $(".devtools-searchinput").focus();
@@ -177,17 +203,32 @@ window.onload = async function () {
       $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"),
       {}, window
     );
     await forceRender(component);
     is(component.state.value, "pqr", "Mouse click selects the item.");
     ok(!$(".devtools-autocomplete-popup"), "Mouse click on item hides the popup");
   }
 
+  async function testTokenizedAutocomplete() {
+    // Test for string "pqr ab" which should show list of ABC, abc
+    sendString(" ab");
+    await forceRender(component);
+    compareAutocompleteList($(".devtools-autocomplete-listbox"), ["ABC", "abc"]);
+
+    // Select the first element, value now should be "pqr ABC"
+    synthesizeMouseAtCenter(
+      $(".devtools-autocomplete-listbox .autocomplete-item:nth-child(1)"),
+      {}, window
+    );
+    is(component.state.value, "pqr ABC", "Post Tokenization value selection");
+  }
+
   add_task(async function () {
     await testSearchBoxWithAutocomplete();
     await testKeyEventsWithAutocomplete();
     await testMouseEventsWithAutocomplete();
+    await testTokenizedAutocomplete();
   });
 };
 </script>
 </body>
 </html>
--- a/devtools/server/actors/errordocs.js
+++ b/devtools/server/actors/errordocs.js
@@ -76,16 +76,17 @@ const ErrorDocs = {
   JSMSG_DEPRECATED_TOLOCALEFORMAT: "Deprecated_toLocaleFormat",
   JSMSG_RESERVED_ID: "Reserved_identifier",
   JSMSG_BAD_CONST_ASSIGN: "Invalid_const_assignment",
   JSMSG_BAD_CONST_DECL: "Missing_initializer_in_const",
   JSMSG_OF_AFTER_FOR_LOOP_DECL: "Invalid_for-of_initializer",
   JSMSG_BAD_URI: "Malformed_URI",
   JSMSG_DEPRECATED_DELETE_OPERAND: "Delete_in_strict_mode",
   JSMSG_MISSING_FORMAL: "Missing_formal_parameter",
+  JSMSG_CANT_TRUNCATE_ARRAY: "Non_configurable_array_element",
 };
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Mixed_content";
 const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
 const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Insecure_passwords";
 const PUBLIC_KEY_PINS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/HTTP/Public_Key_Pinning";
 const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security";
 const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Weak_Signature_Algorithm";
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -409,18 +409,20 @@ DOMIntersectionObserver::Update(nsIDocum
       );
       intersectionRect = EdgeInclusiveIntersection(
         intersectionRectRelativeToRoot,
         rootIntersectionRect
       );
       if (intersectionRect.isSome() && !isSameDoc) {
         nsRect rect = intersectionRect.value();
         nsPresContext* presContext = targetFrame->PresContext();
-        nsLayoutUtils::TransformRect(rootFrame,
-          presContext->PresShell()->GetRootScrollFrame(), rect);
+        nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
+        if (rootScrollFrame) {
+          nsLayoutUtils::TransformRect(rootFrame, rootScrollFrame, rect);
+        }
         intersectionRect = Some(rect);
       }
     }
 
     double targetArea = targetRect.width * targetRect.height;
     double intersectionArea = !intersectionRect ?
       0 : intersectionRect->width * intersectionRect->height;
     
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1370968-inner.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
+        xmlns:xhtml="http://www.w3.org/1999/xhtml">
+<xhtml:div id="target"></xhtml:div>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1370968.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe id="iframe" src="1370968-inner.xul"></iframe>
+<script>
+    var io = new IntersectionObserver(function () {
+    }, { });
+    var iframe = document.getElementById('iframe');
+    iframe.onload = function () {
+        io.observe(iframe.contentDocument.getElementById('target'));
+    };
+</script>
+</body>
+</html>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -196,23 +196,24 @@ load 978646.html
 pref(dom.webcomponents.enabled,true) load 1024428-1.html
 load 1026714.html
 pref(dom.webcomponents.enabled,true) load 1027461-1.html
 pref(dom.webcomponents.enabled,true) load 1029710.html
 load 1154598.xhtml
 load 1157995.html
 load 1158412.html
 load 1181619.html
-load structured_clone_container_throws.html
-HTTP(..) load xhr_abortinprogress.html
-load xhr_empty_datauri.html
-load xhr_html_nullresponse.html
 load 1230422.html
 load 1251361.html
 load 1304437.html
 pref(dom.IntersectionObserver.enabled,true) load 1324209.html
 pref(dom.IntersectionObserver.enabled,true) load 1326194-1.html
 pref(dom.IntersectionObserver.enabled,true) load 1326194-2.html
 pref(dom.IntersectionObserver.enabled,true) load 1332939.html
+pref(dom.webcomponents.enabled,true) load 1341693.html
 pref(dom.IntersectionObserver.enabled,true) load 1353529.xul
-pref(dom.webcomponents.enabled,true) load 1341693.html
 pref(dom.IntersectionObserver.enabled,true) load 1369363.xul
 load 1370072.html
+pref(dom.IntersectionObserver.enabled,true) load 1370968.html
+load structured_clone_container_throws.html
+HTTP(..) load xhr_abortinprogress.html
+load xhr_empty_datauri.html
+load xhr_html_nullresponse.html
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -51,24 +51,26 @@
 #include "nsIArray.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "prmem.h"
 #include "WrapperFactory.h"
 #include "nsGlobalWindow.h"
 #include "nsScriptNameSpaceManager.h"
 #include "mozilla/AutoRestore.h"
+#include "mozilla/MainThreadIdlePeriod.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsAXPCNativeCallContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/SystemGroup.h"
-
+#include "nsRefreshDriver.h"
 #include "nsJSPrincipals.h"
 
 #ifdef XP_MACOSX
 // AssertMacros.h defines 'check' and conflicts with AccessCheck.h
 #undef check
 #endif
 #include "AccessCheck.h"
 
@@ -106,31 +108,34 @@ const size_t gStackSize = 8192;
 
 // The default amount of time to wait from the user being idle to starting a
 // shrinking GC.
 #define NS_DEAULT_INACTIVE_GC_DELAY 300000 // ms
 
 // Maximum amount of time that should elapse between incremental GC slices
 #define NS_INTERSLICE_GC_DELAY      100 // ms
 
-// If we haven't painted in 100ms, or we're in e10s parent process and
-// user isn't active, we allow for a longer GC budget.
-#define NS_INTERSLICE_GC_BUDGET     40 // ms
-
 // The amount of time we wait between a request to CC (after GC ran)
 // and doing the actual CC.
 #define NS_CC_DELAY                 6000 // ms
 
 #define NS_CC_SKIPPABLE_DELAY       250 // ms
 
+// ForgetSkippable is usually fast, so we can use small budgets.
+// This isn't a real budget but a hint to CollectorRunner whether there
+// is enough time to call ForgetSkippable.
+static const int64_t kForgetSkippableSliceDuration = 2;
+
 // Maximum amount of time that should elapse between incremental CC slices
 static const int64_t kICCIntersliceDelay = 32; // ms
 
-// Time budget for an incremental CC slice
+// Time budget for an incremental CC slice when using timer to run it.
 static const int64_t kICCSliceBudget = 5; // ms
+// Minimum budget for an incremental CC slice when using idle time to run it.
+static const int64_t kIdleICCSliceBudget = 3; // ms
 
 // Maximum total duration for an ICC
 static const uint32_t kMaxICCDuration = 2000; // ms
 
 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
 // objects in the purple buffer.
 #define NS_CC_FORCED                (2 * 60 * PR_USEC_PER_SEC) // 2 min
 #define NS_CC_FORCED_PURPLE_LIMIT   10
@@ -139,24 +144,26 @@ static const uint32_t kMaxICCDuration = 
 #define NS_MAX_CC_LOCKEDOUT_TIME    (30 * PR_USEC_PER_SEC) // 30 seconds
 
 // Trigger a CC if the purple buffer exceeds this size when we check it.
 #define NS_CC_PURPLE_LIMIT          200
 
 // Large value used to specify that a script should run essentially forever
 #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
 
+class CollectorRunner;
+
 // if you add statics here, add them to the list in StartupJSEnvironment
 
 static nsITimer *sGCTimer;
 static nsITimer *sShrinkingGCTimer;
-static nsITimer *sCCTimer;
-static nsITimer *sICCTimer;
+static StaticRefPtr<CollectorRunner> sCCRunner;
+static StaticRefPtr<CollectorRunner> sICCRunner;
 static nsITimer *sFullGCTimer;
-static nsITimer *sInterSliceGCTimer;
+static StaticRefPtr<CollectorRunner> sInterSliceGCRunner;
 
 static TimeStamp sLastCCEndTime;
 
 static bool sCCLockedOut;
 static PRTime sCCLockedOutTime;
 
 static JS::GCSliceCallback sPrevGCSliceCallback;
 
@@ -171,29 +178,28 @@ static bool sHasRunGC;
 static uint32_t sPendingLoadCount;
 static bool sLoadingInProgress;
 
 static uint32_t sCCollectedWaitingForGC;
 static uint32_t sCCollectedZonesWaitingForGC;
 static uint32_t sLikelyShortLivingObjectsNeedingGC;
 static bool sPostGCEventsToConsole;
 static bool sPostGCEventsToObserver;
-static int32_t sCCTimerFireCount = 0;
+static int32_t sCCRunnerFireCount = 0;
 static uint32_t sMinForgetSkippableTime = UINT32_MAX;
 static uint32_t sMaxForgetSkippableTime = 0;
 static uint32_t sTotalForgetSkippableTime = 0;
 static uint32_t sRemovedPurples = 0;
 static uint32_t sForgetSkippableBeforeCC = 0;
 static uint32_t sPreviousSuspectedCount = 0;
 static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
 static bool sNeedsFullCC = false;
 static bool sNeedsFullGC = false;
 static bool sNeedsGCAfterCC = false;
 static bool sIncrementalCC = false;
-static bool sDidPaintAfterPreviousICCSlice = false;
 static int32_t sActiveIntersliceGCBudget = 0; // ms;
 static nsScriptNameSpaceManager *gNameSpaceManager;
 
 static PRTime sFirstCollectionTime;
 
 static JSContext* sContext;
 
 static bool sIsInitialized;
@@ -218,16 +224,188 @@ static uint32_t sCompactOnUserInactiveDe
 static bool sIsCompactingOnUserInactive = false;
 
 // In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more
 // aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps
 // us from triggering expensive full collections too frequently.
 static int32_t sExpensiveCollectorPokes = 0;
 static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5;
 
+// Return true if some meaningful work was done.
+typedef bool (*CollectorRunnerCallback) (TimeStamp aDeadline, void* aData);
+
+// Repeating callback runner for CC and GC.
+class CollectorRunner final : public IdleRunnable
+{
+public:
+  static already_AddRefed<CollectorRunner>
+  Create(CollectorRunnerCallback aCallback, uint32_t aDelay,
+         int64_t aBudget, bool aRepeating, void* aData = nullptr)
+  {
+    RefPtr<CollectorRunner> runner =
+      new CollectorRunner(aCallback, aDelay, aBudget, aRepeating, aData);
+    runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
+    return runner.forget();
+  }
+
+  NS_IMETHOD Run() override
+  {
+    if (!mCallback) {
+      return NS_OK;
+    }
+
+    // Deadline is null when called from timer.
+    bool deadLineWasNull = mDeadline.IsNull();
+    bool didRun = false;
+    if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) {
+      CancelTimer();
+      didRun = mCallback(mDeadline, mData);
+    }
+
+    if (mCallback && (mRepeating || !didRun)) {
+      // If we didn't do meaningful work, don't schedule using immediate
+      // idle dispatch, since that could lead to a loop until the idle
+      // period ends.
+      Schedule(didRun);
+    }
+
+    return NS_OK;
+  }
+
+  static void
+  TimedOut(nsITimer* aTimer, void* aClosure)
+  {
+    RefPtr<CollectorRunner> runnable = static_cast<CollectorRunner*>(aClosure);
+    runnable->Run();
+  }
+
+  void SetDeadline(mozilla::TimeStamp aDeadline) override
+  {
+    mDeadline = aDeadline;
+  };
+
+  void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override
+  {
+    if (mTimerActive) {
+      return;
+    }
+
+    mTarget = aTarget;
+    if (!mTimer) {
+      mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    } else {
+      mTimer->Cancel();
+    }
+
+    if (mTimer) {
+      mTimer->SetTarget(mTarget);
+      mTimer->InitWithFuncCallback(TimedOut, this, aDelay,
+                                   nsITimer::TYPE_ONE_SHOT);
+      mTimerActive = true;
+    }
+  }
+
+  nsresult Cancel() override
+  {
+    CancelTimer();
+    mTimer = nullptr;
+    mScheduleTimer = nullptr;
+    mCallback = nullptr;
+    return NS_OK;
+  }
+
+  static void
+  ScheduleTimedOut(nsITimer* aTimer, void* aClosure)
+  {
+    RefPtr<CollectorRunner> runnable = static_cast<CollectorRunner*>(aClosure);
+    runnable->Schedule(true);
+  }
+
+  void Schedule(bool aAllowIdleDispatch)
+  {
+    if (!mCallback) {
+      return;
+    }
+
+    if (sShuttingDown) {
+      Cancel();
+      return;
+    }
+
+    mDeadline = TimeStamp();
+    TimeStamp now = TimeStamp::Now();
+    TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
+    if (hint != now) {
+      // RefreshDriver is ticking, let it schedule the idle dispatch.
+      nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay);
+      // Ensure we get called at some point, even if RefreshDriver is stopped.
+      SetTimer(mDelay, mTarget);
+    } else {
+      // RefreshDriver doesn't seem to be running.
+      if (aAllowIdleDispatch) {
+        nsCOMPtr<nsIRunnable> runnable = this;
+        NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay);
+        SetTimer(mDelay, mTarget);
+      } else {
+        if (!mScheduleTimer) {
+          mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+          if (!mScheduleTimer) {
+            return;
+          }
+        } else {
+          mScheduleTimer->Cancel();
+        }
+
+        // We weren't allowed to do idle dispatch immediately, do it after a
+        // short timeout.
+        mScheduleTimer->InitWithFuncCallback(ScheduleTimedOut, this, 16,
+                                             nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
+      }
+    }
+  }
+
+private:
+  explicit CollectorRunner(CollectorRunnerCallback aCallback,
+                           uint32_t aDelay, int64_t aBudget,
+                           bool aRepeating, void* aData)
+    : mCallback(aCallback), mDelay(aDelay)
+    , mBudget(TimeDuration::FromMilliseconds(aBudget))
+    , mRepeating(aRepeating), mTimerActive(false), mData(aData)
+  {
+  }
+
+  ~CollectorRunner()
+  {
+    CancelTimer();
+  }
+
+  void CancelTimer()
+  {
+    nsRefreshDriver::CancelIdleRunnable(this);
+    if (mTimer) {
+      mTimer->Cancel();
+    }
+    if (mScheduleTimer) {
+      mScheduleTimer->Cancel();
+    }
+    mTimerActive = false;
+  }
+
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsITimer> mScheduleTimer;
+  nsCOMPtr<nsIEventTarget> mTarget;
+  CollectorRunnerCallback mCallback;
+  uint32_t mDelay;
+  TimeStamp mDeadline;
+  TimeDuration mBudget;
+  bool mRepeating;
+  bool mTimerActive;
+  void* mData;
+};
+
 static const char*
 ProcessNameForCollectorLog()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default ?
     "default" : "content";
 }
 
 namespace xpc {
@@ -294,20 +472,20 @@ GetCollectionTimeDelta()
   return 0;
 }
 
 static void
 KillTimers()
 {
   nsJSContext::KillGCTimer();
   nsJSContext::KillShrinkingGCTimer();
-  nsJSContext::KillCCTimer();
-  nsJSContext::KillICCTimer();
+  nsJSContext::KillCCRunner();
+  nsJSContext::KillICCRunner();
   nsJSContext::KillFullGCTimer();
-  nsJSContext::KillInterSliceGCTimer();
+  nsJSContext::KillInterSliceGCRunner();
 }
 
 // If we collected a substantial amount of cycles, poke the GC since more objects
 // might be unreachable now.
 static bool
 NeedsGCAfterCC()
 {
   return sCCollectedWaitingForGC > 250 ||
@@ -1446,59 +1624,66 @@ nsJSContext::CycleCollectNow(nsICycleCol
 
   gCCStats.PrepareForCycleCollectionSlice(aExtraForgetSkippableCalls);
   nsCycleCollector_collect(aListener);
   gCCStats.FinishCycleCollectionSlice();
 }
 
 //static
 void
-nsJSContext::RunCycleCollectorSlice()
+nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline)
 {
   if (!NS_IsMainThread()) {
     return;
   }
 
   PROFILER_LABEL("nsJSContext", "RunCycleCollectorSlice",
     js::ProfileEntry::Category::CC);
 
   gCCStats.PrepareForCycleCollectionSlice();
 
   // Decide how long we want to budget for this slice. By default,
   // use an unlimited budget.
   js::SliceBudget budget = js::SliceBudget::unlimited();
 
   if (sIncrementalCC) {
+    int64_t baseBudget = kICCSliceBudget;
+    if (!aDeadline.IsNull()) {
+      baseBudget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
+    }
+
     if (gCCStats.mBeginTime.IsNull()) {
       // If no CC is in progress, use the standard slice time.
-      budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
+      budget = js::SliceBudget(js::TimeBudget(baseBudget));
     } else {
       TimeStamp now = TimeStamp::Now();
 
       // Only run a limited slice if we're within the max running time.
       uint32_t runningTime = TimeBetween(gCCStats.mBeginTime, now);
       if (runningTime < kMaxICCDuration) {
+        const float maxSlice = MainThreadIdlePeriod::GetLongIdlePeriod();
+
         // Try to make up for a delay in running this slice.
-        float sliceDelayMultiplier = TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
-        float delaySliceBudget = kICCSliceBudget * sliceDelayMultiplier;
-
-        // Increase slice budgets up to |maxLaterSlice| as we approach
+        float sliceDelayMultiplier =
+          TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
+        float delaySliceBudget =
+          std::min(baseBudget * sliceDelayMultiplier, maxSlice);
+
+        // Increase slice budgets up to |maxSlice| as we approach
         // half way through the ICC, to avoid large sync CCs.
         float percentToHalfDone = std::min(2.0f * runningTime / kMaxICCDuration, 1.0f);
-        const float maxLaterSlice = 40.0f;
-        float laterSliceBudget = maxLaterSlice * percentToHalfDone;
+        float laterSliceBudget = maxSlice * percentToHalfDone;
 
         budget = js::SliceBudget(js::TimeBudget(std::max({delaySliceBudget,
-                  laterSliceBudget, (float)kICCSliceBudget})));
+                  laterSliceBudget, (float)baseBudget})));
       }
     }
   }
 
-  nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice);
-  sDidPaintAfterPreviousICCSlice = false;
+  nsCycleCollector_collectSlice(budget);
 
   gCCStats.FinishCycleCollectionSlice();
 }
 
 //static
 void
 nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget)
 {
@@ -1524,76 +1709,71 @@ nsJSContext::ClearMaxCCSliceTime()
 }
 
 uint32_t
 nsJSContext::GetMaxCCSliceTimeSinceClear()
 {
   return gCCStats.mMaxSliceTimeSinceClear;
 }
 
-static void
-ICCTimerFired(nsITimer* aTimer, void* aClosure)
+static bool
+ICCRunnerFired(TimeStamp aDeadline, void* aData)
 {
   if (sDidShutdown) {
-    return;
+    return false;
   }
 
   // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
   // to synchronously finish the GC, which is bad.
 
   if (sCCLockedOut) {
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
       sCCLockedOutTime = now;
-      return;
+      return false;
     }
     if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
-      return;
+      return false;
     }
   }
 
-  nsJSContext::RunCycleCollectorSlice();
+  nsJSContext::RunCycleCollectorSlice(aDeadline);
+  return true;
 }
 
 //static
 void
 nsJSContext::BeginCycleCollectionCallback()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime;
   gCCStats.mSuspected = nsCycleCollector_suspectedCount();
 
-  KillCCTimer();
+  KillCCRunner();
 
   gCCStats.RunForgetSkippable();
 
-  MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
+  MOZ_ASSERT(!sICCRunner, "Tried to create a new ICC timer when one already existed.");
 
   // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
   // an incremental collection, and we want to be sure to finish it.
-  CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
-  if (sICCTimer) {
-    sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
-    sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
-                                         kICCIntersliceDelay,
-                                         nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
-                                         "ICCTimerFired");
-  }
+  sICCRunner = CollectorRunner::Create(ICCRunnerFired, kICCIntersliceDelay,
+                                       kIdleICCSliceBudget, true);
 }
 
 static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
 
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsJSContext::KillICCTimer();
+  nsJSContext::KillICCRunner();
 
   // Update timing information for the current slice before we log it, if
   // we previously called PrepareForCycleCollectionSlice(). During shutdown
   // CCs, this won't happen.
   gCCStats.FinishCycleCollectionSlice();
 
   sCCollectedWaitingForGC += aResults.mFreedGCed;
   sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
@@ -1731,36 +1911,47 @@ nsJSContext::EndCycleCollectionCallback(
   sRemovedPurples = 0;
   sForgetSkippableBeforeCC = 0;
   sNeedsFullCC = false;
   sNeedsGCAfterCC = false;
   gCCStats.Clear();
 }
 
 // static
-void
-InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
+bool
+InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData)
 {
-  nsJSContext::KillInterSliceGCTimer();
-  int64_t budget = XRE_IsE10sParentProcess() && nsContentUtils::GetUserIsInteracting() && sActiveIntersliceGCBudget ?
-    sActiveIntersliceGCBudget : NS_INTERSLICE_GC_BUDGET;
-  nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
+  nsJSContext::KillInterSliceGCRunner();
+  MOZ_ASSERT(sActiveIntersliceGCBudget > 0);
+  int64_t budget = sActiveIntersliceGCBudget;
+  if (!aDeadline.IsNull()) {
+    budget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
+  }
+
+  uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
+  nsJSContext::GarbageCollectNow(aData ?
+                                   static_cast<JS::gcreason::Reason>(reason) :
+                                   JS::gcreason::INTER_SLICE_GC,
                                  nsJSContext::IncrementalGC,
                                  nsJSContext::NonShrinkingGC,
                                  budget);
+  return true;
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   nsJSContext::KillGCTimer();
-  uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
-  nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
-                                 nsJSContext::IncrementalGC);
+  // Now start the actual GC after initial timer has fired.
+  sInterSliceGCRunner = CollectorRunner::Create(InterSliceGCRunnerFired,
+                                                NS_INTERSLICE_GC_DELAY,
+                                                sActiveIntersliceGCBudget,
+                                                false,
+                                                aClosure);
 }
 
 // static
 void
 ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   nsJSContext::KillShrinkingGCTimer();
   sIsCompactingOnUserInactive = true;
@@ -1773,81 +1964,88 @@ static bool
 ShouldTriggerCC(uint32_t aSuspected)
 {
   return sNeedsFullCC ||
          aSuspected > NS_CC_PURPLE_LIMIT ||
          (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
           TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
 }
 
-static void
-CCTimerFired(nsITimer *aTimer, void *aClosure)
+static bool
+CCRunnerFired(TimeStamp aDeadline, void* aData)
 {
   if (sDidShutdown) {
-    return;
+    return false;
   }
 
   static uint32_t ccDelay = NS_CC_DELAY;
   if (sCCLockedOut) {
     ccDelay = NS_CC_DELAY / 3;
 
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
-      // Reset sCCTimerFireCount so that we run forgetSkippable
+      // Reset sCCRunnerFireCount so that we run forgetSkippable
       // often enough before CC. Because of reduced ccDelay
       // forgetSkippable will be called just a few times.
       // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
       // forgetSkippable and CycleCollectNow eventually.
-      sCCTimerFireCount = 0;
+      sCCRunnerFireCount = 0;
       sCCLockedOutTime = now;
-      return;
+      return false;
     }
     if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
-      return;
+      return false;
     }
   }
 
-  ++sCCTimerFireCount;
+  ++sCCRunnerFireCount;
+
+  bool didDoWork = false;
 
   // During early timer fires, we only run forgetSkippable. During the first
   // late timer fire, we decide if we are going to have a second and final
   // late timer fire, where we may begin to run the CC. Should run at least one
   // early timer fire to allow cleanup before the CC.
   int32_t numEarlyTimerFires = std::max((int32_t)ccDelay / NS_CC_SKIPPABLE_DELAY - 2, 1);
-  bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires;
+  bool isLateTimerFire = sCCRunnerFireCount > numEarlyTimerFires;
   uint32_t suspected = nsCycleCollector_suspectedCount();
   if (isLateTimerFire && ShouldTriggerCC(suspected)) {
-    if (sCCTimerFireCount == numEarlyTimerFires + 1) {
+    if (sCCRunnerFireCount == numEarlyTimerFires + 1) {
       FireForgetSkippable(suspected, true);
+      didDoWork = true;
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         // Our efforts to avoid a CC have failed, so we return to let the
         // timer fire once more to trigger a CC.
-        return;
+        return didDoWork;
       }
     } else {
       // We are in the final timer fire and still meet the conditions for
       // triggering a CC. Let RunCycleCollectorSlice finish the current IGC, if
       // any because that will allow us to include the GC time in the CC pause.
-      nsJSContext::RunCycleCollectorSlice();
+      nsJSContext::RunCycleCollectorSlice(aDeadline);
+      didDoWork = true;
     }
   } else if (((sPreviousSuspectedCount + 100) <= suspected) ||
              (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS)) {
       // Only do a forget skippable if there are more than a few new objects
       // or we're doing the initial forget skippables.
       FireForgetSkippable(suspected, false);
+      didDoWork = true;
   }
 
   if (isLateTimerFire) {
     ccDelay = NS_CC_DELAY;
 
     // We have either just run the CC or decided we don't want to run the CC
     // next time, so kill the timer.
     sPreviousSuspectedCount = 0;
-    nsJSContext::KillCCTimer();
+    nsJSContext::KillCCRunner();
   }
+
+  return didDoWork;
 }
 
 // static
 uint32_t
 nsJSContext::CleanupsSinceLastGC()
 {
   return sCleanupsSinceLastGC;
 }
@@ -1884,23 +2082,23 @@ ReadyToTriggerExpensiveCollectorTimer()
   bool ready = kPokesBetweenExpensiveCollectorTriggers < ++sExpensiveCollectorPokes;
   if (ready) {
     sExpensiveCollectorPokes = 0;
   }
   return ready;
 }
 
 
-// Check all of the various collector timers and see if they are waiting to fire.
-// For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger
-// the collection occasionally, because they are expensive.  The incremental collector
-// timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so
+// Check all of the various collector timers/runners and see if they are waiting to fire.
+// For the synchronous collector timers/runners, sGCTimer and sCCRunner, we only want to
+// trigger the collection occasionally, because they are expensive.  The incremental collector
+// timers, sInterSliceGCRunner and sICCRunner, are fast and need to be run many times, so
 // always run their corresponding timer.
 
-// This does not check sFullGCTimer, as that's an even more expensive collection we run
+// This does not check sFullGCTimer, as that's a more expensive collection we run
 // on a long timer.
 
 // static
 void
 nsJSContext::RunNextCollectorTimer()
 {
   if (sShuttingDown) {
     return;
@@ -1908,34 +2106,34 @@ nsJSContext::RunNextCollectorTimer()
 
   if (sGCTimer) {
     if (ReadyToTriggerExpensiveCollectorTimer()) {
       GCTimerFired(nullptr, reinterpret_cast<void *>(JS::gcreason::DOM_WINDOW_UTILS));
     }
     return;
   }
 
-  if (sInterSliceGCTimer) {
-    InterSliceGCTimerFired(nullptr, nullptr);
+  if (sInterSliceGCRunner) {
+    InterSliceGCRunnerFired(TimeStamp(), nullptr);
     return;
   }
 
   // Check the CC timers after the GC timers, because the CC timers won't do
   // anything if a GC is in progress.
   MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
 
-  if (sCCTimer) {
+  if (sCCRunner) {
     if (ReadyToTriggerExpensiveCollectorTimer()) {
-      CCTimerFired(nullptr, nullptr);
+      CCRunnerFired(TimeStamp(), nullptr);
     }
     return;
   }
 
-  if (sICCTimer) {
-    ICCTimerFired(nullptr, nullptr);
+  if (sICCRunner) {
+    ICCRunnerFired(TimeStamp(), nullptr);
     return;
   }
 }
 
 // static
 void
 nsJSContext::PokeGC(JS::gcreason::Reason aReason,
                     JSObject* aObj,
@@ -1947,30 +2145,30 @@ nsJSContext::PokeGC(JS::gcreason::Reason
 
   if (aObj) {
     JS::Zone* zone = JS::GetObjectZone(aObj);
     CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
   } else if (aReason != JS::gcreason::CC_WAITING) {
     sNeedsFullGC = true;
   }
 
-  if (sGCTimer || sInterSliceGCTimer) {
+  if (sGCTimer || sInterSliceGCRunner) {
     // There's already a timer for GC'ing, just return
     return;
   }
 
-  if (sCCTimer) {
+  if (sCCRunner) {
     // Make sure CC is called...
     sNeedsFullCC = true;
     // and GC after it.
     sNeedsGCAfterCC = true;
     return;
   }
 
-  if (sICCTimer) {
+  if (sICCRunner) {
     // Make sure GC is called after the current CC completes.
     // No need to set sNeedsFullCC because we are currently running a CC.
     sNeedsGCAfterCC = true;
     return;
   }
 
   CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
 
@@ -1986,16 +2184,17 @@ nsJSContext::PokeGC(JS::gcreason::Reason
                                       reinterpret_cast<void *>(aReason),
                                       aDelay
                                       ? aDelay
                                       : (first
                                          ? NS_FIRST_GC_DELAY
                                          : NS_GC_DELAY),
                                       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                       "GCTimerFired");
+
   first = false;
 }
 
 // static
 void
 nsJSContext::PokeShrinkingGC()
 {
   if (sShrinkingGCTimer || sShuttingDown) {
@@ -2015,34 +2214,29 @@ nsJSContext::PokeShrinkingGC()
                                                nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                                "ShrinkingGCTimerFired");
 }
 
 // static
 void
 nsJSContext::MaybePokeCC()
 {
-  if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
+  if (sCCRunner || sICCRunner || sShuttingDown || !sHasRunGC) {
     return;
   }
 
   if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
-    sCCTimerFireCount = 0;
-    CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
-    if (!sCCTimer) {
-      return;
-    }
+    sCCRunnerFireCount = 0;
+
     // We can kill some objects before running forgetSkippable.
     nsCycleCollector_dispatchDeferredDeletion();
 
-    sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
-    sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
-                                        NS_CC_SKIPPABLE_DELAY,
-                                        nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
-                                        "CCTimerFired");
+    sCCRunner =
+      CollectorRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY,
+                              kForgetSkippableSliceDuration, true);
   }
 }
 
 //static
 void
 nsJSContext::KillGCTimer()
 {
   if (sGCTimer) {
@@ -2056,54 +2250,54 @@ nsJSContext::KillFullGCTimer()
 {
   if (sFullGCTimer) {
     sFullGCTimer->Cancel();
     NS_RELEASE(sFullGCTimer);
   }
 }
 
 void
-nsJSContext::KillInterSliceGCTimer()
+nsJSContext::KillInterSliceGCRunner()
 {
-  if (sInterSliceGCTimer) {
-    sInterSliceGCTimer->Cancel();
-    NS_RELEASE(sInterSliceGCTimer);
+  if (sInterSliceGCRunner) {
+    sInterSliceGCRunner->Cancel();
+    sInterSliceGCRunner = nullptr;
   }
 }
 
 //static
 void
 nsJSContext::KillShrinkingGCTimer()
 {
   if (sShrinkingGCTimer) {
     sShrinkingGCTimer->Cancel();
     NS_RELEASE(sShrinkingGCTimer);
   }
 }
 
 //static
 void
-nsJSContext::KillCCTimer()
+nsJSContext::KillCCRunner()
 {
   sCCLockedOutTime = 0;
-  if (sCCTimer) {
-    sCCTimer->Cancel();
-    NS_RELEASE(sCCTimer);
+  if (sCCRunner) {
+    sCCRunner->Cancel();
+    sCCRunner = nullptr;
   }
 }
 
 //static
 void
-nsJSContext::KillICCTimer()
+nsJSContext::KillICCRunner()
 {
   sCCLockedOutTime = 0;
 
-  if (sICCTimer) {
-    sICCTimer->Cancel();
-    NS_RELEASE(sICCTimer);
+  if (sICCRunner) {
+    sICCRunner->Cancel();
+    sICCRunner = nullptr;
   }
 }
 
 class NotifyGCEndRunnable : public Runnable
 {
   nsString mMessage;
 
 public:
@@ -2165,18 +2359,18 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
           RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
           NS_DispatchToMainThread(notify);
         }
       }
 
       sCCLockedOut = false;
       sIsCompactingOnUserInactive = false;
 
-      // May need to kill the inter-slice GC timer
-      nsJSContext::KillInterSliceGCTimer();
+      // May need to kill the inter-slice GC runner
+      nsJSContext::KillInterSliceGCRunner();
 
       sCCollectedWaitingForGC = 0;
       sCCollectedZonesWaitingForGC = 0;
       sLikelyShortLivingObjectsNeedingGC = 0;
       sCleanupsSinceLastGC = 0;
       sNeedsFullCC = true;
       sHasRunGC = true;
       nsJSContext::MaybePokeCC();
@@ -2207,25 +2401,21 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
     }
 
     case JS::GC_SLICE_BEGIN:
       break;
 
     case JS::GC_SLICE_END:
 
       // Schedule another GC slice if the GC has more work to do.
-      nsJSContext::KillInterSliceGCTimer();
+      nsJSContext::KillInterSliceGCRunner();
       if (!sShuttingDown && !aDesc.isComplete_) {
-        CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
-        sInterSliceGCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
-        sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
-                                                      nullptr,
-                                                      NS_INTERSLICE_GC_DELAY,
-                                                      nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
-                                                      "InterSliceGCTimerFired");
+        sInterSliceGCRunner =
+          CollectorRunner::Create(InterSliceGCRunnerFired, NS_INTERSLICE_GC_DELAY,
+                                  sActiveIntersliceGCBudget, false);
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString prefix, gcstats;
@@ -2269,17 +2459,17 @@ nsJSContext::LikelyShortLivingObjectCrea
 {
   ++sLikelyShortLivingObjectsNeedingGC;
 }
 
 void
 mozilla::dom::StartupJSEnvironment()
 {
   // initialize all our statics, so that we can restart XPCOM
-  sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr;
+  sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
   sCCLockedOut = false;
   sCCLockedOutTime = 0;
   sLastCCEndTime = TimeStamp();
   sHasRunGC = false;
   sPendingLoadCount = 0;
   sLoadingInProgress = false;
   sCCollectedWaitingForGC = 0;
   sCCollectedZonesWaitingForGC = 0;
@@ -2604,62 +2794,16 @@ nsJSContext::EnsureStatics()
   obs->AddObserver(observer, "user-interaction-inactive", false);
   obs->AddObserver(observer, "user-interaction-active", false);
   obs->AddObserver(observer, "quit-application", false);
   obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 
   sIsInitialized = true;
 }
 
-void
-nsJSContext::NotifyDidPaint()
-{
-  sDidPaintAfterPreviousICCSlice = true;
-  if (sICCTimer) {
-    static uint32_t sCount = 0;
-    // 16 here is the common value for refresh driver tick frequency.
-    static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
-    if (++sCount % kTicksPerSliceDelay != 0) {
-      // Don't trigger CC slice all the time after paint, but often still.
-      // The key point is to trigger it right after paint, especially when
-      // we're running RefreshDriver constantly.
-      return;
-    }
-
-    sICCTimer->Cancel();
-    ICCTimerFired(nullptr, nullptr);
-    if (sICCTimer) {
-      sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
-      sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
-                                           kICCIntersliceDelay,
-                                           nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
-                                           "ICCTimerFired");
-    }
-  } else if (sCCTimer) {
-    static uint32_t sCount = 0;
-    static const uint32_t kTicksPerForgetSkippableDelay =
-      NS_CC_SKIPPABLE_DELAY / 16;
-    if (++sCount % kTicksPerForgetSkippableDelay != 0) {
-      // The comment above about triggering CC slice applies to forget skippable
-      // too.
-      return;
-    }
-
-    sCCTimer->Cancel();
-    CCTimerFired(nullptr, nullptr);
-    if (sCCTimer) {
-      sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
-      sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
-                                          NS_CC_SKIPPABLE_DELAY,
-                                          nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
-                                          "CCTimerFired");
-    }
-  }
-}
-
 nsScriptNameSpaceManager*
 mozilla::dom::GetNameSpaceManager()
 {
   if (sDidShutdown)
     return nullptr;
 
   if (!gNameSpaceManager) {
     gNameSpaceManager = new nsScriptNameSpaceManager;
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -10,16 +10,17 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "prtime.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIXPConnect.h"
 #include "nsIArray.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 class nsICycleCollectorListener;
 class nsScriptNameSpaceManager;
 
 namespace JS {
 class AutoValueVector;
@@ -86,17 +87,17 @@ public:
                                 int64_t aSliceMillis = 0);
 
   // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
   // called even if the previous collection was GC.
   static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr,
                               int32_t aExtraForgetSkippableCalls = 0);
 
   // Run a cycle collector slice, using a heuristic to decide how long to run it.
-  static void RunCycleCollectorSlice();
+  static void RunCycleCollectorSlice(mozilla::TimeStamp aDeadline);
 
   // Run a cycle collector slice, using the given work budget.
   static void RunCycleCollectorWorkSlice(int64_t aWorkBudget);
 
   static void BeginCycleCollectionCallback();
   static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);
 
   // Return the longest CC slice time since ClearMaxCCSliceTime() was last called.
@@ -108,35 +109,34 @@ public:
   // The GC should probably run soon, in the zone of object aObj (if given).
   static void PokeGC(JS::gcreason::Reason aReason, JSObject* aObj, int aDelay = 0);
   static void KillGCTimer();
 
   static void PokeShrinkingGC();
   static void KillShrinkingGCTimer();
 
   static void MaybePokeCC();
-  static void KillCCTimer();
-  static void KillICCTimer();
+  static void KillCCRunner();
+  static void KillICCRunner();
   static void KillFullGCTimer();
-  static void KillInterSliceGCTimer();
+  static void KillInterSliceGCRunner();
 
   // Calling LikelyShortLivingObjectCreated() makes a GC more likely.
   static void LikelyShortLivingObjectCreated();
 
   static uint32_t CleanupsSinceLastGC();
 
   nsIScriptGlobalObject* GetCachedGlobalObject()
   {
     // Verify that we have a global so that this
     // does always return a null when GetGlobalObject() is null.
     JSObject* global = GetWindowProxy();
     return global ? mGlobalObjectRef.get() : nullptr;
   }
 
-  static void NotifyDidPaint();
 protected:
   virtual ~nsJSContext();
 
   // Helper to convert xpcom datatypes to jsvals.
   nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
                                    JS::Handle<JSObject*> aScope,
                                    JS::AutoValueVector &aArgsOut);
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -485,16 +485,17 @@ private:
 
 NS_IMPL_ISUPPORTS(BackgroundChildPrimer, nsIIPCBackgroundChildCreateCallback)
 
 ContentChild* ContentChild::sSingleton;
 
 ContentChild::ContentChild()
  : mID(uint64_t(-1))
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
+ , mMainChromeTid(0)
  , mMsaaID(0)
 #endif
  , mCanOverrideProcessName(true)
  , mIsAlive(true)
  , mShuttingDown(false)
 {
   // This process is a content process, so it's clearly running in
   // multiprocess mode!
@@ -1385,16 +1386,17 @@ StartMacOSContentSandbox()
   nsCOMPtr<nsIFile> tempDir;
   nsresult rv = nsDirectoryService::gService->Get(NS_OS_TEMP_DIR,
       NS_GET_IID(nsIFile), getter_AddRefs(tempDir));
   if (NS_FAILED(rv)) {
     MOZ_CRASH("Failed to get NS_OS_TEMP_DIR");
   }
 
   nsAutoCString tempDirPath;
+  tempDir->Normalize();
   rv = tempDir->GetNativePath(tempDirPath);
   if (NS_FAILED(rv)) {
     MOZ_CRASH("Failed to get NS_OS_TEMP_DIR path");
   }
 
   ContentChild* cc = ContentChild::GetSingleton();
 
   nsCOMPtr<nsIFile> profileDir;
@@ -2417,20 +2419,24 @@ ContentChild::RecvFlushMemory(const nsSt
     mozilla::services::GetObserverService();
   if (os) {
     os->NotifyObservers(nullptr, "memory-pressure", reason.get());
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentChild::RecvActivateA11y(const uint32_t& aMsaaID)
+ContentChild::RecvActivateA11y(const uint32_t& aMainChromeTid,
+                               const uint32_t& aMsaaID)
 {
 #ifdef ACCESSIBILITY
 #ifdef XP_WIN
+  MOZ_ASSERT(aMainChromeTid != 0);
+  mMainChromeTid = aMainChromeTid;
+
   MOZ_ASSERT(aMsaaID != 0);
   mMsaaID = aMsaaID;
 #endif // XP_WIN
 
   // Start accessibility in content process if it's running in chrome
   // process.
   GetOrCreateAccService(nsAccessibilityService::eMainProcess);
 #endif // ACCESSIBILITY
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -383,17 +383,18 @@ public:
 
   virtual mozilla::ipc::IPCResult RecvUpdateAppLocales(nsTArray<nsCString>&& aAppLocales) override;
   virtual mozilla::ipc::IPCResult RecvUpdateRequestedLocales(nsTArray<nsCString>&& aRequestedLocales) override;
 
   virtual mozilla::ipc::IPCResult RecvAddPermission(const IPC::Permission& permission) override;
 
   virtual mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason) override;
 
-  virtual mozilla::ipc::IPCResult RecvActivateA11y(const uint32_t& aMsaaID) override;
+  virtual mozilla::ipc::IPCResult RecvActivateA11y(const uint32_t& aMainChromeTid,
+                                                   const uint32_t& aMsaaID) override;
   virtual mozilla::ipc::IPCResult RecvShutdownA11y() override;
 
   virtual mozilla::ipc::IPCResult RecvGarbageCollect() override;
   virtual mozilla::ipc::IPCResult RecvCycleCollect() override;
 
   virtual mozilla::ipc::IPCResult RecvAppInfo(const nsCString& version, const nsCString& buildID,
                                               const nsCString& name, const nsCString& UAName,
                                               const nsCString& ID, const nsCString& vendor) override;
@@ -494,16 +495,18 @@ public:
 
   // Get the directory for IndexedDB files. We query the parent for this and
   // cache the value
   nsString &GetIndexedDBPath();
 
   ContentParentId GetID() const { return mID; }
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
+  uint32_t GetChromeMainThreadId() const { return mMainChromeTid; }
+
   uint32_t GetMsaaID() const { return mMsaaID; }
 #endif
 
   bool IsForBrowser() const { return mIsForBrowser; }
 
   virtual PFileDescriptorSetChild*
   SendPFileDescriptorSetConstructor(const FileDescriptor&) override;
 
@@ -695,16 +698,21 @@ private:
    *
    * We expect our content parent to set this ID immediately after opening a
    * channel to us.
    */
   ContentParentId mID;
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   /**
+   * The thread ID of the main thread in the chrome process.
+   */
+  uint32_t mMainChromeTid;
+
+  /**
    * This is an a11y-specific unique id for the content process that is
    * generated by the chrome process.
    */
   uint32_t mMsaaID;
 #endif
 
   AppInfo mAppInfo;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1359,20 +1359,25 @@ ContentParent::Init()
     obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created", cpId.get());
   }
 
 #ifdef ACCESSIBILITY
   // If accessibility is running in chrome process then start it in content
   // process.
   if (nsIPresShell::IsAccessibilityActive()) {
 #if defined(XP_WIN)
-    Unused <<
-      SendActivateA11y(a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
+#if defined(RELEASE_OR_BETA)
+    // On Windows we currently only enable a11y in the content process
+    // for testing purposes.
+    if (Preferences::GetBool(kForceEnableE10sPref, false))
+#endif
+      Unused << SendActivateA11y(::GetCurrentThreadId(),
+                                 a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
 #else
-    Unused << SendActivateA11y(0);
+    Unused << SendActivateA11y(0, 0);
 #endif
   }
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
   Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid()));
 #endif
 
@@ -2767,20 +2772,25 @@ ContentParent::Observe(nsISupports* aSub
     Unused << SendLastPrivateDocShellDestroyed();
   }
 #ifdef ACCESSIBILITY
   else if (aData && !strcmp(aTopic, "a11y-init-or-shutdown")) {
     if (*aData == '1') {
       // Make sure accessibility is running in content process when
       // accessibility gets initiated in chrome process.
 #if defined(XP_WIN)
-      Unused <<
-        SendActivateA11y(a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
+#if defined(RELEASE_OR_BETA)
+      // On Windows we currently only enable a11y in the content process
+      // for testing purposes.
+      if (Preferences::GetBool(kForceEnableE10sPref, false))
+#endif
+        Unused << SendActivateA11y(::GetCurrentThreadId(),
+                                   a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
 #else
-      Unused << SendActivateA11y(0);
+      Unused << SendActivateA11y(0, 0);
 #endif
     } else {
       // If possible, shut down accessibility in content process when
       // accessibility gets shutdown in chrome process.
       Unused << SendShutdownA11y();
     }
   }
 #endif
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -438,21 +438,23 @@ child:
 
     async FlushMemory(nsString reason);
 
     async GarbageCollect();
     async CycleCollect();
 
     /**
      * Start accessibility engine in content process.
+     * @param aTid is the thread ID of the chrome process main thread. Only used
+     *             on Windows; pass 0 on other platforms.
      * @param aMsaaID is an a11y-specific unique id for the content process
      *                that is generated by the chrome process. Only used on
      *                Windows; pass 0 on other platforms.
      */
-    async ActivateA11y(uint32_t aMsaaID);
+    async ActivateA11y(uint32_t aMainChromeTid, uint32_t aMsaaID);
 
     /**
      * Shutdown accessibility engine in content process (if not in use).
      */
     async ShutdownA11y();
 
     async AppInfo(nsCString version, nsCString buildID, nsCString name, nsCString UAName,
                   nsCString ID, nsCString vendor);
--- a/dom/svg/SVGMarkerElement.cpp
+++ b/dom/svg/SVGMarkerElement.cpp
@@ -298,38 +298,37 @@ SVGMarkerElement::GetPreserveAspectRatio
   return &mPreserveAspectRatio;
 }
 
 //----------------------------------------------------------------------
 // public helpers
 
 gfx::Matrix
 SVGMarkerElement::GetMarkerTransform(float aStrokeWidth,
-                                     float aX, float aY, float aAutoAngle,
-                                     bool aIsStart)
+                                     const nsSVGMark& aMark)
 {
   float scale = mEnumAttributes[MARKERUNITS].GetAnimValue() ==
                      SVG_MARKERUNITS_STROKEWIDTH ? aStrokeWidth : 1.0f;
 
   float angle;
   switch (mOrientType.GetAnimValueInternal()) {
     case SVG_MARKER_ORIENT_AUTO:
-      angle = aAutoAngle;
+      angle = aMark.angle;
       break;
     case SVG_MARKER_ORIENT_AUTO_START_REVERSE:
-      angle = aAutoAngle + (aIsStart ? M_PI : 0.0f);
+      angle = aMark.angle + (aMark.type == nsSVGMark::eStart ? M_PI : 0.0f);
       break;
     default: // SVG_MARKER_ORIENT_ANGLE
       angle = mAngleAttributes[ORIENT].GetAnimValue() * M_PI / 180.0f;
       break;
   }
 
   return gfx::Matrix(cos(angle) * scale,   sin(angle) * scale,
                      -sin(angle) * scale,  cos(angle) * scale,
-                     aX,                    aY);
+                     aMark.x,              aMark.y);
 }
 
 nsSVGViewBoxRect
 SVGMarkerElement::GetViewBoxRect()
 {
   if (mViewBox.HasRect()) {
     return mViewBox.GetAnimValue();
   }
--- a/dom/svg/SVGMarkerElement.h
+++ b/dom/svg/SVGMarkerElement.h
@@ -13,16 +13,17 @@
 #include "nsSVGLength2.h"
 #include "nsSVGViewBox.h"
 #include "SVGAnimatedPreserveAspectRatio.h"
 #include "nsSVGElement.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/SVGAnimatedEnumeration.h"
 
 class nsSVGMarkerFrame;
+struct nsSVGMark;
 
 nsresult NS_NewSVGMarkerElement(nsIContent **aResult,
                                 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 
 namespace mozilla {
 namespace dom {
 
 // Marker Unit Types
@@ -116,19 +117,17 @@ public:
 
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
 
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const override;
 
   // public helpers
-  gfx::Matrix GetMarkerTransform(float aStrokeWidth,
-                                 float aX, float aY, float aAutoAngle,
-                                 bool aIsStart);
+  gfx::Matrix GetMarkerTransform(float aStrokeWidth, const nsSVGMark& aMark);
   nsSVGViewBoxRect GetViewBoxRect();
   gfx::Matrix GetViewBoxTransform();
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   nsSVGOrientType* GetOrientType() { return &mOrientType; }
 
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -889,16 +889,21 @@ public:
   virtual bool IsCaptureDT() const { return false; }
 
   /**
    * Returns a SourceSurface which is a snapshot of the current contents of the DrawTarget.
    * Multiple calls to Snapshot() without any drawing operations in between will
    * normally return the same SourceSurface object.
    */
   virtual already_AddRefed<SourceSurface> Snapshot() = 0;
+
+  // Snapshots the contents and returns an alpha mask
+  // based on the RGB values.
+  virtual already_AddRefed<SourceSurface> IntoLuminanceSource(LuminanceType aLuminanceType,
+                                                              float aOpacity);
   virtual IntSize GetSize() = 0;
 
   /**
    * If possible returns the bits to this DrawTarget for direct manipulation. While
    * the bits is locked any modifications to this DrawTarget is forbidden.
    * Release takes the original data pointer for safety.
    */
   virtual bool LockBits(uint8_t** aData, IntSize* aSize,
--- a/gfx/2d/DrawTarget.cpp
+++ b/gfx/2d/DrawTarget.cpp
@@ -4,19 +4,172 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "2D.h"
 #include "Logging.h"
 #include "PathHelpers.h"
 
 #include "DrawTargetCapture.h"
 
+#ifdef BUILD_ARM_NEON
+#include "mozilla/arm.h"
+#endif
+
 namespace mozilla {
 namespace gfx {
 
+/**
+ * Byte offsets of channels in a native packed gfxColor or cairo image surface.
+ */
+#ifdef IS_BIG_ENDIAN
+#define GFX_ARGB32_OFFSET_A 0
+#define GFX_ARGB32_OFFSET_R 1
+#define GFX_ARGB32_OFFSET_G 2
+#define GFX_ARGB32_OFFSET_B 3
+#else
+#define GFX_ARGB32_OFFSET_A 3
+#define GFX_ARGB32_OFFSET_R 2
+#define GFX_ARGB32_OFFSET_G 1
+#define GFX_ARGB32_OFFSET_B 0
+#endif
+
+// c = n / 255
+// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
+static const uint8_t gsRGBToLinearRGBMap[256] = {
+  0,   0,   0,   0,   0,   0,   0,   1,
+  1,   1,   1,   1,   1,   1,   1,   1,
+  1,   1,   2,   2,   2,   2,   2,   2,
+  2,   2,   3,   3,   3,   3,   3,   3,
+  4,   4,   4,   4,   4,   5,   5,   5,
+  5,   6,   6,   6,   6,   7,   7,   7,
+  8,   8,   8,   8,   9,   9,   9,  10,
+ 10,  10,  11,  11,  12,  12,  12,  13,
+ 13,  13,  14,  14,  15,  15,  16,  16,
+ 17,  17,  17,  18,  18,  19,  19,  20,
+ 20,  21,  22,  22,  23,  23,  24,  24,
+ 25,  25,  26,  27,  27,  28,  29,  29,
+ 30,  30,  31,  32,  32,  33,  34,  35,
+ 35,  36,  37,  37,  38,  39,  40,  41,
+ 41,  42,  43,  44,  45,  45,  46,  47,
+ 48,  49,  50,  51,  51,  52,  53,  54,
+ 55,  56,  57,  58,  59,  60,  61,  62,
+ 63,  64,  65,  66,  67,  68,  69,  70,
+ 71,  72,  73,  74,  76,  77,  78,  79,
+ 80,  81,  82,  84,  85,  86,  87,  88,
+ 90,  91,  92,  93,  95,  96,  97,  99,
+100, 101, 103, 104, 105, 107, 108, 109,
+111, 112, 114, 115, 116, 118, 119, 121,
+122, 124, 125, 127, 128, 130, 131, 133,
+134, 136, 138, 139, 141, 142, 144, 146,
+147, 149, 151, 152, 154, 156, 157, 159,
+161, 163, 164, 166, 168, 170, 171, 173,
+175, 177, 179, 181, 183, 184, 186, 188,
+190, 192, 194, 196, 198, 200, 202, 204,
+206, 208, 210, 212, 214, 216, 218, 220,
+222, 224, 226, 229, 231, 233, 235, 237,
+239, 242, 244, 246, 248, 250, 253, 255
+};
+
+static void
+ComputesRGBLuminanceMask(const uint8_t *aSourceData,
+                         int32_t aSourceStride,
+                         uint8_t *aDestData,
+                         int32_t aDestStride,
+                         const IntSize &aSize,
+                         float aOpacity)
+{
+  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+  const uint8_t *sourcePixel = aSourceData;
+  int32_t destOffset = aDestStride - aSize.width;
+  uint8_t *destPixel = aDestData;
+
+  for (int32_t y = 0; y < aSize.height; y++) {
+    for (int32_t x = 0; x < aSize.width; x++) {
+      uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+      if (a) {
+        *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
+                      greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+                      blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+      } else {
+        *destPixel = 0;
+      }
+      sourcePixel += 4;
+      destPixel++;
+    }
+    sourcePixel += sourceOffset;
+    destPixel += destOffset;
+  }
+}
+
+static void
+ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
+                              int32_t aSourceStride,
+                              uint8_t *aDestData,
+                              int32_t aDestStride,
+                              const IntSize &aSize,
+                              float aOpacity)
+{
+  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+  const uint8_t *sourcePixel = aSourceData;
+  int32_t destOffset = aDestStride - aSize.width;
+  uint8_t *destPixel = aDestData;
+
+  for (int32_t y = 0; y < aSize.height; y++) {
+    for (int32_t x = 0; x < aSize.width; x++) {
+      uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+      // unpremultiply
+      if (a) {
+        if (a == 255) {
+          /* sRGB -> linearRGB -> intensity */
+          *destPixel =
+            static_cast<uint8_t>
+                       ((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
+                         redFactor +
+                         gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
+                         greenFactor +
+                         gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
+                         blueFactor) >> 8);
+        } else {
+          uint8_t tempPixel[4];
+          tempPixel[GFX_ARGB32_OFFSET_B] =
+            (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
+          tempPixel[GFX_ARGB32_OFFSET_G] =
+            (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
+          tempPixel[GFX_ARGB32_OFFSET_R] =
+            (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
+
+          /* sRGB -> linearRGB -> intensity */
+          *destPixel =
+            static_cast<uint8_t>
+                       (((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
+                          redFactor +
+                          gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
+                          greenFactor +
+                          gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
+                          blueFactor) >> 8) * (a / 255.0f));
+        }
+      } else {
+        *destPixel = 0;
+      }
+      sourcePixel += 4;
+      destPixel++;
+    }
+    sourcePixel += sourceOffset;
+    destPixel += destOffset;
+  }
+}
+
 already_AddRefed<DrawTargetCapture>
 DrawTarget::CreateCaptureDT(const IntSize& aSize)
 {
   RefPtr<DrawTargetCaptureImpl> dt = new DrawTargetCaptureImpl();
 
   if (!dt->Init(aSize, this)) {
     gfxWarning() << "Failed to initialize Capture DrawTarget!";
     return nullptr;
@@ -59,11 +212,56 @@ DrawTarget::StrokeGlyphs(ScaledFont* aFo
                          const StrokeOptions& aStrokeOptions,
                          const DrawOptions& aOptions,
                          const GlyphRenderingOptions* aRenderingOptions)
 {
   RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this);
   Stroke(path, aPattern, aStrokeOptions, aOptions);
 }
 
+already_AddRefed<SourceSurface>
+DrawTarget::IntoLuminanceSource(LuminanceType aMaskType, float aOpacity)
+{
+  RefPtr<SourceSurface> surface = Snapshot();
+  IntSize size = surface->GetSize();
+
+  RefPtr<DataSourceSurface> maskSurface = surface->GetDataSurface();
+  DataSourceSurface::MappedSurface map;
+  if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+    return nullptr;
+  }
+
+  // Create alpha channel mask for output
+  RefPtr<DataSourceSurface> destMaskSurface =
+    Factory::CreateDataSourceSurface(size, SurfaceFormat::A8);
+  if (!destMaskSurface) {
+    return nullptr;
+  }
+  DataSourceSurface::MappedSurface destMap;
+  if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+    return nullptr;
+  }
+
+  switch (aMaskType) {
+    case LuminanceType::LUMINANCE:
+    {
+      ComputesRGBLuminanceMask(map.mData, map.mStride,
+                               destMap.mData, destMap.mStride,
+                               size, aOpacity);
+      break;
+    }
+    case LuminanceType::LINEARRGB:
+    {
+      ComputeLinearRGBLuminanceMask(map.mData, map.mStride,
+                                    destMap.mData, destMap.mStride,
+                                    size, aOpacity);
+      break;
+    }
+  }
+
+  maskSurface->Unmap();
+  destMaskSurface->Unmap();
+
+  return destMaskSurface.forget();
+}
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -94,16 +94,48 @@ DrawTargetD2D1::Snapshot()
   Flush();
 
   mSnapshot = new SourceSurfaceD2D1(mBitmap, mDC, mFormat, mSize, this);
 
   RefPtr<SourceSurface> snapshot(mSnapshot);
   return snapshot.forget();
 }
 
+void
+DrawTargetD2D1::EnsureLuminanceEffect()
+{
+  if (mLuminanceEffect.get()) {
+    return;
+  }
+
+  HRESULT hr = mDC->CreateEffect(CLSID_D2D1LuminanceToAlpha,
+                                 getter_AddRefs(mLuminanceEffect));
+  if (FAILED(hr)) {
+    gfxWarning() << "Failed to create luminance effect. Code: " << hexa(hr);
+  }
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetD2D1::IntoLuminanceSource(LuminanceType aLuminanceType, float aOpacity)
+{
+  //return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
+  if (aLuminanceType != LuminanceType::LUMINANCE) {
+    return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
+  }
+
+  // Create the luminance effect
+  EnsureLuminanceEffect();
+  mLuminanceEffect->SetInput(0, mBitmap);
+
+  RefPtr<ID2D1Image> luminanceOutput;
+  mLuminanceEffect->GetOutput(getter_AddRefs(luminanceOutput));
+
+ return MakeAndAddRef<SourceSurfaceD2D1>(luminanceOutput, mDC, SurfaceFormat::A8, mSize);
+}
+
 // Command lists are kept around by device contexts until EndDraw is called,
 // this can cause issues with memory usage (see bug 1238328). EndDraw/BeginDraw
 // are expensive though, especially relatively when little work is done, so
 // we try to reduce the amount of times we execute these purges.
 static const uint32_t kPushedLayersBeforePurge = 25;
 
 void
 DrawTargetD2D1::Flush()
@@ -343,30 +375,58 @@ DrawTargetD2D1::MaskSurface(const Patter
 
   if (!image) {
     gfxWarning() << "Failed to get image for surface.";
     return;
   }
 
   PrepareForDrawing(aOptions.mCompositionOp, aSource);
 
+  IntSize size = IntSize::Truncate(aMask->GetSize().width, aMask->GetSize().height);
+  Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height));
+
+  HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
+  if (!bitmap || FAILED(hr)) {
+    // D2D says if we have an actual ID2D1Image and not a bitmap underlying the object,
+    // we can't query for a bitmap. Instead, Push/PopLayer
+    gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces. Falling back to push/pop layer";
+
+    RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions.mAlpha);
+    RefPtr<ID2D1ImageBrush> maskBrush;
+    hr = mDC->CreateImageBrush(image,
+                               D2D1::ImageBrushProperties(D2D1::RectF(0, 0, size.width, size.height)),
+                               D2D1::BrushProperties(1.0f, D2D1::IdentityMatrix()),
+                               getter_AddRefs(maskBrush));
+    MOZ_ASSERT(SUCCEEDED(hr));
+
+    mDC->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), nullptr,
+                                          D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+                                          D2D1::IdentityMatrix(),
+                                          1.0f, maskBrush, D2D1_LAYER_OPTIONS1_NONE),
+                  nullptr);
+
+    mDC->FillRectangle(D2DRect(dest), source);
+    mDC->PopLayer();
+
+    FinalizeDrawing(aOptions.mCompositionOp, aSource);
+    return;
+  } else {
+    // If this is a data source surface, we might have created a partial bitmap
+    // for this surface and only uploaded part of the mask. In that case,
+    // we have to fixup our sizes here.
+    size.width = bitmap->GetSize().width;
+    size.height = bitmap->GetSize().height;
+    dest.width = size.width;
+    dest.height = size.height;
+  }
+
   // FillOpacityMask only works if the antialias mode is MODE_ALIASED
   mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
 
-  image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
-  if (!bitmap) {
-    gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces.";
-    return;
-  }
-
-  IntSize size = IntSize::Truncate(bitmap->GetSize().width, bitmap->GetSize().height);
-
   Rect maskRect = Rect(0.f, 0.f, Float(size.width), Float(size.height));
-
-  Rect dest = Rect(aOffset.x, aOffset.y, Float(size.width), Float(size.height));
   RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions.mAlpha);
   mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, D2DRect(dest), D2DRect(maskRect));
 
   mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
 
   FinalizeDrawing(aOptions.mCompositionOp, aSource);
 }
 
@@ -1866,17 +1926,16 @@ DrawTargetD2D1::CreateBrushForPattern(co
 }
 
 already_AddRefed<ID2D1Image>
 DrawTargetD2D1::GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform,
                                    ExtendMode aExtendMode, const IntRect* aSourceRect,
                                    bool aUserSpace)
 {
   RefPtr<ID2D1Image> image;
-
   switch (aSurface->GetType()) {
   case SurfaceType::D2D1_1_IMAGE:
     {
       SourceSurfaceD2D1 *surf = static_cast<SourceSurfaceD2D1*>(aSurface);
       image = surf->GetImage();
       AddDependencyOnSource(surf);
     }
     break;
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -31,16 +31,18 @@ class DrawTargetD2D1 : public DrawTarget
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1, override)
   DrawTargetD2D1();
   virtual ~DrawTargetD2D1();
 
   virtual DrawTargetType GetType() const override { return DrawTargetType::HARDWARE_RASTER; }
   virtual BackendType GetBackendType() const override { return BackendType::DIRECT2D1_1; }
   virtual already_AddRefed<SourceSurface> Snapshot() override;
+  virtual already_AddRefed<SourceSurface> IntoLuminanceSource(LuminanceType aLuminanceType,
+                                                              float aOpacity) override;
   virtual IntSize GetSize() override { return mSize; }
 
   virtual void Flush() override;
   virtual void DrawSurface(SourceSurface *aSurface,
                            const Rect &aDest,
                            const Rect &aSource,
                            const DrawSurfaceOptions &aSurfOptions,
                            const DrawOptions &aOptions) override;
@@ -290,14 +292,18 @@ private:
   // If we resolve the current command list before this happens
   // we can avoid the subsequent hang. (See bug 1293586)
   bool mDidComplexBlendWithListInList;
 
   static ID2D1Factory1 *mFactory;
   static IDWriteFactory *mDWriteFactory;
   // This value is uesed to verify if the DrawTarget is created by a stale device.
   uint32_t mDeviceSeq;
+
+  // List of effects we use
+  void EnsureLuminanceEffect();
+  RefPtr<ID2D1Effect> mLuminanceEffect;
 };
 
 }
 }
 
 #endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -273,16 +273,22 @@ enum class CapStyle : int8_t {
   SQUARE
 };
 
 enum class SamplingBounds : int8_t {
   UNBOUNDED,
   BOUNDED
 };
 
+// Moz2d version for SVG mask types
+enum class LuminanceType : int8_t {
+  LUMINANCE,
+  LINEARRGB,
+};
+
 /* Color is stored in non-premultiplied form */
 struct Color
 {
 public:
   Color()
     : r(0.0f), g(0.0f), b(0.0f), a(0.0f)
   {}
   Color(Float aR, Float aG, Float aB, Float aA)
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -14,17 +14,19 @@
 #include "gfxPlatform.h"
 #include "imgFrame.h"
 #include "nsColor.h"
 #include "nsIInputStream.h"
 #include "nsMemory.h"
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
+
 #include "RasterImage.h"
+#include "SurfaceCache.h"
 #include "SurfacePipeFactory.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Telemetry.h"
 
 using namespace mozilla::gfx;
 
 using std::min;
 
@@ -564,16 +566,23 @@ nsPNGDecoder::info_callback(png_structp 
   // Always decode to 24-bit RGB or 32-bit RGBA
   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_type, &compression_type, &filter_type);
 
   const IntRect frameRect(0, 0, width, height);
 
   // Post our size to the superclass
   decoder->PostSize(frameRect.width, frameRect.height);
+
+  if (width >
+    SurfaceCache::MaximumCapacity()/(bit_depth > 8 ? 16:8)) {
+    // libpng needs space to allocate two row buffers
+    png_error(decoder->mPNG, "Image is too wide");
+  }
+
   if (decoder->HasError()) {
     // Setting the size led to an error.
     png_error(decoder->mPNG, "Sizing error");
   }
 
   if (color_type == PNG_COLOR_TYPE_PALETTE) {
     png_set_expand(png_ptr);
   }
@@ -730,16 +739,21 @@ nsPNGDecoder::info_callback(png_structp 
     if (!decoder->mCMSLine) {
       png_error(decoder->mPNG, "malloc of mCMSLine failed");
     }
   }
 
   if (interlace_type == PNG_INTERLACE_ADAM7) {
     if (frameRect.height < INT32_MAX / (frameRect.width * int32_t(channels))) {
       const size_t bufferSize = channels * frameRect.width * frameRect.height;
+
+      if (bufferSize > SurfaceCache::MaximumCapacity()) {
+        png_error(decoder->mPNG, "Insufficient memory to deinterlace image");
+      }
+
       decoder->interlacebuf = static_cast<uint8_t*>(malloc(bufferSize));
     }
     if (!decoder->interlacebuf) {
       png_error(decoder->mPNG, "malloc of interlacebuf failed");
     }
   }
 }
 
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -1,30 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #define INITGUID
 
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/Move.h"
 #include "mozilla/mscom/DispatchForwarder.h"
 #include "mozilla/mscom/Interceptor.h"
 #include "mozilla/mscom/InterceptorLog.h"
 #include "mozilla/mscom/MainThreadInvoker.h"
+#include "mozilla/mscom/Objref.h"
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/mscom/Utils.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsRefPtrHashtable.h"
 #include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
 
 namespace mozilla {
 namespace mscom {
 
 class LiveSet final
 {
 public:
   LiveSet()
@@ -137,16 +140,17 @@ Interceptor::~Interceptor()
 HRESULT
 Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
                                 CLSID* aHandlerClsid)
 {
   if (aDestContextPtr || !aHandlerClsid ||
       aDestContext == MSHCTX_DIFFERENTMACHINE) {
     return E_INVALIDARG;
   }
+
   MOZ_ASSERT(mEventSink);
   return mEventSink->GetHandler(WrapNotNull(aHandlerClsid));
 }
 
 HRESULT
 Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
                                void* pvDestContext, DWORD mshlflags,
                                CLSID* pCid)
@@ -172,22 +176,70 @@ Interceptor::GetMarshalSizeMax(REFIID ri
   return hr;
 }
 
 HRESULT
 Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
                               DWORD dwDestContext, void* pvDestContext,
                               DWORD mshlflags)
 {
-  HRESULT hr = mStdMarshal->MarshalInterface(pStm, riid, pv, dwDestContext,
-                                             pvDestContext, mshlflags);
+  HRESULT hr;
+
+#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+  // Save the current stream position
+  LARGE_INTEGER seekTo;
+  seekTo.QuadPart = 0;
+
+  ULARGE_INTEGER objrefPos;
+
+  hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+
+  hr = mStdMarshal->MarshalInterface(pStm, riid, pv, dwDestContext,
+                                     pvDestContext, mshlflags);
   if (FAILED(hr)) {
     return hr;
   }
 
+#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+  if (XRE_IsContentProcess()) {
+    const DWORD chromeMainTid =
+      dom::ContentChild::GetSingleton()->GetChromeMainThreadId();
+
+    /*
+     * CoGetCallerTID() gives us the caller's thread ID when that thread resides
+     * in a single-threaded apartment. Since our chrome main thread does live
+     * inside an STA, we will therefore be able to check whether the caller TID
+     * equals our chrome main thread TID. This enables us to distinguish
+     * between our chrome thread vs other out-of-process callers.
+     */
+    DWORD callerTid;
+    if (::CoGetCallerTID(&callerTid) == S_FALSE && callerTid != chromeMainTid) {
+      // The caller isn't our chrome process, so do not provide a handler.
+      // First, seek back to the stream position that we prevously saved.
+      seekTo.QuadPart = objrefPos.QuadPart;
+      hr = pStm->Seek(seekTo, STREAM_SEEK_SET, nullptr);
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      // Now strip out the handler.
+      if (!StripHandlerFromOBJREF(WrapNotNull(pStm))) {
+        return E_FAIL;
+      }
+
+      return S_OK;
+    }
+  }
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+
   return mEventSink->WriteHandlerPayload(WrapNotNull(pStm));
 }
 
 HRESULT
 Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid,
                                 void** ppv)
 {
   return mStdMarshal->UnmarshalInterface(pStm, riid, ppv);
--- a/ipc/mscom/MainThreadHandoff.cpp
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -448,16 +448,17 @@ MainThreadHandoff::SetInterceptor(IWeakR
 }
 
 HRESULT
 MainThreadHandoff::GetHandler(NotNull<CLSID*> aHandlerClsid)
 {
   if (!mHandlerProvider) {
     return E_NOTIMPL;
   }
+
   return mHandlerProvider->GetHandler(aHandlerClsid);
 }
 
 HRESULT
 MainThreadHandoff::GetHandlerPayloadSize(NotNull<DWORD*> aOutPayloadSize)
 {
   if (!mHandlerProvider) {
     return E_NOTIMPL;
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Objref.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/mscom/Objref.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/UniquePtr.h"
+
+#include <guiddef.h>
+#include <objidl.h>
+
+namespace {
+
+#pragma pack(push, 1)
+
+typedef uint64_t OID;
+typedef uint64_t OXID;
+typedef GUID IPID;
+
+struct STDOBJREF
+{
+  uint32_t      mFlags;
+  uint32_t      mPublicRefs;
+  OXID          mOxid;
+  OID           mOid;
+  IPID          mIpid;
+};
+
+enum STDOBJREF_FLAGS
+{
+  SORF_PING = 0,
+  SORF_NOPING = 0x1000
+};
+
+struct DUALSTRINGARRAY
+{
+  static size_t SizeFromNumEntries(const uint16_t aNumEntries)
+  {
+    return sizeof(mNumEntries) + sizeof(mSecurityOffset) +
+           aNumEntries * sizeof(uint16_t);
+  }
+
+  size_t SizeOf() const
+  {
+    return SizeFromNumEntries(mNumEntries);
+  }
+
+  uint16_t  mNumEntries;
+  uint16_t  mSecurityOffset;
+  uint16_t  mStringArray[1]; // Length is mNumEntries
+};
+
+struct OBJREF_STANDARD
+{
+  size_t SizeOf() const
+  {
+    return sizeof(mStd) + mResAddr.SizeOf();
+  }
+
+  STDOBJREF       mStd;
+  DUALSTRINGARRAY mResAddr;
+};
+
+struct OBJREF_HANDLER
+{
+  size_t SizeOf() const
+  {
+    return sizeof(mStd) + sizeof(mClsid) + mResAddr.SizeOf();
+  }
+
+  STDOBJREF       mStd;
+  CLSID           mClsid;
+  DUALSTRINGARRAY mResAddr;
+};
+
+enum OBJREF_FLAGS
+{
+  OBJREF_TYPE_STANDARD = 0x00000001UL,
+  OBJREF_TYPE_HANDLER = 0x00000002UL,
+  OBJREF_TYPE_CUSTOM = 0x00000004UL,
+  OBJREF_TYPE_EXTENDED = 0x00000008UL,
+};
+
+struct OBJREF
+{
+  size_t SizeOf() const
+  {
+    size_t size = sizeof(mSignature) + sizeof(mFlags) + sizeof(mIid);
+
+    switch (mFlags) {
+      case OBJREF_TYPE_STANDARD:
+        size += mObjRefStd.SizeOf();
+        break;
+      case OBJREF_TYPE_HANDLER:
+        size += mObjRefHandler.SizeOf();
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unsupported OBJREF type");
+        return 0;
+    }
+
+    return size;
+  }
+
+  uint32_t  mSignature;
+  uint32_t  mFlags;
+  IID       mIid;
+  union {
+    OBJREF_STANDARD mObjRefStd;
+    OBJREF_HANDLER  mObjRefHandler;
+    // There are others but we're not supporting them here
+  };
+};
+
+enum OBJREF_SIGNATURES
+{
+  OBJREF_SIGNATURE = 0x574F454DUL
+};
+
+#pragma pack(pop)
+
+struct ByteArrayDeleter
+{
+  void operator()(void* aPtr)
+  {
+    delete[] reinterpret_cast<uint8_t*>(aPtr);
+  }
+};
+
+template <typename T>
+using VarStructUniquePtr = mozilla::UniquePtr<T, ByteArrayDeleter>;
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+bool
+StripHandlerFromOBJREF(NotNull<IStream*> aStream)
+{
+  // Get current stream position
+  LARGE_INTEGER seekTo;
+  seekTo.QuadPart = 0;
+
+  ULARGE_INTEGER objrefPos;
+
+  HRESULT hr = aStream->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  ULONG bytesRead;
+
+  uint32_t signature;
+  hr = aStream->Read(&signature, sizeof(signature), &bytesRead);
+  if (FAILED(hr) || bytesRead != sizeof(signature) ||
+      signature != OBJREF_SIGNATURE) {
+    return false;
+  }
+
+  uint32_t type;
+  hr = aStream->Read(&type, sizeof(type), &bytesRead);
+  if (FAILED(hr) || bytesRead != sizeof(type) ||
+      type != OBJREF_TYPE_HANDLER) {
+    return false;
+  }
+
+  IID iid;
+  hr = aStream->Read(&iid, sizeof(iid), &bytesRead);
+  if (FAILED(hr) || bytesRead != sizeof(iid) || !IsValidGUID(iid)) {
+    return false;
+  }
+
+  // Seek past fixed-size STDOBJREF and CLSID
+  seekTo.QuadPart = sizeof(STDOBJREF) + sizeof(CLSID);
+  hr = aStream->Seek(seekTo, STREAM_SEEK_CUR, nullptr);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  uint16_t numEntries;
+  hr = aStream->Read(&numEntries, sizeof(numEntries), &bytesRead);
+  if (FAILED(hr) || bytesRead != sizeof(numEntries)) {
+    return false;
+  }
+
+  // We'll try to use a stack buffer if resAddrSize <= kMinDualStringArraySize
+  const uint32_t kMinDualStringArraySize = 12;
+  uint16_t staticResAddrBuf[kMinDualStringArraySize / sizeof(uint16_t)];
+
+  size_t resAddrSize = DUALSTRINGARRAY::SizeFromNumEntries(numEntries);
+
+  DUALSTRINGARRAY* resAddr;
+  VarStructUniquePtr<DUALSTRINGARRAY> dynamicResAddrBuf;
+
+  if (resAddrSize <= kMinDualStringArraySize) {
+    resAddr = reinterpret_cast<DUALSTRINGARRAY*>(staticResAddrBuf);
+  } else {
+    dynamicResAddrBuf.reset(
+        reinterpret_cast<DUALSTRINGARRAY*>(new uint8_t[resAddrSize]));
+    resAddr = dynamicResAddrBuf.get();
+  }
+
+  resAddr->mNumEntries = numEntries;
+
+  // Because we've already read numEntries
+  ULONG bytesToRead = resAddrSize - sizeof(numEntries);
+
+  hr = aStream->Read(&resAddr->mSecurityOffset, bytesToRead, &bytesRead);
+  if (FAILED(hr) || bytesRead != bytesToRead) {
+    return false;
+  }
+
+  // Signature doesn't change so we'll seek past that
+  seekTo.QuadPart = objrefPos.QuadPart + sizeof(signature);
+  hr = aStream->Seek(seekTo, STREAM_SEEK_SET, nullptr);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  ULONG bytesWritten;
+
+  uint32_t newType = OBJREF_TYPE_STANDARD;
+  hr = aStream->Write(&newType, sizeof(newType), &bytesWritten);
+  if (FAILED(hr) || bytesWritten != sizeof(newType)) {
+    return false;
+  }
+
+  // Skip past IID and STDOBJREF since those don't change
+  seekTo.QuadPart = sizeof(IID) + sizeof(STDOBJREF);
+  hr = aStream->Seek(seekTo, STREAM_SEEK_CUR, nullptr);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  hr = aStream->Write(resAddr, resAddrSize, &bytesWritten);
+  if (FAILED(hr) || bytesWritten != resAddrSize) {
+    return false;
+  }
+
+  return true;
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Objref.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_mscom_Objref_h
+#define mozilla_mscom_Objref_h
+
+#include "mozilla/NotNull.h"
+
+struct IStream;
+
+namespace mozilla {
+namespace mscom {
+
+/**
+ * Given a buffer containing a serialized proxy to an interface with a handler,
+ * this function strips out the handler and converts it to a standard one.
+ * @param aStream IStream whose pointer is positioned at the beginning of the
+ *                OBJREF to be stripped. There should be nothing else written
+ *                to the stream past the current OBJREF.
+ * @return true if the handler was successfully stripped, otherwise false.
+ */
+bool
+StripHandlerFromOBJREF(NotNull<IStream*> aStream);
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Objref_h
+
--- a/ipc/mscom/Utils.cpp
+++ b/ipc/mscom/Utils.cpp
@@ -1,18 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifdef ACCESSIBILITY
+#if defined(ACCESSIBILITY)
 #include "mozilla/mscom/Registration.h"
+#if defined(MOZILLA_INTERNAL_API)
 #include "nsTArray.h"
 #endif
+#endif
 
 #include "mozilla/mscom/Utils.h"
 #include "mozilla/RefPtr.h"
 
 #include <objbase.h>
 #include <objidl.h>
 
 namespace mozilla {
@@ -134,16 +136,18 @@ IsVtableIndexFromParentInterface(REFIID 
   }
 
   bool result = IsVtableIndexFromParentInterface(typeAttr, aVtableIndex);
 
   typeInfo->ReleaseTypeAttr(typeAttr);
   return result;
 }
 
+#if defined(MOZILLA_INTERNAL_API)
+
 bool
 IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom,
                                   unsigned long aVtableIndexHint)
 {
   if (aInterface == aFrom) {
     return true;
   }
 
@@ -218,12 +222,14 @@ IsInterfaceEqualToOrInheritedFrom(REFIID
 
       typeInfos.AppendElement(Move(nextTypeInfo));
     }
   }
 
   return false;
 }
 
+#endif // defined(MOZILLA_INTERNAL_API)
+
 #endif // defined(ACCESSIBILITY)
 
 } // namespace mscom
 } // namespace mozilla
--- a/ipc/mscom/Utils.h
+++ b/ipc/mscom/Utils.h
@@ -6,19 +6,17 @@
 
 #ifndef mozilla_mscom_Utils_h
 #define mozilla_mscom_Utils_h
 
 #if defined(MOZILLA_INTERNAL_API)
 #include "nsString.h"
 #endif // defined(MOZILLA_INTERNAL_API)
 
-#if defined(ACCESSIBILITY)
 #include <guiddef.h>
-#endif // defined(ACCESSIBILITY)
 
 struct IUnknown;
 
 namespace mozilla {
 namespace mscom {
 
 bool IsCurrentThreadMTA();
 bool IsProxy(IUnknown* aUnknown);
@@ -26,17 +24,19 @@ bool IsValidGUID(REFGUID aCheckGuid);
 
 #if defined(MOZILLA_INTERNAL_API)
 void GUIDToString(REFGUID aGuid, nsAString& aOutString);
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #if defined(ACCESSIBILITY)
 bool IsVtableIndexFromParentInterface(REFIID aInterface,
                                       unsigned long aVtableIndex);
+#if defined(MOZILLA_INTERNAL_API)
 bool IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom,
                                        unsigned long aVtableIndexHint);
+#endif // defined(MOZILLA_INTERNAL_API)
 #endif // defined(ACCESSIBILITY)
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_Utils_h
 
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -7,25 +7,27 @@
 EXPORTS.mozilla.mscom += [
     'Aggregation.h',
     'AgileReference.h',
     'AsyncInvoker.h',
     'COMApartmentRegion.h',
     'COMPtrHolder.h',
     'EnsureMTA.h',
     'MainThreadRuntime.h',
+    'Objref.h',
     'ProxyStream.h',
     'Ptr.h',
     'Utils.h',
 ]
 
 UNIFIED_SOURCES += [
     'AgileReference.cpp',
     'EnsureMTA.cpp',
     'MainThreadRuntime.cpp',
+    'Objref.cpp',
     'ProxyStream.cpp',
     'Utils.cpp',
 ]
 
 if CONFIG['MOZ_CRASHREPORTER']:
     UNIFIED_SOURCES += [
         'InterfaceRegistrationAnnotator.cpp',
     ]
@@ -65,14 +67,16 @@ if CONFIG['ACCESSIBILITY']:
         'StructStream.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/xpcom/base',
     '/xpcom/build',
 ]
 
+DEFINES['MOZ_MSCOM_REMARSHAL_NO_HANDLER'] = True
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Disability Access APIs")
--- a/ipc/mscom/oop/Handler.cpp
+++ b/ipc/mscom/oop/Handler.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Handler.h"
 #include "Module.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/mscom/Objref.h"
 #include "nsWindowsHelpers.h"
 
 #include <objbase.h>
 #include <shlwapi.h>
 #include <string.h>
 
 /* WARNING! The code in this file may be loaded into the address spaces of other
    processes! It MUST NOT link against xul.dll or other Gecko binaries! Only
@@ -43,17 +44,17 @@ Handler::Handler(IUnknown* aOuter, HRESU
     return;
   }
 
   *aResult = mInnerUnk->QueryInterface(IID_IMarshal, (void**)&mUnmarshal);
   if (FAILED(*aResult)) {
     return;
   }
 
-  // mInnerMarshal is a weak ref
+  // mUnmarshal is a weak ref
   mUnmarshal->Release();
 }
 
 HRESULT
 Handler::InternalQueryInterface(REFIID riid, void** ppv)
 {
   if (!ppv) {
     return E_INVALIDARG;
@@ -164,39 +165,70 @@ Handler::MarshalInterface(IStream* pStm,
                           DWORD mshlflags)
 {
   // We do not necessarily want to use the pv that COM is giving us; we may want
   // to marshal a different proxy that is more appropriate to what we're
   // wrapping...
   RefPtr<IUnknown> unkToMarshal;
   HRESULT hr;
 
+#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+  LARGE_INTEGER seekTo;
+  seekTo.QuadPart = 0;
+
+  ULARGE_INTEGER objrefPos;
+
+  // Save the current position as it points to the location where the OBJREF
+  // will be written.
+  hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // When marshaling without a handler, we just use the riid as passed in.
+  REFIID marshalAs = riid;
+#else
   REFIID marshalAs = MarshalAs(riid);
-  if (marshalAs == riid) {
-    unkToMarshal = static_cast<IUnknown*>(pv);
-  } else {
-    hr = mInnerUnk->QueryInterface(marshalAs, getter_AddRefs(unkToMarshal));
-    if (FAILED(hr)) {
-      return hr;
-    }
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+
+  hr = mInnerUnk->QueryInterface(marshalAs, getter_AddRefs(unkToMarshal));
+  if (FAILED(hr)) {
+    return hr;
   }
 
   hr = mUnmarshal->MarshalInterface(pStm, marshalAs, unkToMarshal.get(),
                                     dwDestContext, pvDestContext, mshlflags);
   if (FAILED(hr)) {
     return hr;
   }
 
+#if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
+  // Now the OBJREF has been written, so seek back to its beginning (the
+  // position that we saved earlier).
+  seekTo.QuadPart = objrefPos.QuadPart;
+  hr = pStm->Seek(seekTo, STREAM_SEEK_SET, nullptr);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // Now strip out the handler.
+  if (!StripHandlerFromOBJREF(WrapNotNull(pStm))) {
+    return E_FAIL;
+  }
+
+  return S_OK;
+#else
   if (!HasPayload()) {
     return S_OK;
   }
 
   // Unfortunately when COM re-marshals a proxy that prevouisly had a payload,
   // we must re-serialize it.
   return WriteHandlerPayload(pStm, marshalAs);
+#endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
 }
 
 HRESULT
 Handler::UnmarshalInterface(IStream* pStm, REFIID riid, void** ppv)
 {
   REFIID unmarshalAs = MarshalAs(riid);
   HRESULT hr = mUnmarshal->UnmarshalInterface(pStm, unmarshalAs, ppv);
   if (FAILED(hr)) {
--- a/ipc/mscom/oop/moz.build
+++ b/ipc/mscom/oop/moz.build
@@ -3,34 +3,37 @@
 # 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/.
 
 Library('mscom_oop')
 
 SOURCES += [
     '../ActivationContext.cpp',
+    '../Objref.cpp',
     '../Registration.cpp',
     '../StructStream.cpp',
+    '../Utils.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'Handler.cpp',
     'Module.cpp',
 ]
 
 OS_LIBS += [
     'ole32',
     'oleaut32',
     'shlwapi',
 ]
 
 LIBRARY_DEFINES['UNICODE'] = True
 LIBRARY_DEFINES['_UNICODE'] = True
 LIBRARY_DEFINES['MOZ_NO_MOZALLOC'] = True
+LIBRARY_DEFINES['MOZ_MSCOM_REMARSHAL_NO_HANDLER'] = True
 
 DISABLE_STL_WRAPPING = True
 NO_EXPAND_LIBS = True
 FORCE_STATIC_LIB = True
 
 # This DLL may be loaded into other processes, so we need static libs for
 # Windows 7 and Windows 8.
 USE_STATIC_LIBS = True
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -336,30 +336,32 @@ class JSFunction : public js::NativeObje
         return atom_;
     }
 
     void setCompileTimeName(JSAtom* atom) {
         MOZ_ASSERT(!atom_);
         MOZ_ASSERT(atom);
         MOZ_ASSERT(!hasGuessedAtom());
         MOZ_ASSERT(!isClassConstructor());
+        MOZ_ASSERT(js::AtomIsMarked(zone(), atom));
         atom_ = atom;
         flags_ |= HAS_COMPILE_TIME_NAME;
     }
     JSAtom* compileTimeName() const {
         MOZ_ASSERT(hasCompileTimeName());
         MOZ_ASSERT(atom_);
         return atom_;
     }
 
     void setGuessedAtom(JSAtom* atom) {
         MOZ_ASSERT(!atom_);
         MOZ_ASSERT(atom);
         MOZ_ASSERT(!hasCompileTimeName());
         MOZ_ASSERT(!hasGuessedAtom());
+        MOZ_ASSERT(js::AtomIsMarked(zone(), atom));
         atom_ = atom;
         flags_ |= HAS_GUESSED_ATOM;
     }
     void clearGuessedAtom() {
         MOZ_ASSERT(hasGuessedAtom());
         MOZ_ASSERT(atom_);
         atom_ = nullptr;
         flags_ &= ~HAS_GUESSED_ATOM;
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -261,48 +261,52 @@ DeleteScopeData(ConcreteScopeData* data)
     // Some scope Data classes have GCManagedDeletePolicy because then contain
     // GCPtrs. Dispose of them in the appropriate way.
     JS::DeletePolicy<ConcreteScopeData>()(data);
 }
 
 template <typename ConcreteScope, XDRMode mode>
 /* static */ bool
 Scope::XDRSizedBindingNames(XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
-                            MutableHandle<typename ConcreteScope::Data*> data)
+                            MutableHandle<typename ConcreteScope::Data*> data, uint32_t* lengthOut)
 {
     MOZ_ASSERT(!data);
+    MOZ_ASSERT(!*lengthOut);
 
     JSContext* cx = xdr->cx();
 
     uint32_t length;
     if (mode == XDR_ENCODE)
         length = scope->data().length;
     if (!xdr->codeUint32(&length))
         return false;
 
     if (mode == XDR_ENCODE) {
         data.set(&scope->data());
     } else {
-        data.set(NewEmptyScopeData<ConcreteScope>(cx, length).release());
-        if (!data)
-            return false;
-        data->length = length;
+        if (length) {
+            data.set(NewEmptyScopeData<ConcreteScope>(cx, length).release());
+            if (!data)
+                return false;
+            data->length = length;
+        }
     }
 
     for (uint32_t i = 0; i < length; i++) {
         if (!XDRBindingName(xdr, &data->names[i])) {
             if (mode == XDR_DECODE) {
                 DeleteScopeData(data.get());
                 data.set(nullptr);
             }
 
             return false;
         }
     }
 
+    *lengthOut = length;
     return true;
 }
 
 /* static */ Scope*
 Scope::create(JSContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape)
 {
     Scope* scope = Allocate<Scope>(cx);
     if (scope)
@@ -554,17 +558,18 @@ LexicalScope::getEmptyExtensibleEnvironm
 template <XDRMode mode>
 /* static */ bool
 LexicalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
                   MutableHandleScope scope)
 {
     JSContext* cx = xdr->cx();
 
     Rooted<Data*> data(cx);
-    if (!XDRSizedBindingNames<LexicalScope>(xdr, scope.as<LexicalScope>(), &data))
+    uint32_t length = 0;
+    if (!XDRSizedBindingNames<LexicalScope>(xdr, scope.as<LexicalScope>(), &data, &length))
         return false;
 
     {
         auto deleteOnLeave = MakeScopeExit([&data]() {
             if (mode == XDR_DECODE)
                 DeleteScopeData(data.get());
         });
 
@@ -578,16 +583,19 @@ LexicalScope::XDR(XDRState<mode>* xdr, S
         if (!xdr->codeUint32(&data->constStart))
             return false;
         if (!xdr->codeUint32(&firstFrameSlot))
             return false;
         if (!xdr->codeUint32(&nextFrameSlot))
             return false;
 
         if (mode == XDR_DECODE) {
+            if (!data)
+                return false;
+
             scope.set(create(cx, kind, data, firstFrameSlot, enclosing));
             if (!scope)
                 return false;
 
             // nextFrameSlot is used only for this correctness check.
             MOZ_ASSERT(nextFrameSlot == scope->as<LexicalScope>().data().nextFrameSlot);
         }
     }
@@ -738,59 +746,66 @@ FunctionScope::clone(JSContext* cx, Hand
 
 template <XDRMode mode>
 /* static */ bool
 FunctionScope::XDR(XDRState<mode>* xdr, HandleFunction fun, HandleScope enclosing,
                    MutableHandleScope scope)
 {
     JSContext* cx = xdr->cx();
     Rooted<Data*> data(cx);
-    if (!XDRSizedBindingNames<FunctionScope>(xdr, scope.as<FunctionScope>(), &data))
+    uint32_t length = 0;
+    if (!XDRSizedBindingNames<FunctionScope>(xdr, scope.as<FunctionScope>(), &data, &length))
         return false;
 
     {
         auto deleteOnLeave = MakeScopeExit([&data]() {
             if (mode == XDR_DECODE)
                 DeleteScopeData(data.get());
         });
 
         uint8_t needsEnvironment;
         uint8_t hasParameterExprs;
+        uint16_t nonPositionalFormalStart;
+        uint16_t varStart;
         uint32_t nextFrameSlot;
         if (mode == XDR_ENCODE) {
             needsEnvironment = scope->hasEnvironment();
             hasParameterExprs = data->hasParameterExprs;
+            nonPositionalFormalStart = data->nonPositionalFormalStart;
+            varStart = data->varStart;
             nextFrameSlot = data->nextFrameSlot;
         }
         if (!xdr->codeUint8(&needsEnvironment))
             return false;
         if (!xdr->codeUint8(&hasParameterExprs))
             return false;
-        if (!xdr->codeUint16(&data->nonPositionalFormalStart))
+        if (!xdr->codeUint16(&nonPositionalFormalStart))
             return false;
-        if (!xdr->codeUint16(&data->varStart))
+        if (!xdr->codeUint16(&varStart))
             return false;
         if (!xdr->codeUint32(&nextFrameSlot))
             return false;
 
         if (mode == XDR_DECODE) {
-            if (!data->length) {
-                MOZ_ASSERT(!data->nonPositionalFormalStart);
-                MOZ_ASSERT(!data->varStart);
-                MOZ_ASSERT(!data->nextFrameSlot);
-                DeleteScopeData(data.get());
-                data = nullptr;
+            MOZ_ASSERT(!length == !data);
+            if (length) {
+                data->nonPositionalFormalStart = nonPositionalFormalStart;
+                data->varStart = varStart;
+            } else {
+                MOZ_ASSERT(!nonPositionalFormalStart);
+                MOZ_ASSERT(!varStart);
             }
 
             scope.set(create(cx, data, hasParameterExprs, needsEnvironment, fun, enclosing));
             if (!scope)
                 return false;
 
             // nextFrameSlot is used only for this correctness check.
             MOZ_ASSERT(nextFrameSlot == scope->as<FunctionScope>().data().nextFrameSlot);
+            MOZ_ASSERT_IF(!data, !nextFrameSlot);
         }
     }
 
     return true;
 }
 
 template
 /* static */ bool
@@ -866,17 +881,18 @@ VarScope::firstFrameSlot() const
 
 template <XDRMode mode>
 /* static */ bool
 VarScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
               MutableHandleScope scope)
 {
     JSContext* cx = xdr->cx();
     Rooted<Data*> data(cx);
-    if (!XDRSizedBindingNames<VarScope>(xdr, scope.as<VarScope>(), &data))
+    uint32_t length = 0;
+    if (!XDRSizedBindingNames<VarScope>(xdr, scope.as<VarScope>(), &data, &length))
         return false;
 
     {
         auto deleteOnLeave = MakeScopeExit([&data]() {
             if (mode == XDR_DECODE)
                 DeleteScopeData(data.get());
         });
 
@@ -891,22 +907,17 @@ VarScope::XDR(XDRState<mode>* xdr, Scope
         if (!xdr->codeUint8(&needsEnvironment))
             return false;
         if (!xdr->codeUint32(&firstFrameSlot))
             return false;
         if (!xdr->codeUint32(&nextFrameSlot))
             return false;
 
         if (mode == XDR_DECODE) {
-            if (!data->length) {
-                MOZ_ASSERT(!data->nextFrameSlot);
-                DeleteScopeData(data.get());
-                data = nullptr;
-            }
-
+            MOZ_ASSERT(!length == !data);
             scope.set(create(cx, kind, data, firstFrameSlot, needsEnvironment, enclosing));
             if (!scope)
                 return false;
 
             // nextFrameSlot is used only for this correctness check.
             MOZ_ASSERT(nextFrameSlot == scope->as<VarScope>().data().nextFrameSlot);
         }
     }
@@ -969,39 +980,51 @@ GlobalScope::clone(JSContext* cx, Handle
 template <XDRMode mode>
 /* static */ bool
 GlobalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, MutableHandleScope scope)
 {
     MOZ_ASSERT((mode == XDR_DECODE) == !scope);
 
     JSContext* cx = xdr->cx();
     Rooted<Data*> data(cx);
-    if (!XDRSizedBindingNames<GlobalScope>(xdr, scope.as<GlobalScope>(), &data))
+    uint32_t length = 0;
+    if (!XDRSizedBindingNames<GlobalScope>(xdr, scope.as<GlobalScope>(), &data, &length))
         return false;
 
     {
         auto deleteOnLeave = MakeScopeExit([&data]() {
             if (mode == XDR_DECODE)
                 DeleteScopeData(data.get());
         });
 
-        if (!xdr->codeUint32(&data->varStart))
+        uint32_t varStart;
+        uint32_t letStart;
+        uint32_t constStart;
+        if (mode == XDR_ENCODE) {
+            varStart = data->varStart;
+            letStart = data->letStart;
+            constStart = data->constStart;
+        }
+        if (!xdr->codeUint32(&varStart))
             return false;
-        if (!xdr->codeUint32(&data->letStart))
+        if (!xdr->codeUint32(&letStart))
             return false;
-        if (!xdr->codeUint32(&data->constStart))
+        if (!xdr->codeUint32(&constStart))
             return false;
 
         if (mode == XDR_DECODE) {
-            if (!data->length) {
-                MOZ_ASSERT(!data->varStart);
-                MOZ_ASSERT(!data->letStart);
-                MOZ_ASSERT(!data->constStart);
-                DeleteScopeData(data.get());
-                data = nullptr;
+            MOZ_ASSERT(!length == !data);
+            if (length) {
+                data->varStart = varStart;
+                data->letStart = letStart;
+                data->constStart = constStart;
+            } else {
+                MOZ_ASSERT(!varStart);
+                MOZ_ASSERT(!letStart);
+                MOZ_ASSERT(!constStart);
             }
 
             scope.set(create(cx, kind, data));
             if (!scope)
                 return false;
         }
     }
 
@@ -1101,26 +1124,22 @@ EvalScope::XDR(XDRState<mode>* xdr, Scop
     Rooted<Data*> data(cx);
 
     {
         auto deleteOnLeave = MakeScopeExit([&data]() {
             if (mode == XDR_DECODE)
                 DeleteScopeData(data.get());
         });
 
-        if (!XDRSizedBindingNames<EvalScope>(xdr, scope.as<EvalScope>(), &data))
+        uint32_t length = 0;
+        if (!XDRSizedBindingNames<EvalScope>(xdr, scope.as<EvalScope>(), &data, &length))
             return false;
 
         if (mode == XDR_DECODE) {
-            if (!data->length) {
-                MOZ_ASSERT(!data->nextFrameSlot);
-                DeleteScopeData(data.get());
-                data = nullptr;
-            }
-
+            MOZ_ASSERT(!length == !data);
             scope.set(create(cx, kind, data, enclosing));
             if (!scope)
                 return false;
         }
     }
 
     return true;
 }
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -243,17 +243,18 @@ class Scope : public js::gc::TenuredCell
                          HandleShape envShape);
 
     template <typename T, typename D>
     static Scope* create(JSContext* cx, ScopeKind kind, HandleScope enclosing,
                          HandleShape envShape, mozilla::UniquePtr<T, D> data);
 
     template <typename ConcreteScope, XDRMode mode>
     static bool XDRSizedBindingNames(XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
-                                     MutableHandle<typename ConcreteScope::Data*> data);
+                                     MutableHandle<typename ConcreteScope::Data*> data,
+                                     uint32_t* lengthOut);
 
     Shape* maybeCloneEnvironmentShape(JSContext* cx);
 
     template <typename T, typename D>
     void initData(mozilla::UniquePtr<T, D> data) {
         MOZ_ASSERT(!data_);
         data_ = reinterpret_cast<uintptr_t>(data.release());
     }
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1692,16 +1692,56 @@ nsRefreshDriver::RunFrameRequestCallback
       }
       for (auto& callback : docCallbacks.mCallbacks) {
         callback->Call(timeStamp);
       }
     }
   }
 }
 
+struct RunnableWithDelay
+{
+  nsCOMPtr<nsIRunnable> mRunnable;
+  uint32_t mDelay;
+};
+
+static AutoTArray<RunnableWithDelay, 8>* sPendingIdleRunnables = nullptr;
+
+void
+nsRefreshDriver::DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
+                                               uint32_t aDelay)
+{
+  if (!sPendingIdleRunnables) {
+    sPendingIdleRunnables = new AutoTArray<RunnableWithDelay, 8>();
+  }
+
+  RunnableWithDelay rwd = {aRunnable, aDelay};
+  sPendingIdleRunnables->AppendElement(rwd);
+}
+
+void
+nsRefreshDriver::CancelIdleRunnable(nsIRunnable* aRunnable)
+{
+  if (!sPendingIdleRunnables) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < sPendingIdleRunnables->Length(); ++i) {
+    if ((*sPendingIdleRunnables)[i].mRunnable == aRunnable) {
+      sPendingIdleRunnables->RemoveElementAt(i);
+      break;
+    }
+  }
+
+  if (sPendingIdleRunnables->IsEmpty()) {
+    delete sPendingIdleRunnables;
+    sPendingIdleRunnables = nullptr;
+  }
+}
+
 void
 nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
 {
   NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
                   "Shouldn't have a JSContext on the stack");
 
   if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
     NS_ERROR("Refresh driver should not run during plugin call!");
@@ -1954,17 +1994,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
       }
     }
 
     for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
       imagesToRefresh[i]->RequestRefresh(aNowTime);
     }
   }
 
-  bool notifyGC = false;
+  bool dispatchRunnablesAfterTick = false;
   if (mViewManagerFlushIsPending) {
     RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
 
     nsTArray<nsDocShell*> profilingDocShells;
     GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
     for (nsDocShell* docShell : profilingDocShells) {
       // For the sake of the profile timeline's simplicity, this is flagged as
       // paint even if it includes creating display lists
@@ -1993,17 +2033,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
 #endif
 
     for (nsDocShell* docShell : profilingDocShells) {
       MOZ_ASSERT(timelines);
       MOZ_ASSERT(timelines->HasConsumer(docShell));
       timelines->AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::END);
     }
 
-    notifyGC = true;
+    dispatchRunnablesAfterTick = true;
   }
 
 #ifndef ANDROID  /* bug 1142079 */
   mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
 #endif
 
   nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
   while (iter.HasMore()) {
@@ -2012,20 +2052,24 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
   }
 
   NS_ASSERTION(mInRefresh, "Still in refresh");
 
   if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
     ScheduleViewManagerFlush();
   }
 
-  if (notifyGC && nsContentUtils::XPConnect()) {
-    GeckoProfilerTracingRAII tracer("Paint", "NotifyDidPaint");
-    nsContentUtils::XPConnect()->NotifyDidPaint();
-    nsJSContext::NotifyDidPaint();
+  if (dispatchRunnablesAfterTick && sPendingIdleRunnables) {
+    AutoTArray<RunnableWithDelay, 8>* runnables = sPendingIdleRunnables;
+    sPendingIdleRunnables = nullptr;
+    for (uint32_t i = 0; i < runnables->Length(); ++i) {
+      NS_IdleDispatchToCurrentThread((*runnables)[i].mRunnable.forget(),
+                                     (*runnables)[i].mDelay);
+    }
+    delete runnables;
   }
 }
 
 void
 nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
                                        mozilla::TimeStamp aDesired)
 {
   for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -28,16 +28,17 @@
 #include "mozilla/layers/TransactionIdAllocator.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
 class nsIDOMEvent;
 class nsINode;
+class nsIRunnable;
 
 namespace mozilla {
 class RefreshDriverTimer;
 namespace layout {
 class VsyncChild;
 } // namespace layout
 } // namespace mozilla
 
@@ -328,16 +329,20 @@ public:
    * expected deadline. If the next expected deadline is later than
    * the default value, the default value is returned.
    *
    * If we're animating and we have skipped paints a time in the past
    * is returned.
    */
   static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
 
+  static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
+                                            uint32_t aDelay);
+  static void CancelIdleRunnable(nsIRunnable* aRunnable);
+
   bool SkippedPaints() const
   {
     return mSkippedPaints;
   }
 
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
--- a/layout/reftests/w3c-css/submitted/masking/reftest.list
+++ b/layout/reftests/w3c-css/submitted/masking/reftest.list
@@ -9,31 +9,31 @@ fuzzy-if(skiaContent||winWidget,1,5000) 
 fuzzy-if(skiaContent||winWidget,64,5200) == mask-composite-2b.html mask-composite-2-ref.html
 == mask-composite-2c.html mask-composite-2-ref.html
 
 # mask-mode test cases
 fuzzy-if(skiaContent,1,30000) == mask-mode-a.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-b.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-c.html mask-mode-ref.html
 fuzzy-if(skiaContent,1,30000) == mask-mode-d.html mask-mode-ref.html
-fuzzy-if(skiaContent,1,30000) == mask-mode-to-mask-type.html mask-mode-to-mask-type-ref.html
+fuzzy-if(skiaContent,1,30000) fuzzy-if(d2d,1,30000) == mask-mode-to-mask-type.html mask-mode-to-mask-type-ref.html
 
 # mask-image test cases
 == mask-image-1a.html mask-image-1-ref.html
 == mask-image-1b.html mask-image-1-ref.html
 == mask-image-1c.html mask-image-1-ref.html
 == mask-image-1d.html mask-image-1-ref.html
 fuzzy-if(skiaContent||winWidget,1,20000) == mask-image-2.html mask-image-2-ref.html
 == mask-image-3a.html mask-image-3-ref.html
 == mask-image-3b.html mask-image-3-ref.html
 fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3c.html mask-image-3-ref.html
 fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3d.html mask-image-3-ref.html
 == mask-image-3e.html mask-image-3-ref.html
-fuzzy-if(skiaContent||winWidget,50,85) fuzzy-if(webrender,1,126) == mask-image-3f.html mask-image-3-ref.html
-fuzzy-if(skiaContent||winWidget,50,85) fuzzy-if(webrender,1,126) == mask-image-3g.html mask-image-3-ref.html
+fuzzy-if(skiaContent||winWidget,50,85) fuzzy-if(webrender,1,126) fuzzy-if(d2d,255,1) == mask-image-3f.html mask-image-3-ref.html
+fuzzy-if(skiaContent||winWidget,50,85) fuzzy-if(webrender,1,126) fuzzy-if(d2d,255,1) == mask-image-3g.html mask-image-3-ref.html
 pref(layout.css.clip-path-shapes.enabled,true) fuzzy-if(winWidget,1,3) fuzzy-if(skiaContent,2,12) == mask-image-3h.html mask-image-3-ref.html
 fuzzy-if(skiaContent,71,203) == mask-image-3i.html mask-image-3-ref.html
 == mask-image-4a.html blank.html
 == mask-image-4b.html blank.html
 == mask-image-5.html mask-image-5-ref.html
 == mask-image-6.html mask-image-6-ref.html
 
 # mask-clip test cases
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -663,22 +663,22 @@ SVGGeometryFrame::GetBBoxContribution(co
         properties.GetMarkerStartFrame(),
         properties.GetMarkerMidFrame(),
         properties.GetMarkerEndFrame(),
       };
       static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
                     "Number of Marker frames should be equal to eTypeCount");
 
       for (uint32_t i = 0; i < num; i++) {
-        nsSVGMark& mark = marks[i];
+        const nsSVGMark& mark = marks[i];
         nsSVGMarkerFrame* frame = markerFrames[mark.type];
         if (frame) {
           SVGBBox mbbox =
             frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
-                                           &marks[i], strokeWidth);
+                                           mark, strokeWidth);
           MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
           bbox.UnionEdges(mbbox);
         }
       }
     }
   }
 
   return bbox;
@@ -891,20 +891,20 @@ SVGGeometryFrame::PaintMarkers(gfxContex
           properties.GetMarkerStartFrame(),
           properties.GetMarkerMidFrame(),
           properties.GetMarkerEndFrame(),
         };
         static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
                       "Number of Marker frames should be equal to eTypeCount");
 
         for (uint32_t i = 0; i < num; i++) {
-          nsSVGMark& mark = marks[i];
+          const nsSVGMark& mark = marks[i];
           nsSVGMarkerFrame* frame = markerFrames[mark.type];
           if (frame) {
-            frame->PaintMark(aContext, aTransform, this, &mark, strokeWidth,
+            frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
                              aImgParams);
           }
         }
       }
     }
   }
 }
 
--- a/layout/svg/moz.build
+++ b/layout/svg/moz.build
@@ -61,20 +61,16 @@ UNIFIED_SOURCES += [
     'SVGFELeafFrame.cpp',
     'SVGFEUnstyledLeafFrame.cpp',
     'SVGGeometryFrame.cpp',
     'SVGImageContext.cpp',
     'SVGTextFrame.cpp',
     'SVGViewFrame.cpp',
 ]
 
-if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']:
-    SOURCES += ['nsSVGMaskFrameNEON.cpp']
-    SOURCES['nsSVGMaskFrameNEON.cpp'].flags += CONFIG['NEON_FLAGS']
-
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '../../widget',
     '../base',
     '../generic',
     '../painting',
     '../style',
     '../xul',
--- a/layout/svg/nsSVGMarkerFrame.cpp
+++ b/layout/svg/nsSVGMarkerFrame.cpp
@@ -75,37 +75,35 @@ nsSVGMarkerFrame::GetCanvasTM()
   }
 
   SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
   
   mInUse2 = true;
   gfxMatrix markedTM = mMarkedFrame->GetCanvasTM();
   mInUse2 = false;
 
-  Matrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY,
-                                                mAutoAngle, mIsStart);
   Matrix viewBoxTM = content->GetViewBoxTransform();
 
-  return ThebesMatrix(viewBoxTM * markerTM) * markedTM;
+  return ThebesMatrix(viewBoxTM * mMarkerTM) * markedTM;
 }
 
 static nsIFrame*
 GetAnonymousChildFrame(nsIFrame* aFrame)
 {
   nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
   MOZ_ASSERT(kid && kid->IsSVGMarkerAnonChildFrame(),
              "expected to find anonymous child of marker frame");
   return kid;
 }
 
 void
 nsSVGMarkerFrame::PaintMark(gfxContext& aContext,
                             const gfxMatrix& aToMarkedFrameUserSpace,
-                            SVGGeometryFrame *aMarkedFrame,
-                            nsSVGMark *aMark, float aStrokeWidth,
+                            SVGGeometryFrame* aMarkedFrame,
+                            const nsSVGMark& aMark, float aStrokeWidth,
                             imgDrawingParams& aImgParams)
 {
   // If the flag is set when we get here, it means this marker frame
   // has already been used painting the current mark, and the document
   // has a marker reference loop.
   if (mInUse) {
     return;
   }
@@ -119,28 +117,21 @@ nsSVGMarkerFrame::PaintMark(gfxContext& 
 
   const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect();
 
   if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
     // We must disable rendering if the viewBox width or height are zero.
     return;
   }
 
-  mStrokeWidth = aStrokeWidth;
-  mX = aMark->x;
-  mY = aMark->y;
-  mAutoAngle = aMark->angle;
-  mIsStart = aMark->type == nsSVGMark::eStart;
-
   Matrix viewBoxTM = marker->GetViewBoxTransform();
 
-  Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY,
-                                               mAutoAngle, mIsStart);
+  mMarkerTM = marker->GetMarkerTransform(aStrokeWidth, aMark);
 
-  gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) *
+  gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(mMarkerTM) *
                      aToMarkedFrameUserSpace;
 
   if (StyleDisplay()->IsScrollableOverflow()) {
     aContext.Save();
     gfxRect clipRect =
       nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y,
                                       viewBox.width, viewBox.height);
     nsSVGUtils::SetClipRect(&aContext, markTM, clipRect);
@@ -153,20 +144,20 @@ nsSVGMarkerFrame::PaintMark(gfxContext& 
   SVGFrame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
   nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM, aImgParams);
 
   if (StyleDisplay()->IsScrollableOverflow())
     aContext.Restore();
 }
 
 SVGBBox
-nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
+nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix& aToBBoxUserspace,
                                           uint32_t aFlags,
-                                          SVGGeometryFrame *aMarkedFrame,
-                                          const nsSVGMark *aMark,
+                                          SVGGeometryFrame* aMarkedFrame,
+                                          const nsSVGMark& aMark,
                                           float aStrokeWidth)
 {
   SVGBBox bbox;
 
   // If the flag is set when we get here, it means this marker frame
   // has already been used in calculating the current mark bbox, and
   // the document has a marker reference loop.
   if (mInUse)
@@ -180,27 +171,20 @@ nsSVGMarkerFrame::GetMarkBBoxContributio
   }
 
   const nsSVGViewBoxRect viewBox = content->GetViewBoxRect();
 
   if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
     return bbox;
   }
 
-  mStrokeWidth = aStrokeWidth;
-  mX = aMark->x;
-  mY = aMark->y;
-  mAutoAngle = aMark->angle;
-  mIsStart = aMark->type == nsSVGMark::eStart;
-
-  Matrix markerTM =
-    content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart);
+  mMarkerTM = content->GetMarkerTransform(aStrokeWidth, aMark);
   Matrix viewBoxTM = content->GetViewBoxTransform();
 
-  Matrix tm = viewBoxTM * markerTM * aToBBoxUserspace;
+  Matrix tm = viewBoxTM * mMarkerTM * aToBBoxUserspace;
 
   nsSVGDisplayableFrame* child = do_QueryFrame(GetAnonymousChildFrame(this));
   // When we're being called to obtain the invalidation area, we need to
   // pass down all the flags so that stroke is included. However, once DOM
   // getBBox() accepts flags, maybe we should strip some of those here?
 
   // We need to include zero width/height vertical/horizontal lines, so we have
   // to use UnionEdges.
--- a/layout/svg/nsSVGMarkerFrame.h
+++ b/layout/svg/nsSVGMarkerFrame.h
@@ -74,37 +74,36 @@ public:
                PrincipalChildList().FirstChild()->IsSVGMarkerAnonChildFrame(),
                "Where is our anonymous child?");
     return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
   }
 
   // nsSVGMarkerFrame methods:
   void PaintMark(gfxContext& aContext,
                  const gfxMatrix& aToMarkedFrameUserSpace,
-                 mozilla::SVGGeometryFrame *aMarkedFrame,
-                 nsSVGMark *aMark,
+                 mozilla::SVGGeometryFrame* aMarkedFrame,
+                 const nsSVGMark& aMark,
                  float aStrokeWidth,
                  imgDrawingParams& aImgParams);
 
-  SVGBBox GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
+  SVGBBox GetMarkBBoxContribution(const Matrix& aToBBoxUserspace,
                                   uint32_t aFlags,
-                                  mozilla::SVGGeometryFrame *aMarkedFrame,
-                                  const nsSVGMark *aMark,
+                                  mozilla::SVGGeometryFrame* aMarkedFrame,
+                                  const nsSVGMark& aMark,
                                   float aStrokeWidth);
 
   // Update the style on our anonymous box child.
   void DoUpdateStyleOfOwnedAnonBoxes(mozilla::ServoStyleSet& aStyleSet,
                                      nsStyleChangeList& aChangeList,
                                      nsChangeHint aHintForThisFrame) override;
 
 private:
   // stuff needed for callback
   mozilla::SVGGeometryFrame *mMarkedFrame;
-  float mStrokeWidth, mX, mY, mAutoAngle;
-  bool mIsStart;  // whether the callback is for a marker-start marker
+  Matrix mMarkerTM;
 
   // nsSVGContainerFrame methods:
   virtual gfxMatrix GetCanvasTM() override;
 
   // A helper class to allow us to paint markers safely. The helper
   // automatically sets and clears the mInUse flag on the marker frame (to
   // prevent nasty reference loops) as well as the reference to the marked
   // frame and its coordinate context. It's easy to mess this up
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -9,172 +9,38 @@
 // Keep others in (case-insensitive) order:
 #include "AutoReferenceChainGuard.h"
 #include "gfx2DGlue.h"
 #include "gfxContext.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "nsSVGEffects.h"
 #include "mozilla/dom/SVGMaskElement.h"
-#ifdef BUILD_ARM_NEON
-#include "mozilla/arm.h"
-#include "nsSVGMaskFrameNEON.h"
-#endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
-// c = n / 255
-// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
-static const uint8_t gsRGBToLinearRGBMap[256] = {
-  0,   0,   0,   0,   0,   0,   0,   1,
-  1,   1,   1,   1,   1,   1,   1,   1,
-  1,   1,   2,   2,   2,   2,   2,   2,
-  2,   2,   3,   3,   3,   3,   3,   3,
-  4,   4,   4,   4,   4,   5,   5,   5,
-  5,   6,   6,   6,   6,   7,   7,   7,
-  8,   8,   8,   8,   9,   9,   9,  10,
- 10,  10,  11,  11,  12,  12,  12,  13,
- 13,  13,  14,  14,  15,  15,  16,  16,
- 17,  17,  17,  18,  18,  19,  19,  20,
- 20,  21,  22,  22,  23,  23,  24,  24,
- 25,  25,  26,  27,  27,  28,  29,  29,
- 30,  30,  31,  32,  32,  33,  34,  35,
- 35,  36,  37,  37,  38,  39,  40,  41,
- 41,  42,  43,  44,  45,  45,  46,  47,
- 48,  49,  50,  51,  51,  52,  53,  54,
- 55,  56,  57,  58,  59,  60,  61,  62,
- 63,  64,  65,  66,  67,  68,  69,  70,
- 71,  72,  73,  74,  76,  77,  78,  79,
- 80,  81,  82,  84,  85,  86,  87,  88,
- 90,  91,  92,  93,  95,  96,  97,  99,
-100, 101, 103, 104, 105, 107, 108, 109,
-111, 112, 114, 115, 116, 118, 119, 121,
-122, 124, 125, 127, 128, 130, 131, 133,
-134, 136, 138, 139, 141, 142, 144, 146,
-147, 149, 151, 152, 154, 156, 157, 159,
-161, 163, 164, 166, 168, 170, 171, 173,
-175, 177, 179, 181, 183, 184, 186, 188,
-190, 192, 194, 196, 198, 200, 202, 204,
-206, 208, 210, 212, 214, 216, 218, 220,
-222, 224, 226, 229, 231, 233, 235, 237,
-239, 242, 244, 246, 248, 250, 253, 255
-};
-
-static void
-ComputesRGBLuminanceMask(const uint8_t *aSourceData,
-                         int32_t aSourceStride,
-                         uint8_t *aDestData,
-                         int32_t aDestStride,
-                         const IntSize &aSize,
-                         float aOpacity)
+static LuminanceType
+GetLuminanceType(uint8_t aNSMaskType)
 {
-#ifdef BUILD_ARM_NEON
-  if (mozilla::supports_neon()) {
-    ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride,
-                                  aDestData, aDestStride,
-                                  aSize, aOpacity);
-    return;
-  }
-#endif
-
-  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
-  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
-  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
-  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
-  const uint8_t *sourcePixel = aSourceData;
-  int32_t destOffset = aDestStride - aSize.width;
-  uint8_t *destPixel = aDestData;
-
-  for (int32_t y = 0; y < aSize.height; y++) {
-    for (int32_t x = 0; x < aSize.width; x++) {
-      uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
-
-      if (a) {
-        *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
-                      greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
-                      blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
-      } else {
-        *destPixel = 0;
-      }
-      sourcePixel += 4;
-      destPixel++;
+  switch (aNSMaskType) {
+    case NS_STYLE_MASK_TYPE_LUMINANCE:
+      return LuminanceType::LUMINANCE;
+    case NS_STYLE_COLOR_INTERPOLATION_LINEARRGB:
+      return LuminanceType::LINEARRGB;
+    default:
+    {
+      NS_WARNING("Unknown SVG mask type, defaulting to luminance");
+      return LuminanceType::LUMINANCE;
     }
-    sourcePixel += sourceOffset;
-    destPixel += destOffset;
   }
 }
 
-static void
-ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
-                              int32_t aSourceStride,
-                              uint8_t *aDestData,
-                              int32_t aDestStride,
-                              const IntSize &aSize,
-                              float aOpacity)
-{
-  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
-  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
-  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
-  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
-  const uint8_t *sourcePixel = aSourceData;
-  int32_t destOffset = aDestStride - aSize.width;
-  uint8_t *destPixel = aDestData;
-
-  for (int32_t y = 0; y < aSize.height; y++) {
-    for (int32_t x = 0; x < aSize.width; x++) {
-      uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
-
-      // unpremultiply
-      if (a) {
-        if (a == 255) {
-          /* sRGB -> linearRGB -> intensity */
-          *destPixel =
-            static_cast<uint8_t>
-                       ((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
-                         redFactor +
-                         gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
-                         greenFactor +
-                         gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
-                         blueFactor) >> 8);
-        } else {
-          uint8_t tempPixel[4];
-          tempPixel[GFX_ARGB32_OFFSET_B] =
-            (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
-          tempPixel[GFX_ARGB32_OFFSET_G] =
-            (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
-          tempPixel[GFX_ARGB32_OFFSET_R] =
-            (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
-
-          /* sRGB -> linearRGB -> intensity */
-          *destPixel =
-            static_cast<uint8_t>
-                       (((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
-                          redFactor +
-                          gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
-                          greenFactor +
-                          gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
-                          blueFactor) >> 8) * (a / 255.0f));
-        }
-      } else {
-        *destPixel = 0;
-      }
-      sourcePixel += 4;
-      destPixel++;
-    }
-    sourcePixel += sourceOffset;
-    destPixel += destOffset;
-  }
-}
-
-//----------------------------------------------------------------------
-// Implementation
-
 nsIFrame*
 NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsSVGMaskFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsSVGMaskFrame)
 
@@ -253,54 +119,30 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(Ma
     gfxMatrix m = mMatrixForChildren;
     if (kid->GetContent()->IsSVGElement()) {
       m = static_cast<nsSVGElement*>(kid->GetContent())->
             PrependLocalTransformsTo(m, eUserSpaceToParent);
     }
     nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams);
   }
 
+  if (StyleSVG()->mColorInterpolation ==
+    NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
+    maskType = NS_STYLE_COLOR_INTERPOLATION_LINEARRGB;
+  }
+
   RefPtr<SourceSurface> surface;
   if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) {
-    RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot();
+    RefPtr<SourceSurface> maskSnapshot =
+      maskDT->IntoLuminanceSource(GetLuminanceType(maskType),
+                                  aParams.opacity);
     if (!maskSnapshot) {
       return nullptr;
     }
-
-    RefPtr<DataSourceSurface> maskSurface = maskSnapshot->GetDataSurface();
-    DataSourceSurface::MappedSurface map;
-    if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
-      return nullptr;
-    }
-
-    // Create alpha channel mask for output
-    RefPtr<DataSourceSurface> destMaskSurface =
-      Factory::CreateDataSourceSurface(maskSurfaceSize, SurfaceFormat::A8);
-    if (!destMaskSurface) {
-      return nullptr;
-    }
-    DataSourceSurface::MappedSurface destMap;
-    if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
-      return nullptr;
-    }
-
-    if (StyleSVG()->mColorInterpolation ==
-        NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
-      ComputeLinearRGBLuminanceMask(map.mData, map.mStride,
-                                    destMap.mData, destMap.mStride,
-                                    maskSurfaceSize, aParams.opacity);
-    } else {
-      ComputesRGBLuminanceMask(map.mData, map.mStride,
-                               destMap.mData, destMap.mStride,
-                               maskSurfaceSize, aParams.opacity);
-    }
-
-    maskSurface->Unmap();
-    destMaskSurface->Unmap();
-    surface = destMaskSurface.forget();
+    surface = maskSnapshot.forget();
   } else {
     maskDT->SetTransform(Matrix());
     maskDT->FillRect(Rect(0, 0, maskSurfaceSize.width, maskSurfaceSize.height), ColorPattern(Color(1.0f, 1.0f, 1.0f, aParams.opacity)), DrawOptions(1, CompositionOp::OP_IN));
     RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot();
     if (!maskSnapshot) {
       return nullptr;
     }
     surface = maskSnapshot.forget();
--- a/layout/svg/nsSVGMaskFrame.h
+++ b/layout/svg/nsSVGMaskFrame.h
@@ -11,31 +11,16 @@
 #include "mozilla/RefPtr.h"
 #include "gfxPattern.h"
 #include "gfxMatrix.h"
 #include "nsSVGContainerFrame.h"
 #include "nsSVGUtils.h"
 
 class gfxContext;
 
-/**
- * Byte offsets of channels in a native packed gfxColor or cairo image surface.
- */
-#ifdef IS_BIG_ENDIAN
-#define GFX_ARGB32_OFFSET_A 0
-#define GFX_ARGB32_OFFSET_R 1
-#define GFX_ARGB32_OFFSET_G 2
-#define GFX_ARGB32_OFFSET_B 3
-#else
-#define GFX_ARGB32_OFFSET_A 3
-#define GFX_ARGB32_OFFSET_R 2
-#define GFX_ARGB32_OFFSET_G 1
-#define GFX_ARGB32_OFFSET_B 0
-#endif
-
 class nsSVGMaskFrame final : public nsSVGContainerFrame
 {
   friend nsIFrame*
   NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
   typedef mozilla::gfx::Matrix Matrix;
   typedef mozilla::gfx::SourceSurface SourceSurface;
   typedef mozilla::image::imgDrawingParams imgDrawingParams;
deleted file mode 100644
--- a/layout/svg/nsSVGMaskFrameNEON.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsSVGMaskFrameNEON.h"
-#include "nsSVGMaskFrame.h"
-#include <arm_neon.h>
-
-using namespace mozilla::gfx;
-
-void
-ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
-                              int32_t aSourceStride,
-                              uint8_t *aDestData,
-                              int32_t aDestStride,
-                              const IntSize &aSize,
-                              float aOpacity)
-{
-  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
-  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
-  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
-  const uint8_t *sourcePixel = aSourceData;
-  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
-  uint8_t *destPixel = aDestData;
-  int32_t destOffset = aDestStride - aSize.width;
-
-  sourcePixel = aSourceData;
-  int32_t remainderWidth = aSize.width % 8;
-  int32_t roundedWidth = aSize.width - remainderWidth;
-  uint16x8_t temp;
-  uint8x8_t gray;
-  uint8x8_t redVector = vdup_n_u8(redFactor);
-  uint8x8_t greenVector = vdup_n_u8(greenFactor);
-  uint8x8_t blueVector = vdup_n_u8(blueFactor);
-  uint8x8_t fullBitVector = vdup_n_u8(255);
-  uint8x8_t oneVector = vdup_n_u8(1);
-  for (int32_t y = 0; y < aSize.height; y++) {
-    // Calculate luminance by neon with 8 pixels per loop
-    for (int32_t x = 0; x < roundedWidth; x += 8) {
-      uint8x8x4_t argb  = vld4_u8(sourcePixel);
-      temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R], redVector); // temp = red * redFactor
-      temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G], greenVector); // temp += green * greenFactor
-      temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B], blueVector); // temp += blue * blueFactor
-      gray = vshrn_n_u16(temp, 8); // gray = temp >> 8
-
-      // Check alpha value
-      uint8x8_t alphaVector = vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector);
-      gray = vmul_u8(gray, vand_u8(alphaVector, oneVector));
-
-      // Put the result to the 8 pixels
-      vst1_u8(destPixel, gray);
-      sourcePixel += 8 * 4;
-      destPixel += 8;
-    }
-
-    // Calculate the rest pixels of the line by cpu
-    for (int32_t x = 0; x < remainderWidth; x++) {
-      if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) {
-        *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R]+
-                      greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
-                      blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
-      } else {
-        *destPixel = 0;
-      }
-      sourcePixel += 4;
-      destPixel++;
-    }
-    sourcePixel += sourceOffset;
-    destPixel += destOffset;
-  }
-}
-
deleted file mode 100644
--- a/layout/svg/nsSVGMaskFrameNEON.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* this source code form is subject to the terms of the mozilla public
- * license, v. 2.0. if a copy of the mpl was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef __NS_SVGMASKFRAMENEON_H__
-#define __NS_SVGMASKFRAMENEON_H__
-
-#include "mozilla/gfx/Point.h"
-
-void
-ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
-                              int32_t aSourceStride,
-                              uint8_t *aDestData,
-                              int32_t aDestStride,
-                              const mozilla::gfx::IntSize &aSize,
-                              float aOpacity);
-
-#endif /* __NS_SVGMASKFRAMENEON_H__ */
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -139,17 +139,17 @@ function SetFailureTimeout(cb, timeout)
 }
 
 function StartTestURI(type, uri, timeout)
 {
     // The GC is only able to clean up compartments after the CC runs. Since
     // the JS ref tests disable the normal browser chrome and do not otherwise
     // create substatial DOM garbage, the CC tends not to run enough normally.
     ++gTestCount;
-    if (gTestCount % 1000 == 0) {
+    if (gTestCount % 250 == 0) {
         CU.forceGC();
         CU.forceCC();
     }
 
     // Reset gExplicitPendingPaintCount in case there was a timeout or
     // the count is out of sync for some other reason
     if (gExplicitPendingPaintCount != 0) {
         LogWarning("Resetting gExplicitPendingPaintCount to zero (currently " +
--- a/media/libpng/pnglibconf.h
+++ b/media/libpng/pnglibconf.h
@@ -3,22 +3,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef PNGLCONF_H
 #define PNGLCONF_H
 
 #define MOZ_EMBEDDED_LIBPNG
 
 /* Limit image dimensions (bug #251381, #591822, #967656, and #1283961) */
+#define PNG_USER_LIMITS_SUPPORTED
 #ifndef MOZ_PNG_MAX_WIDTH
 #  define MOZ_PNG_MAX_WIDTH 0x7fffffffL /* Unlimited */
 #endif
 #ifndef MOZ_PNG_MAX_HEIGHT
 #  define MOZ_PNG_MAX_HEIGHT 0x7fffffffL /* Unlimited */
 #endif
+/* but allow nsPNGDecoder to override the limits (bug #1368407) */
+#define PNG_SET_USER_LIMITS_SUPPORTED
 
 #define PNG_API_RULE 0
 #define PNG_COST_SHIFT 3
 #define PNG_GAMMA_THRESHOLD_FIXED 5000
 #define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE
 #define PNG_INFLATE_BUF_SIZE 1024
 #define PNG_LINKAGE_API extern
 #define PNG_LINKAGE_CALLBACK extern
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1389,17 +1389,17 @@ pref("javascript.options.discardSystemSo
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721
 // Comment 32 and Bug 613551.
 pref("javascript.options.mem.high_water_mark", 128);
 pref("javascript.options.mem.max", -1);
 pref("javascript.options.mem.gc_per_zone", true);
 pref("javascript.options.mem.gc_incremental", true);
-pref("javascript.options.mem.gc_incremental_slice_ms", 10);
+pref("javascript.options.mem.gc_incremental_slice_ms", 5);
 pref("javascript.options.mem.gc_compacting", true);
 pref("javascript.options.mem.log", false);
 pref("javascript.options.mem.notify", false);
 pref("javascript.options.gc_on_memory_pressure", true);
 pref("javascript.options.compact_on_user_inactive", true);
 #ifdef NIGHTLY_BUILD
 pref("javascript.options.compact_on_user_inactive_delay", 15000); // ms
 #else
--- a/toolkit/components/places/tests/bookmarks/test_388695.js
+++ b/toolkit/components/places/tests/bookmarks/test_388695.js
@@ -1,52 +1,38 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 // Get bookmark service
-try {
-  var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
-              getService(Ci.nsINavBookmarksService);
-} catch (ex) {
-  do_throw("Could not get nav-bookmarks-service\n");
-}
+let bm = PlacesUtils.bookmarks;
 
-var gTestRoot;
-var gURI;
-var gItemId1;
-var gItemId2;
+// Test that Bookmarks fetch properly orders its results based on
+// the last modified value. Note we cannot rely on dateAdded due to
+// the low PR_Now() resolution.
 
-// main
-function run_test() {
-  gURI = uri("http://foo.tld.com/");
-  gTestRoot = bmsvc.createFolder(bmsvc.placesRoot, "test folder",
-                                 bmsvc.DEFAULT_INDEX);
+add_task(async function sort_bookmark_by_relevance() {
+    let now = new Date();
+    let modifiedTime = new Date(now.setHours(now.getHours() - 2));
 
-  // test getBookmarkIdsForURI
-  // getBookmarkIdsForURI sorts by the most recently added/modified (descending)
-  //
-  // we cannot rely on dateAdded growing when doing so in a simple iteration,
-  // see PR_Now() documentation
-  do_test_pending();
-
-  gItemId1 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
-  do_timeout(100, phase2);
-}
+    let url = "http://foo.tld.com/";
+    let parentGuid = (await bm.insert({type: bm.TYPE_FOLDER,
+                                       title: "test folder",
+                                       parentGuid: bm.unfiledGuid})).guid;
+    let item1Guid = (await bm.insert({url,
+                                      parentGuid})).guid;
+    let item2Guid = (await bm.insert({url,
+                                      parentGuid,
+                                      dateAdded: modifiedTime,
+                                      lastModified: modifiedTime})).guid;
+    let bms = [];
+    await bm.fetch({url}, bm1 => bms.push(bm1));
+    Assert.equal(bms[0].guid, item1Guid);
+    Assert.equal(bms[1].guid, item2Guid);
+    await bm.update({guid: item2Guid, title: "modified"});
 
-function phase2() {
-  gItemId2 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
-  var b = bmsvc.getBookmarkIdsForURI(gURI);
-  do_check_eq(b[0], gItemId2);
-  do_check_eq(b[1], gItemId1);
-  do_timeout(100, phase3);
-}
-
-function phase3() {
-  // trigger last modified change
-  bmsvc.setItemTitle(gItemId1, "");
-  var b = bmsvc.getBookmarkIdsForURI(gURI);
-  do_check_eq(b[0], gItemId1);
-  do_check_eq(b[1], gItemId2);
-  do_test_finished();
-}
+    let bms1 = [];
+    await bm.fetch({url}, bm2 => bms1.push(bm2));
+    Assert.equal(bms1[0].guid, item2Guid);
+    Assert.equal(bms1[1].guid, item1Guid);
+});
--- a/toolkit/modules/subprocess/test/xpcshell/test_subprocess.js
+++ b/toolkit/modules/subprocess/test/xpcshell/test_subprocess.js
@@ -522,17 +522,21 @@ add_task(async function test_subprocess_
       return error.errorCode == Subprocess.ERROR_BAD_EXECUTABLE;
     },
     "Subprocess.call should fail for a bad executable");
 });
 
 
 add_task(async function test_subprocess_workdir() {
   let procDir = await OS.File.getCurrentDirectory();
-  let tmpDir = OS.Constants.Path.tmpDir;
+  let tmpDirFile = Components.classes["@mozilla.org/file/local;1"]
+                     .createInstance(Components.interfaces.nsILocalFile);
+  tmpDirFile.initWithPath(OS.Constants.Path.tmpDir);
+  tmpDirFile.normalize();
+  let tmpDir = tmpDirFile.path;
 
   notEqual(procDir, tmpDir,
            "Current process directory must not be the current temp directory");
 
   async function pwd(options) {
     let proc = await Subprocess.call(Object.assign({
       command: PYTHON,
       arguments: ["-u", TEST_SCRIPT, "pwd"],
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -5014,50 +5014,48 @@ MultiprocessBlockPolicy() {
                                                       : NS_LITERAL_CSTRING("0"));
 #endif
 
   if (addonsCanDisable && disabledByAddons) {
     gMultiprocessBlockPolicy = kE10sDisabledForAddons;
     return gMultiprocessBlockPolicy;
   }
 
-#if defined(XP_WIN)
-  // These checks are currently only in use under WinXP
-  if (false) { // !IsVistaOrLater()
-    bool disabledForA11y = false;
-    /**
-      * Avoids enabling e10s if accessibility has recently loaded. Performs the
-      * following checks:
-      * 1) Checks a pref indicating if a11y loaded in the last session. This pref
-      * is set in nsBrowserGlue.js. If a11y was loaded in the last session we
-      * do not enable e10s in this session.
-      * 2) Accessibility stores a last run date (PR_IntervalNow) when it is
-      * initialized (see nsBaseWidget.cpp). We check if this pref exists and
-      * compare it to now. If a11y hasn't run in an extended period of time or
-      * if the date pref does not exist we load e10s.
-      */
-    disabledForA11y = Preferences::GetBool(kAccessibilityLoadedLastSessionPref, false);
-    if (!disabledForA11y  &&
-        Preferences::HasUserValue(kAccessibilityLastRunDatePref)) {
-      #define ONE_WEEK_IN_SECONDS (60*60*24*7)
-      uint32_t a11yRunDate = Preferences::GetInt(kAccessibilityLastRunDatePref, 0);
-      MOZ_ASSERT(0 != a11yRunDate);
-      // If a11y hasn't run for a period of time, clear the pref and load e10s
-      uint32_t now = PRTimeToSeconds(PR_Now());
-      uint32_t difference = now - a11yRunDate;
-      if (difference > ONE_WEEK_IN_SECONDS || !a11yRunDate) {
-        Preferences::ClearUser(kAccessibilityLastRunDatePref);
-      } else {
-        disabledForA11y = true;
-      }
+#if defined(XP_WIN) && defined(RELEASE_OR_BETA)
+  bool disabledForA11y = false;
+  /**
+    * Avoids enabling e10s if accessibility has recently loaded. Performs the
+    * following checks:
+    * 1) Checks a pref indicating if a11y loaded in the last session. This pref
+    * is set in nsBrowserGlue.js. If a11y was loaded in the last session we
+    * do not enable e10s in this session.
+    * 2) Accessibility stores a last run date (PR_IntervalNow) when it is
+    * initialized (see nsBaseWidget.cpp). We check if this pref exists and
+    * compare it to now. If a11y hasn't run in an extended period of time or
+    * if the date pref does not exist we load e10s.
+    */
+  disabledForA11y = Preferences::GetBool(kAccessibilityLoadedLastSessionPref, false);
+  if (!disabledForA11y  &&
+      Preferences::HasUserValue(kAccessibilityLastRunDatePref)) {
+    #define ONE_WEEK_IN_SECONDS (60*60*24*7)
+    uint32_t a11yRunDate = Preferences::GetInt(kAccessibilityLastRunDatePref, 0);
+    MOZ_ASSERT(0 != a11yRunDate);
+    // If a11y hasn't run for a period of time, clear the pref and load e10s
+    uint32_t now = PRTimeToSeconds(PR_Now());
+    uint32_t difference = now - a11yRunDate;
+    if (difference > ONE_WEEK_IN_SECONDS || !a11yRunDate) {
+      Preferences::ClearUser(kAccessibilityLastRunDatePref);
+    } else {
+      disabledForA11y = true;
     }
-    if (disabledForA11y) {
-      gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;
-      return gMultiprocessBlockPolicy;
-    }
+  }
+
+  if (disabledForA11y) {
+    gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;
+    return gMultiprocessBlockPolicy;
   }
 #endif
 
   /*
    * None of the blocking policies matched, so e10s is allowed to run.
    * Cache the information and return 0, indicating success.
    */
   gMultiprocessBlockPolicy = 0;
--- a/xpcom/io/CocoaFileUtils.h
+++ b/xpcom/io/CocoaFileUtils.h
@@ -23,12 +23,13 @@ nsresult GetFileTypeCode(CFURLRef aUrl, 
 nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode);
 void     AddOriginMetadataToFile(const CFStringRef filePath,
                                  const CFURLRef sourceURL,
                                  const CFURLRef referrerURL);
 void     AddQuarantineMetadataToFile(const CFStringRef filePath,
                                      const CFURLRef sourceURL,
                                      const CFURLRef referrerURL,
                                      const bool isFromWeb);
+CFURLRef GetTemporaryFolderCFURLRef();
 
 } // namespace CocoaFileUtils
 
 #endif
--- a/xpcom/io/CocoaFileUtils.mm
+++ b/xpcom/io/CocoaFileUtils.mm
@@ -252,9 +252,16 @@ void AddQuarantineMetadataToFile(const C
                                    quarantinePropKey,
                                    mutQuarantineProps,
                                    NULL);
 
   ::CFRelease(fileURL);
   ::CFRelease(mutQuarantineProps);
 }
 
+CFURLRef GetTemporaryFolderCFURLRef()
+{
+  NSString* tempDir = ::NSTemporaryDirectory();
+  return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir
+                                                      isDirectory:YES];
+}
+
 } // namespace CocoaFileUtils
--- a/xpcom/io/SpecialSystemDirectory.cpp
+++ b/xpcom/io/SpecialSystemDirectory.cpp
@@ -22,16 +22,19 @@
 
 #elif defined(XP_UNIX)
 
 #include <limits.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/param.h>
 #include "prenv.h"
+#if defined(MOZ_WIDGET_COCOA)
+#include "CocoaFileUtils.h"
+#endif
 
 #endif
 
 #ifndef MAXPATHLEN
 #ifdef PATH_MAX
 #define MAXPATHLEN PATH_MAX
 #elif defined(MAX_PATH)
 #define MAXPATHLEN MAX_PATH
@@ -761,20 +764,30 @@ GetSpecialSystemDirectory(SystemDirector
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 #if defined (MOZ_WIDGET_COCOA)
 nsresult
 GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile)
 {
+  nsresult rv = NS_ERROR_FAILURE;
+
+  if (aFolderType == kTemporaryFolderType) {
+    NS_NewLocalFile(EmptyString(), true, aLocalFile);
+    nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
+    if (localMacFile) {
+      rv = localMacFile->InitWithCFURL(
+             CocoaFileUtils::GetTemporaryFolderCFURLRef());
+    }
+    return rv;
+  }
+
   OSErr err;
   FSRef fsRef;
-  nsresult rv = NS_ERROR_FAILURE;
-
   err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef);
   if (err == noErr) {
     NS_NewLocalFile(EmptyString(), true, aLocalFile);
     nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
     if (localMacFile) {
       rv = localMacFile->InitWithFSRef(&fsRef);
     }
   }
--- a/xpcom/tests/unit/test_file_equality.js
+++ b/xpcom/tests/unit/test_file_equality.js
@@ -20,16 +20,20 @@ function test_normalized_vs_non_normaliz
   // get a directory that exists on all platforms
   var dirProvider = Components.classes["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
   var tmp1 = dirProvider.get("TmpD", Ci.nsILocalFile);
   var exists = tmp1.exists();
   do_check_true(exists);
   if (!exists)
     return;
 
+  // the test logic below assumes we're starting with a normalized path, but the
+  // default location on macos is a symbolic link, so resolve it before starting
+  tmp1.normalize();
+
   // this has the same exact path as tmp1, it should equal tmp1
   var tmp2 = new LocalFile(tmp1.path);
   do_check_true(tmp1.equals(tmp2));
 
   // this is a non-normalized version of tmp1, it should not equal tmp1
   tmp2.appendRelativePath(".");
   do_check_false(tmp1.equals(tmp2));