Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 28 Jul 2016 17:44:09 +0200
changeset 347169 e8ca183fb7ffff634610fcd7783959fa20a4c3a9
parent 347168 88b6929d3c0bedcb7e5486b549e977d273f7a21a (current diff)
parent 347068 9ec789c0ee5bd3a5e765513c21027fdad953b022 (diff)
child 347170 312eafb1465bf3151826cdc6bf5eae6799cb430a
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
devtools/shared/event-parsers.js
testing/docker/rust-build/repack_rust.sh
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -41,16 +41,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuChild();
 });
 XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
   "resource:///modules/Feeds.jsm");
 
+Cu.importGlobalProperties(["URL"]);
+
 // TabChildGlobal
 var global = this;
 
 // Load the form validation popup handler
 var formSubmitObserver = new FormSubmitObserver(content, this);
 
 addMessageListener("ContextMenu:DoCustomCommand", function(message) {
   E10SUtils.wrapHandlingUserInput(
@@ -787,19 +789,21 @@ addMessageListener("ContextMenu:MediaCom
       break;
     case "fullscreen":
       if (content.document.fullscreenEnabled)
         media.requestFullscreen();
       break;
   }
 });
 
-addMessageListener("ContextMenu:Canvas:ToDataURL", (message) => {
-  let dataURL = message.objects.target.toDataURL();
-  sendAsyncMessage("ContextMenu:Canvas:ToDataURL:Result", { dataURL });
+addMessageListener("ContextMenu:Canvas:ToBlobURL", (message) => {
+  message.objects.target.toBlob((blob) => {
+    let blobURL = URL.createObjectURL(blob);
+    sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
+  });
 });
 
 addMessageListener("ContextMenu:ReloadFrame", (message) => {
   message.objects.target.ownerDocument.location.reload();
 });
 
 addMessageListener("ContextMenu:ReloadImage", (message) => {
   let image = message.objects.target;
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1136,35 +1136,35 @@ nsContextMenu.prototype = {
     urlSecurityCheck(this.mediaURL,
                      this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 
     this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
                                                  null, { target: this.target });
   },
 
-  _canvasToDataURL: function(target) {
+  _canvasToBlobURL: function(target) {
     let mm = this.browser.messageManager;
     return new Promise(function(resolve) {
-      mm.sendAsyncMessage("ContextMenu:Canvas:ToDataURL", {}, { target });
+      mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL", {}, { target });
 
       let onMessage = (message) => {
-        mm.removeMessageListener("ContextMenu:Canvas:ToDataURL:Result", onMessage);
-        resolve(message.data.dataURL);
+        mm.removeMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
+        resolve(message.data.blobURL);
       };
-      mm.addMessageListener("ContextMenu:Canvas:ToDataURL:Result", onMessage);
+      mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
     });
   },
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia: function(e) {
     let referrerURI = gContextMenuContentData.documentURIObject;
     if (this.onCanvas) {
-      this._canvasToDataURL(this.target).then(function(dataURL) {
-        openUILink(dataURL, e, { disallowInheritPrincipal: true,
+      this._canvasToBlobURL(this.target).then(function(blobURL) {
+        openUILink(blobURL, e, { disallowInheritPrincipal: true,
                                  referrerURI: referrerURI });
       }, Cu.reportError);
     }
     else {
       urlSecurityCheck(this.mediaURL,
                        this.browser.contentPrincipal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
       openUILink(this.mediaURL, e, { disallowInheritPrincipal: true,
@@ -1431,18 +1431,18 @@ nsContextMenu.prototype = {
 
   // Save URL of the clicked upon image, video, or audio.
   saveMedia: function() {
     let doc = this.ownerDoc;
     let referrerURI = gContextMenuContentData.documentURIObject;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
     if (this.onCanvas) {
       // Bypass cache, since it's a data: URL.
-      this._canvasToDataURL(this.target).then(function(dataURL) {
-        saveImageURL(dataURL, "canvas.png", "SaveImageTitle",
+      this._canvasToBlobURL(this.target).then(function(blobURL) {
+        saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
                      true, false, referrerURI, null, null, null,
                      isPrivate);
       }, Cu.reportError);
     }
     else if (this.onImage) {
       urlSecurityCheck(this.mediaURL, this.principal);
       saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
                    false, referrerURI, null, gContextMenuContentData.contentType,
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3030,33 +3030,45 @@
           if (oldPosition == aIndex)
             return;
 
           this._lastRelatedTab = null;
 
           let wasFocused = (document.activeElement == this.mCurrentTab);
 
           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
-          this.mCurrentTab._logicallySelected = false;
-          this.mCurrentTab._visuallySelected = false;
 
           // invalidate cache
           this._visibleTabs = null;
 
           // use .item() instead of [] because dragging to the end of the strip goes out of
           // bounds: .item() returns null (so it acts like appendChild), but [] throws
           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
 
           for (let i = 0; i < this.tabs.length; i++) {
             this.tabs[i]._tPos = i;
-            this.tabs[i]._logicallySelected = false;
-            this.tabs[i]._visuallySelected = false;
+            this.tabs[i]._selected = false;
           }
-          this.mCurrentTab._logicallySelected = true;
-          this.mCurrentTab._visuallySelected = true;
+
+          // If we're in the midst of an async tab switch while calling
+          // moveTabTo, we can get into a case where _visuallySelected
+          // is set to true on two different tabs.
+          //
+          // What we want to do in moveTabTo is to remove logical selection
+          // from all tabs, and then re-add logical selection to mCurrentTab
+          // (and visual selection as well if we're not running with e10s, which
+          // setting _selected will do automatically).
+          //
+          // If we're running with e10s, then the visual selection will not
+          // be changed, which is fine, since if we weren't in the midst of a
+          // tab switch, the previously visually selected tab should still be
+          // correct, and if we are in the midst of a tab switch, then the async
+          // tab switcher will set the visually selected tab once the tab switch
+          // has completed.
+          this.mCurrentTab._selected = true;
 
           if (wasFocused)
             this.mCurrentTab.focus();
 
           this.tabContainer._handleTabSelect(false);
 
           if (aTab.pinned)
             this.tabContainer._positionPinnedTabs();
@@ -6283,36 +6295,26 @@
 
           this._setPositionAttributes(val);
 
           return val;
           ]]>
         </setter>
       </property>
 
-      <property name="_logicallySelected">
-        <setter>
-          <![CDATA[
-          if (val)
-            this.setAttribute("selected", "true");
-          else
-            this.removeAttribute("selected");
-
-          return val;
-          ]]>
-        </setter>
-      </property>
-
       <property name="_selected">
         <setter>
           <![CDATA[
           // in e10s we want to only pseudo-select a tab before its rendering is done, so that
           // the rest of the system knows that the tab is selected, but we don't want to update its
           // visual status to selected until after we receive confirmation that its content has painted.
-          this._logicallySelected = val;
+          if (val)
+            this.setAttribute("selected", "true");
+          else
+            this.removeAttribute("selected");
 
           // If we're non-e10s we should update the visual selection as well at the same time,
           // *or* if we're e10s and the visually selected tab isn't changing, in which case the
           // tab switcher code won't run and update anything else (like the before- and after-
           // selected attributes).
           if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) {
             this._visuallySelected = val;
           }
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -4,16 +4,17 @@ support-files =
   empty_file.html
   file_reflect_cookie_into_title.html
   favicon-normal32.png
   file_set_storages.html
   serviceworker.html
   worker.js
 
 [browser_aboutURLs.js]
+skip-if = (debug && (os == "win" || os == "linux")) # intermittent negative leak bug 1271182
 [browser_eme.js]
 [browser_favicon.js]
 [browser_forgetaboutsite.js]
 [browser_usercontext.js]
 [browser_usercontextid_tabdrop.js]
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
 [browser_windowName.js]
 tags = openwindow
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -42,35 +42,65 @@ def windows_sdk_dir(value, host):
         return value
     if host.kernel != 'WINNT':
         return ()
 
     return tuple(x[1] for x in get_registry_values(
         r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots'
         r'\KitsRoot*'))
 
+# The Windows SDK 8.1 and 10 have different layouts. The former has
+# $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir.
+# The vcvars* scripts don't actually care about the version, they just take
+# the last.
+@imports('os')
+@imports('re')
+@imports(_from='__builtin__', _import='sorted')
+@imports(_from='__builtin__', _import='WindowsError')
+def get_include_dir(sdk, subdir):
+    base = os.path.join(sdk, 'include')
+    try:
+        subdirs = [d for d in os.listdir(base)
+                   if os.path.isdir(os.path.join(base, d))]
+    except WindowsError:
+        subdirs = []
+    if not subdirs:
+        return None
+    if subdir in subdirs:
+        return os.path.join(base, subdir)
+    # At this point, either we have an incomplete or invalid SDK directory,
+    # or we exclusively have version numbers in subdirs.
+    versions = sorted((Version(d) for d in subdirs), reverse=True)
+    # Version('non-number').major is 0, so if the biggest version we have is
+    # 0, we have a problem.
+    if versions[0].major == 0:
+        return None
+    path = os.path.join(base, str(versions[0]), subdir)
+    return path if os.path.isdir(path) else None
+
+
 @imports(_from='mozbuild.shellutil', _import='quote')
 def valid_windows_sdk_dir_result(value):
     if value:
         return '0x%04x in %s' % (value.version, quote(value.path))
 
 @depends_win(c_compiler, windows_sdk_dir, valid_windows_version,
              'WINDOWSSDKDIR')
 @checking('for Windows SDK', valid_windows_sdk_dir_result)
 @imports(_from='__builtin__', _import='sorted')
 @imports(_from='textwrap', _import='dedent')
 def valid_windows_sdk_dir(compiler, windows_sdk_dir, target_version,
                           windows_sdk_dir_env):
     if windows_sdk_dir_env:
         windows_sdk_dir_env = windows_sdk_dir_env[0]
     sdks = {}
     for d in windows_sdk_dir:
-        um_dir = os.path.join(d, 'include', 'um')
-        shared_dir = os.path.join(d, 'include', 'shared')
-        if os.path.isdir(um_dir) and os.path.isdir(shared_dir):
+        um_dir = get_include_dir(d, 'um')
+        shared_dir = get_include_dir(d, 'shared')
+        if um_dir and shared_dir:
             check = dedent('''\
             #include <winsdkver.h>
             WINVER_MAXVER
             ''')
             result = try_preprocess(compiler.wrapper + [compiler.compiler] +
                                     compiler.flags +
                                     ['-I', um_dir, '-I', shared_dir], 'C',
                                     check)
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2087,17 +2087,17 @@ Toolbox.prototype = {
     // Destroy the profiler connection
     outstanding.push(this.destroyPerformance());
 
     // Detach the thread
     detachThread(this._threadClient);
     this._threadClient = null;
 
     // We need to grab a reference to win before this._host is destroyed.
-    let win = this.frame.ownerGlobal;
+    let win = this.frame.ownerDocument.defaultView;
 
     if (this._requisition) {
       CommandUtils.destroyRequisition(this._requisition, this.target);
     }
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
 
     // Finish all outstanding tasks (which means finish destroying panels and
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -18,17 +18,17 @@ responsive.editDeviceList=Edit list…
 # LOCALIZATION NOTE (responsive.exit): tooltip text of the exit button.
 responsive.exit=Close Responsive Design Mode
 
 # LOCALIZATION NOTE (responsive.deviceListLoading): placeholder text for
 # device selector when it's still fetching devices
 responsive.deviceListLoading=Loading…
 
 # LOCALIZATION NOTE (responsive.deviceListError): placeholder text for
-# device selector when an error occured
+# device selector when an error occurred
 responsive.deviceListError=No list available
 
 # LOCALIZATION NOTE (responsive.done): button text in the device list modal
 responsive.done=Done
 
 # LOCALIZATION NOTE (responsive.noDeviceSelected): placeholder text for the
 # device selector
 responsive.noDeviceSelected=no device selected
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -114,17 +114,17 @@ const SPECIAL_VALUES = new Set(["none", 
  *
  * @param {nsIDOMNode} el
  *        The widget container.
  * @param {String} value
  *        CSS filter value
  */
 function CSSFilterEditorWidget(el, value = "") {
   this.doc = el.ownerDocument;
-  this.win = this.doc.ownerGlobal;
+  this.win = this.doc.defaultView;
   this.el = el;
 
   this._addButtonClick = this._addButtonClick.bind(this);
   this._removeButtonClick = this._removeButtonClick.bind(this);
   this._mouseMove = this._mouseMove.bind(this);
   this._mouseUp = this._mouseUp.bind(this);
   this._mouseDown = this._mouseDown.bind(this);
   this._keyDown = this._keyDown.bind(this);
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -1773,17 +1773,17 @@ EditableFieldsEngine.prototype = {
    * Copies various styles from one node to another.
    *
    * @param  {Node} source
    *         The node to copy styles from.
    * @param  {Node} destination [description]
    *         The node to copy styles to.
    */
   copyStyles: function (source, destination) {
-    let style = source.ownerGlobal.getComputedStyle(source);
+    let style = source.ownerDocument.defaultView.getComputedStyle(source);
     let props = [
       "borderTopWidth",
       "borderRightWidth",
       "borderBottomWidth",
       "borderLeftWidth",
       "fontFamily",
       "fontSize",
       "fontWeight",
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -952,17 +952,17 @@ StyleEditorUI.prototype = {
   /**
    * Launches the responsive mode with a specific width or height
    *
    * @param  {object} options
    *         Object with width or/and height properties.
    */
   _launchResponsiveMode: Task.async(function* (options = {}) {
     let tab = this._target.tab;
-    let win = this._target.tab.ownerGlobal;
+    let win = this._target.tab.ownerDocument.defaultView;
 
     yield ResponsiveUIManager.runIfNeeded(win, tab);
     if (options.width && options.height) {
       ResponsiveUIManager.getResponsiveUIForTab(tab).setSize(options.width,
                                                              options.height);
     } else if (options.width) {
       ResponsiveUIManager.getResponsiveUIForTab(tab).setWidth(options.width);
     } else if (options.height) {
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -73,17 +73,17 @@ const {
   isShadowAnonymous,
   getFrameElement
 } = require("devtools/shared/layout/utils");
 const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/layout");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
-const {EventParsers} = require("devtools/shared/event-parsers");
+const {EventParsers} = require("devtools/server/event-parsers");
 const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
rename from devtools/shared/event-parsers.js
rename to devtools/server/event-parsers.js
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -28,13 +28,14 @@ SOURCES += [
 
 FINAL_LIBRARY = 'xul'
 
 DevToolsModules(
     'child.js',
     'content-globals.js',
     'content-server.jsm',
     'css-logic.js',
+    'event-parsers.js',
     'main.js',
     'primitive.js',
     'service-worker-child.js',
     'worker.js'
 )
--- a/devtools/shared/locales/en-US/gclicommands.properties
+++ b/devtools/shared/locales/en-US/gclicommands.properties
@@ -1452,17 +1452,17 @@ folderInvalidPath=Please enter a valid p
 # describe the result of the 'folder open' command.
 # The argument (%1$S) is the folder path.
 folderOpenDirResult=Opened %1$S
 
 # LOCALIZATION NOTE (mdnDesc) A very short string used to describe the
 # use of 'mdn' command.
 mdnDesc=Retrieve documentation from MDN
 # LOCALIZATION NOTE (mdnCssDesc) A very short string used to describe the
-# result of the 'mdn css' commmand.
+# result of the 'mdn css' command.
 mdnCssDesc=Retrieve documentation about a given CSS property name from MDN
 # LOCALIZATION NOTE (mdnCssProp) String used to describe the 'property name'
 # parameter used in the 'mdn css' command.
 mdnCssProp=Property name
 # LOCALIZATION NOTE (mdnCssPropertyNotFound) String used to display an error in
 # the result of the 'mdn css' command. Errors occur when a given CSS property
 # wasn't found on MDN. The %1$S parameter will be replaced with the name of the
 # CSS property.
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -48,17 +48,16 @@ DevToolsModules(
     'css-parsing-utils.js',
     'css-properties-db.js',
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
     'event-emitter.js',
-    'event-parsers.js',
     'indentation.js',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
     'protocol.js',
     'system.js',
     'task.js',
     'ThreadSafeDevToolsUtils.js',
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2609,16 +2609,19 @@ void
 HTMLMediaElement::ResetConnectionState()
 {
   SetCurrentTime(0);
   FireTimeUpdate(false);
   DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
   ChangeDelayLoadStatus(false);
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
+  if (mDecoder) {
+    ShutdownDecoder();
+  }
 }
 
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
   nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -92,17 +92,17 @@ InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn’t implement nsIWritablePropertyBag2.
 ResponseTypeSyncXHRWarning=Use of XMLHttpRequest’s responseType attribute is no longer supported in the synchronous mode in window context.
 TimeoutSyncXHRWarning=Use of XMLHttpRequest’s timeout attribute is not supported in the synchronous mode in window context.
 JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
 # LOCALIZATION NOTE: Do not translate AudioBufferSourceNode
 MediaBufferSourceNodeResampleOutOfMemory=Insufficient memory to resample the AudioBufferSourceNode for playback.
 # LOCALIZATION NOTE: Do not translate decodeAudioData.
 MediaDecodeAudioDataUnknownContentType=The buffer passed to decodeAudioData contains an unknown content type.
 # LOCALIZATION NOTE: Do not translate decodeAudioData.
-MediaDecodeAudioDataUnknownError=An unknown error occured while processing decodeAudioData.
+MediaDecodeAudioDataUnknownError=An unknown error occurred while processing decodeAudioData.
 # LOCALIZATION NOTE: Do not translate decodeAudioData.
 MediaDecodeAudioDataInvalidContent=The buffer passed to decodeAudioData contains invalid content which cannot be decoded successfully.
 # LOCALIZATION NOTE: Do not translate decodeAudioData.
 MediaDecodeAudioDataNoAudio=The buffer passed to decodeAudioData does not contain any audio.
 # LOCALIZATION NOTE: Do not translate HTMLMediaElement and createMediaElementSource.
 MediaElementAudioSourceNodeCrossOrigin=The HTMLMediaElement passed to createMediaElementSource has a cross-origin resource, the node will output silence.
 # LOCALIZATION NOTE: Do not translate MediaStream and createMediaStreamSource.
 MediaStreamAudioSourceNodeCrossOrigin=The MediaStream passed to createMediaStreamSource has a cross-origin resource, the node will output silence.
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1020,44 +1020,36 @@ MediaDecoder::FinishDecoderSetup(MediaRe
   return NS_OK;
 }
 
 void
 MediaDecoder::ResetConnectionState()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
-
-  // Notify the media element that connection gets lost.
   mOwner->ResetConnectionState();
-
-  // Since we have notified the media element the connection
-  // lost event, the decoder will be reloaded when user tries
-  // to play the Rtsp streaming next time.
-  Shutdown();
+  MOZ_ASSERT(IsShutdown());
 }
 
 void
 MediaDecoder::NetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (IsShutdown())
-    return;
-
+  MOZ_ASSERT(!IsShutdown());
   mOwner->NetworkError();
-  Shutdown();
+  MOZ_ASSERT(IsShutdown());
 }
 
 void
 MediaDecoder::DecodeError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
   mOwner->DecodeError();
-  Shutdown();
+  MOZ_ASSERT(IsShutdown());
 }
 
 void
 MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSameOriginMedia = aSameOrigin;
 }
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -52,22 +52,26 @@ public:
   // Must take ownership of MetadataTags aTags argument.
   virtual void MetadataLoaded(const MediaInfo* aInfo,
                               nsAutoPtr<const MetadataTags> aTags) = 0;
 
   // Called by the decoder object, on the main thread,
   // when it has read the first frame of the video or audio.
   virtual void FirstFrameLoaded() = 0;
 
-  // Called by the video decoder object, on the main thread,
+  // Called by the decoder object, on the main thread,
   // when the resource has a network error during loading.
+  // The decoder owner should call Shutdown() on the decoder and drop the
+  // reference to the decoder to prevent further calls into the decoder.
   virtual void NetworkError() = 0;
 
-  // Called by the video decoder object, on the main thread, when the
+  // Called by the decoder object, on the main thread, when the
   // resource has a decode error during metadata loading or decoding.
+  // The decoder owner should call Shutdown() on the decoder and drop the
+  // reference to the decoder to prevent further calls into the decoder.
   virtual void DecodeError() = 0;
 
   // Return true if media element error attribute is not null.
   virtual bool HasError() const = 0;
 
   // Called by the video decoder object, on the main thread, when the
   // resource load has been cancelled.
   virtual void LoadAborted() = 0;
@@ -125,18 +129,20 @@ public:
 
   // Check if the decoder owner is hidden.
   virtual bool IsHidden() const = 0;
 
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
 
-  // Called by the media decoder object, on the main thread,
+  // Called by the decoder object, on the main thread,
   // when the connection between Rtsp server and client gets lost.
+  // The decoder owner should call Shutdown() on the decoder and drop the
+  // reference to the decoder to prevent further calls into the decoder.
   virtual void ResetConnectionState() = 0;
 
   // Called by media decoder when the audible state changed
   virtual void SetAudibleState(bool aAudible) = 0;
 
 #ifdef MOZ_EME
   // Dispatches a "encrypted" event to the HTMLMediaElement, with the
   // provided init data. Actual dispatch may be delayed until HAVE_METADATA.
--- a/dom/svg/DOMSVGLength.cpp
+++ b/dom/svg/DOMSVGLength.cpp
@@ -28,20 +28,18 @@ static nsSVGAttrTearoffTable<nsSVGLength
 
 // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to
 // clear our list's weak ref to us to be safe. (The other option would be to
 // not unlink and rely on the breaking of the other edges in the cycle, as
 // NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.)
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGLength)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGLength)
-  // We may not belong to a list, so we must null check tmp->mList.
-  if (tmp->mList) {
-    tmp->mList->mItems[tmp->mListIndex] = nullptr;
-  }
+  tmp->CleanupWeakRefs();
+  tmp->mVal = nullptr; // (owned by mSVGElement, which we drop our ref to here)
 NS_IMPL_CYCLE_COLLECTION_UNLINK(mList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSVGElement)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGLength)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSVGElement)
@@ -135,31 +133,42 @@ DOMSVGLength::DOMSVGLength(nsSVGLength2*
   , mIsAnimValItem(aAnimVal)
   , mUnit(nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER)
   , mValue(0.0f)
   , mVal(aVal)
   , mSVGElement(aSVGElement)
 {
 }
 
-DOMSVGLength::~DOMSVGLength()
+void
+DOMSVGLength::CleanupWeakRefs()
 {
-  // Our mList's weak ref to us must be nulled out when we die. If GC has
-  // unlinked us using the cycle collector code, then that has already
-  // happened, and mList is null.
+  // Our mList's weak ref to us must be nulled out when we die (or when we're
+  // cycle collected), so we that don't leave behind a pointer to
+  // free / soon-to-be-free memory.
   if (mList) {
+    MOZ_ASSERT(mList->mItems[mListIndex] == this,
+               "Clearing out the wrong list index...?");
     mList->mItems[mListIndex] = nullptr;
   }
 
+  // Similarly, we must update the tearoff table to remove its (non-owning)
+  // pointer to mVal.
   if (mVal) {
-    auto& table = mIsAnimValItem ? sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable;
+    auto& table = mIsAnimValItem ?
+      sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable;
     table.RemoveTearoff(mVal);
   }
 }
 
+DOMSVGLength::~DOMSVGLength()
+{
+  CleanupWeakRefs();
+}
+
 already_AddRefed<DOMSVGLength>
 DOMSVGLength::GetTearOff(nsSVGLength2* aVal, nsSVGElement* aSVGElement,
                          bool aAnimVal)
 {
   auto& table = aAnimVal ? sAnimSVGLengthTearOffTable : sBaseSVGLengthTearOffTable;
   RefPtr<DOMSVGLength> domLength = table.GetTearoff(aVal);
   if (!domLength) {
     domLength = new DOMSVGLength(aVal, aSVGElement, aAnimVal);
--- a/dom/svg/DOMSVGLength.h
+++ b/dom/svg/DOMSVGLength.h
@@ -218,16 +218,23 @@ private:
    * animVal items.
    */
   SVGLength& InternalItem();
 
 #ifdef DEBUG
   bool IndexIsValid();
 #endif
 
+  /**
+   * Clears soon-to-be-invalid weak references in external objects that were
+   * set up during the creation of this object. This should be called during
+   * destruction and during cycle collection.
+   */
+  void CleanupWeakRefs();
+
   RefPtr<DOMSVGLengthList> mList;
 
   // Bounds for the following are checked in the ctor, so be sure to update
   // that if you change the capacity of any of the following.
 
   uint32_t mListIndex:MOZ_SVG_LIST_INDEX_BIT_COUNT;
   uint32_t mAttrEnum:4; // supports up to 16 attributes
   uint32_t mIsAnimValItem:1;
--- a/dom/svg/test/mochitest.ini
+++ b/dom/svg/test/mochitest.ini
@@ -81,16 +81,18 @@ skip-if = android_version == '18' # bug 
 [test_SVGStringList.xhtml]
 [test_SVGStyleElement.xhtml]
 [test_SVGTransformListAddition.xhtml]
 [test_SVGTransformList.xhtml]
 [test_SVGUnitTypes.html]
 [test_SVGxxxListIndexing.xhtml]
 [test_SVGxxxList.xhtml]
 [test_switch.xhtml]
+[test_tearoff_with_cc.html]
+support-files = tearoff_with_cc_helper.html
 [test_text_2.html]
 [test_text_dirty.html]
 [test_text.html]
 [test_text_lengthAdjust.html]
 [test_text_scaled.html]
 [test_text_selection.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' # b2g(Mouse selection not workin on b2g) b2g-debug(Mouse selection not workin on b2g) b2g-desktop(Mouse selection not workin on b2g)
 [test_text_update.html]
new file mode 100644
--- /dev/null
+++ b/dom/svg/test/tearoff_with_cc_helper.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<body onload="go()">
+  <svg id="outerSvg" width="50%" height="50%"
+       style="border: 1px solid black">
+  </svg>
+  <script type="application/javascript">
+    /* I'm not sure what exactly was required to trigger bug 1288228's crash,
+     * but it involved tweaking a length's specified units and cycle-collecting
+     * and reloading (in some combination).  So, we'll tweak the units and
+     * cycle-collect a few times, and message the outer page to reload us
+     * after we've made the first tweak.
+     */
+    const maxTweaks = 5;
+    let remainingTweaks = maxTweaks;
+
+    var savedBaseVal =  document.getElementById("outerSvg").width.baseVal;
+    function go() {
+      window.parent.SpecialPowers.DOMWindowUtils.cycleCollect();
+      tweak();
+    }
+
+    function tweak() {
+      console.log("tweaked");
+      savedBaseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX);
+      savedBaseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PERCENTAGE);
+      if (remainingTweaks == maxTweaks) {
+        window.parent.postMessage("ping", "*"); // only do this on first tweak
+      }
+      if (--remainingTweaks) {
+        setTimeout(tweak, 0);
+      }
+    }
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/svg/test/test_tearoff_with_cc.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288228
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1288228</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+  /** Test for Bug 1288228 **/
+  /* Note: the crash in bug 1288228 doesn't happen reliably (and only happens
+   * after several reloads).  So, we reload the iframe 10 times, and then call
+   * it good if we haven't crashed.
+   */
+  const maxReloads = 10;
+  let remainingReloads = maxReloads;
+
+  /* The helper-file in the iframe will notify us after it's performed its
+   * potentially-crash-triggering tweak. At that point, we reload the iframe
+   * and wait for it to notify again (or we simply finish, if we've completed
+   * all of the reloads we planned to do).
+   */
+  window.addEventListener("message", reloadIframe, false);
+
+  function reloadIframe() {
+    if (--remainingReloads == 0) {
+      ok(true, "Didn't crash!");
+      SimpleTest.finish();
+    } else {
+      var frame = document.getElementById("testIframe");
+      frame.setAttribute("src", "");
+      frame.setAttribute("src", "tearoff_with_cc_helper.html");
+    }
+  }
+  SimpleTest.waitForExplicitFinish();
+  </script>
+</head>
+<body onload="reloadIframe()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288228">
+  Mozilla Bug 1288228
+</a>
+<p id="display">
+  <iframe id="testIframe"></iframe>
+</p>
+</body>
+</html>
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -149,19 +149,22 @@ IsAccelAngleSupported(const nsCOMPtr<nsI
                       nsACString* const out_failureId)
 {
     int32_t angleSupport;
     nsCString failureId;
     gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
                                          nsIGfxInfo::FEATURE_WEBGL_ANGLE,
                                          failureId,
                                          &angleSupport);
-    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
-                          failureId);
-    if (failureId.IsEmpty()) {
+    if (failureId.IsEmpty() && angleSupport != nsIGfxInfo::FEATURE_STATUS_OK) {
+        // This shouldn't happen, if we see this it's because we've missed
+        // some failure paths
+        failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NOT_OK");
+    }
+    if (out_failureId->IsEmpty()) {
         *out_failureId = failureId;
     }
     return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK);
 }
 
 static EGLDisplay
 GetAndInitDisplay(GLLibraryEGL& egl, void* displayType)
 {
@@ -253,16 +256,20 @@ GetAndInitDisplayForAccelANGLE(GLLibrary
     if (d3d11ANGLE.IsEnabled()) {
         ret = GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE);
     }
 
     if (!ret) {
         ret = GetAndInitDisplay(egl, EGL_DEFAULT_DISPLAY);
     }
 
+    if (!ret) {
+        *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NO_DISP");
+    }
+
     return ret;
 }
 
 bool
 GLLibraryEGL::ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface)
 {
     StaticMutexAutoUnlock lock(sMutex);
     if (!mReadbackGL) {
@@ -453,31 +460,49 @@ GLLibraryEGL::EnsureInitialized(bool for
 
     // Check the ANGLE support the system has
     nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
     mIsANGLE = IsExtensionSupported(ANGLE_platform_angle);
 
     EGLDisplay chosenDisplay = nullptr;
 
     if (IsExtensionSupported(ANGLE_platform_angle_d3d)) {
-        bool accelAngleSupport = IsAccelAngleSupported(gfxInfo, out_failureId);
+        nsCString accelAngleFailureId;
+        bool accelAngleSupport = IsAccelAngleSupported(gfxInfo, &accelAngleFailureId);
         bool shouldTryAccel = forceAccel || accelAngleSupport;
         bool shouldTryWARP = !forceAccel; // Only if ANGLE not supported or fails
 
         // If WARP preferred, will override ANGLE support
         if (gfxPrefs::WebGLANGLEForceWARP()) {
             shouldTryWARP = true;
             shouldTryAccel = false;
+            if (accelAngleFailureId.IsEmpty()) {
+                accelAngleFailureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_FORCE_WARP");
+            }
         }
 
         // Hardware accelerated ANGLE path (supported or force accel)
         if (shouldTryAccel) {
             chosenDisplay = GetAndInitDisplayForAccelANGLE(*this, out_failureId);
         }
 
+        // Report the acceleration status to telemetry
+        if (!chosenDisplay) {
+            if (accelAngleFailureId.IsEmpty()) {
+                Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
+                                      NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_UNKNOWN"));
+            } else {
+                Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
+                                      accelAngleFailureId);
+            }
+        } else {
+            Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
+                                  NS_LITERAL_CSTRING("SUCCESS"));
+        }
+
         // Fallback to a WARP display if ANGLE fails, or if WARP is forced
         if (!chosenDisplay && shouldTryWARP) {
             chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY);
             if (!chosenDisplay) {
                 if (out_failureId->IsEmpty()) {
                     *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WARP_FALLBACK");
                 }
                 NS_ERROR("Fallback WARP context failed to initialize.");
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/COMPtrHolder.h
@@ -0,0 +1,144 @@
+/* -*- 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_COMPtrHolder_h
+#define mozilla_mscom_COMPtrHolder_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Move.h"
+#include "mozilla/mscom/ProxyStream.h"
+#include "mozilla/mscom/Ptr.h"
+
+namespace mozilla {
+namespace mscom {
+
+template<typename Interface, const IID& _IID>
+class COMPtrHolder
+{
+public:
+  typedef ProxyUniquePtr<Interface> COMPtrType;
+  typedef COMPtrHolder<Interface, _IID> ThisType;
+
+  COMPtrHolder() {}
+
+  MOZ_IMPLICIT COMPtrHolder(decltype(nullptr))
+  {
+  }
+
+  explicit COMPtrHolder(COMPtrType&& aPtr)
+    : mPtr(Forward<COMPtrType>(aPtr))
+  {
+  }
+
+  Interface* Get() const
+  {
+    return mPtr.get();
+  }
+
+  MOZ_MUST_USE Interface* Release()
+  {
+    return mPtr.release();
+  }
+
+  void Set(COMPtrType&& aPtr)
+  {
+    mPtr = Forward<COMPtrType>(aPtr);
+  }
+
+  COMPtrHolder(const COMPtrHolder& aOther) = delete;
+
+  COMPtrHolder(COMPtrHolder&& aOther)
+    : mPtr(Move(aOther.mPtr))
+  {
+  }
+
+  // COMPtrHolder is eventually added as a member of a struct that is declared
+  // in IPDL. The generated C++ code for that IPDL struct includes copy
+  // constructors and assignment operators that assume that all members are
+  // copyable. I don't think that those copy constructors and operator= are
+  // actually used by any generated code, but they are made available. Since no
+  // move semantics are available, this terrible hack makes COMPtrHolder build
+  // when used as a member of an IPDL struct.
+  ThisType& operator=(const ThisType& aOther)
+  {
+    Set(Move(aOther.mPtr));
+    return *this;
+  }
+
+  ThisType& operator=(ThisType&& aOther)
+  {
+    Set(Move(aOther.mPtr));
+    return *this;
+  }
+
+  bool operator==(const ThisType& aOther) const
+  {
+    return mPtr == aOther.mPtr;
+  }
+
+  bool IsNull() const
+  {
+    return !mPtr;
+  }
+
+private:
+  // This is mutable to facilitate the above operator= hack
+  mutable COMPtrType mPtr;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+namespace IPC {
+
+template<typename Interface, const IID& _IID>
+struct ParamTraits<mozilla::mscom::COMPtrHolder<Interface, _IID>>
+{
+  typedef mozilla::mscom::COMPtrHolder<Interface, _IID> paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    mozilla::mscom::ProxyStream proxyStream(_IID, aParam.Get());
+    int bufLen;
+    const BYTE* buf = proxyStream.GetBuffer(bufLen);
+    MOZ_ASSERT(buf);
+    aMsg->WriteInt(bufLen);
+    aMsg->WriteBytes(reinterpret_cast<const char*>(buf), bufLen);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    int length;
+    if (!aMsg->ReadLength(aIter, &length)) {
+      return false;
+    }
+
+    mozilla::UniquePtr<BYTE[]> buf;
+    if (length) {
+      buf = mozilla::MakeUnique<BYTE[]>(length);
+      if (!aMsg->ReadBytesInto(aIter, buf.get(), length)) {
+        return false;
+      }
+    }
+
+    mozilla::mscom::ProxyStream proxyStream(buf.get(), length);
+    if (!proxyStream.IsValid()) {
+      return false;
+    }
+    Interface* rawInterface = nullptr;
+    if (!proxyStream.GetInterface(_IID, (void**)&rawInterface)) {
+      return false;
+    }
+    paramType::COMPtrType ptr(rawInterface);
+    aResult->Set(mozilla::Move(ptr));
+    return true;
+  }
+};
+
+} // namespace IPC
+
+#endif // mozilla_mscom_COMPtrHolder_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.cpp
@@ -0,0 +1,78 @@
+/* -*- 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/EnsureMTA.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+
+#include "private/pprthred.h"
+
+namespace {
+
+class EnterMTARunnable : public mozilla::Runnable
+{
+public:
+  NS_IMETHOD Run() override
+  {
+    mozilla::DebugOnly<HRESULT> hr = ::CoInitializeEx(nullptr,
+                                                      COINIT_MULTITHREADED);
+    MOZ_ASSERT(SUCCEEDED(hr));
+    return NS_OK;
+  }
+};
+
+class BackgroundMTAData
+{
+public:
+  BackgroundMTAData()
+  {
+    nsCOMPtr<nsIRunnable> runnable = new EnterMTARunnable();
+    nsresult rv = NS_NewNamedThread("COM MTA",
+                                    getter_AddRefs(mThread), runnable);
+    NS_WARN_IF(NS_FAILED(rv));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  ~BackgroundMTAData()
+  {
+    if (mThread) {
+      mThread->Dispatch(NS_NewRunnableFunction(&::CoUninitialize),
+                        NS_DISPATCH_NORMAL);
+      mThread->Shutdown();
+    }
+  }
+
+  nsCOMPtr<nsIThread> GetThread() const
+  {
+    return mThread;
+  }
+
+private:
+  nsCOMPtr<nsIThread> mThread;
+};
+
+} // anonymous namespace
+
+static mozilla::StaticAutoPtr<BackgroundMTAData> sMTAData;
+
+namespace mozilla {
+namespace mscom {
+
+/* static */ nsCOMPtr<nsIThread>
+EnsureMTA::GetMTAThread()
+{
+  if (!sMTAData) {
+    sMTAData = new BackgroundMTAData();
+    ClearOnShutdown(&sMTAData, ShutdownPhase::ShutdownThreads);
+  }
+  return sMTAData->GetThread();
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/EnsureMTA.h
@@ -0,0 +1,77 @@
+/* -*- 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_EnsureMTA_h
+#define mozilla_mscom_EnsureMTA_h
+
+#include "MainThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Function.h"
+#include "mozilla/mscom/COMApartmentRegion.h"
+#include "mozilla/mscom/utils.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include <windows.h>
+
+namespace mozilla {
+namespace mscom {
+
+// This class is OK to use as a temporary on the stack.
+class MOZ_STACK_CLASS EnsureMTA
+{
+public:
+  template <typename FuncT>
+  EnsureMTA(const FuncT& aClosure)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (IsCurrentThreadMTA()) {
+      // We're already on the MTA, we can run aClosure directly
+      aClosure();
+      return;
+    }
+
+    // In this case we need to run aClosure on a background thread in the MTA
+    nsCOMPtr<nsIThread> thread = GetMTAThread();
+    MOZ_ASSERT(thread);
+
+    HANDLE event = ::CreateEventW(nullptr, FALSE, FALSE, nullptr);
+    if (!event) {
+      return;
+    }
+
+    auto eventSetter = [&]() -> void {
+      aClosure();
+      ::SetEvent(event);
+    };
+
+    nsresult rv =
+      thread->Dispatch(NS_NewRunnableFunction(eventSetter), NS_DISPATCH_NORMAL);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ::CloseHandle(event);
+      return;
+    }
+
+    DWORD waitResult;
+    while ((waitResult = ::WaitForSingleObjectEx(event, INFINITE, TRUE)) ==
+           WAIT_IO_COMPLETION) {
+    }
+    MOZ_ASSERT(waitResult == WAIT_OBJECT_0);
+    ::CloseHandle(event);
+  }
+
+private:
+  static nsCOMPtr<nsIThread> GetMTAThread();
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_EnsureMTA_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Interceptor.cpp
@@ -0,0 +1,293 @@
+/* -*- 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/mscom/Interceptor.h"
+#include "mozilla/mscom/InterceptorLog.h"
+
+#include "mozilla/mscom/MainThreadInvoker.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 "nsThreadUtils.h"
+
+namespace mozilla {
+namespace mscom {
+
+/* static */ HRESULT
+Interceptor::Create(STAUniquePtr<IUnknown>& aTarget, IInterceptorSink* aSink,
+                    REFIID aIid, void** aOutput)
+{
+  if (!aOutput) {
+    return E_INVALIDARG;
+  }
+  *aOutput = nullptr;
+  if (!aTarget || !aSink) {
+    return E_INVALIDARG;
+  }
+  Interceptor* intcpt = new Interceptor(aTarget, aSink);
+  HRESULT hr = intcpt->QueryInterface(aIid, aOutput);
+  static_cast<WeakReferenceSupport*>(intcpt)->Release();
+  return hr;
+}
+
+Interceptor::Interceptor(STAUniquePtr<IUnknown>& aTarget, IInterceptorSink* aSink)
+  : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread)
+  , mTarget(Move(aTarget))
+  , mEventSink(aSink)
+  , mMutex("mozilla::mscom::Interceptor::mMutex")
+{
+  MOZ_ASSERT(aSink);
+  MOZ_ASSERT(!IsProxy(mTarget.get()));
+  RefPtr<IWeakReference> weakRef;
+  if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
+    aSink->SetInterceptor(weakRef);
+  }
+}
+
+Interceptor::~Interceptor()
+{
+  // This needs to run on the main thread because it releases target interface
+  // reference counts which may not be thread-safe.
+  MOZ_ASSERT(NS_IsMainThread());
+  for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
+    MapEntry& entry = mInterceptorMap[index];
+    entry.mInterceptor->Release();
+    entry.mTargetInterface->Release();
+  }
+}
+
+Interceptor::MapEntry*
+Interceptor::Lookup(REFIID aIid)
+{
+  mMutex.AssertCurrentThreadOwns();
+  for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
+    if (mInterceptorMap[index].mIID == aIid) {
+      return &mInterceptorMap[index];
+    }
+  }
+  return nullptr;
+}
+
+HRESULT
+Interceptor::GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget)
+{
+  MutexAutoLock lock(mMutex);
+  MapEntry* entry = Lookup(aIid);
+  if (entry) {
+    aTarget.reset(entry->mTargetInterface);
+    return S_OK;
+  }
+
+  return E_NOINTERFACE;
+}
+
+// CoGetInterceptor requires information from a typelib to be able to
+// generate its emulated vtable. If a typelib is unavailable,
+// CoGetInterceptor returns 0x80070002.
+static const HRESULT kFileNotFound = 0x80070002;
+
+HRESULT
+Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput)
+{
+  // In order to aggregate, we *must* request IID_IUnknown as the initial
+  // interface for the interceptor, as that IUnknown is non-delegating.
+  // This is a fundamental rule for creating aggregated objects in COM.
+  HRESULT hr = ::CoGetInterceptor(aIid, aOuter, IID_IUnknown, (void**)aOutput);
+  if (hr != kFileNotFound) {
+    return hr;
+  }
+
+  // In the case that CoGetInterceptor returns kFileNotFound, we can try to
+  // explicitly load typelib data from our runtime registration facility and
+  // pass that into CoGetInterceptorFromTypeInfo.
+
+  RefPtr<ITypeInfo> typeInfo;
+  bool found = RegisteredProxy::Find(aIid, getter_AddRefs(typeInfo));
+  // If this assert fires then we have omitted registering the typelib for a
+  // required interface. To fix this, review our calls to mscom::RegisterProxy
+  // and mscom::RegisterTypelib, and add the additional typelib as necessary.
+  MOZ_ASSERT(found);
+  if (!found) {
+    return kFileNotFound;
+  }
+
+  hr = ::CoGetInterceptorFromTypeInfo(aIid, aOuter, typeInfo, IID_IUnknown,
+                                      (void**)aOutput);
+  // If this assert fires then the interceptor doesn't like something about
+  // the format of the typelib. One thing in particular that it doesn't like
+  // is complex types that contain unions.
+  MOZ_ASSERT(SUCCEEDED(hr));
+  return hr;
+}
+
+/**
+ * This method contains the core guts of the handling of QueryInterface calls
+ * that are delegated to us from the ICallInterceptor.
+ *
+ * @param aIid ID of the desired interface
+ * @param aOutInterceptor The resulting emulated vtable that corresponds to
+ * the interface specified by aIid.
+ */
+HRESULT
+Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
+{
+  if (!aOutInterceptor) {
+    return E_INVALIDARG;
+  }
+
+  RefPtr<IUnknown> unkInterceptor;
+  IUnknown* interfaceForQILog = nullptr;
+
+  // (1) Check to see if we already have an existing interceptor for aIid.
+
+  { // Scope for lock
+    MutexAutoLock lock(mMutex);
+    MapEntry* entry = Lookup(aIid);
+    if (entry) {
+      unkInterceptor = entry->mInterceptor;
+      interfaceForQILog = entry->mTargetInterface;
+    }
+  }
+
+  // (1a) A COM interceptor already exists for this interface, so all we need
+  // to do is run a QI on it.
+  if (unkInterceptor) {
+    // Technically we didn't actually execute a QI on the target interface, but
+    // for logging purposes we would like to record the fact that this interface
+    // was requested.
+    InterceptorLog::QI(S_OK, mTarget.get(), aIid, interfaceForQILog);
+
+    return unkInterceptor->QueryInterface(aIid, aOutInterceptor);
+  }
+
+  // (2) Obtain a new target interface.
+
+  // (2a) First, make sure that the target interface is available
+  // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
+  // pvReceiver argument directly to the required interface! DO NOT assume
+  // that COM will use QI or upcast/downcast!
+  HRESULT hr;
+
+  STAUniquePtr<IUnknown> targetInterface;
+  IUnknown* rawTargetInterface = nullptr;
+  hr = QueryInterfaceTarget(aIid, (void**)&rawTargetInterface);
+  targetInterface.reset(rawTargetInterface);
+  InterceptorLog::QI(hr, mTarget.get(), aIid, targetInterface.get());
+  MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // We *really* shouldn't be adding interceptors to proxies
+  MOZ_ASSERT(aIid != IID_IMarshal);
+
+  // (3) Create a new COM interceptor to that interface that delegates its
+  // IUnknown to |this|.
+
+  // Raise the refcount for stabilization purposes during aggregation
+  RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(
+        static_cast<WeakReferenceSupport*>(this)));
+
+  hr = CreateInterceptor(aIid, kungFuDeathGrip, getter_AddRefs(unkInterceptor));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // (4) Obtain the interceptor's ICallInterceptor interface and register our
+  // event sink.
+  RefPtr<ICallInterceptor> interceptor;
+  hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
+                                      (void**)getter_AddRefs(interceptor));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  hr = interceptor->RegisterSink(mEventSink);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // (5) Now that we have this new COM interceptor, insert it into the map.
+
+  { // Scope for lock
+    MutexAutoLock lock(mMutex);
+    // We might have raced with another thread, so first check that we don't
+    // already have an entry for this
+    MapEntry* entry = Lookup(aIid);
+    if (entry && entry->mInterceptor) {
+      unkInterceptor = entry->mInterceptor;
+    } else {
+      // We're inserting unkInterceptor into the map but we still want to hang
+      // onto it locally so that we can QI it below.
+      unkInterceptor->AddRef();
+      // OTOH we must not touch the refcount for the target interface
+      // because we are just moving it into the map and its refcounting might
+      // not be thread-safe.
+      IUnknown* rawTargetInterface = targetInterface.release();
+      mInterceptorMap.AppendElement(MapEntry(aIid,
+                                             unkInterceptor,
+                                             rawTargetInterface));
+    }
+  }
+
+  return unkInterceptor->QueryInterface(aIid, aOutInterceptor);
+}
+
+HRESULT
+Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput)
+{
+  // NB: This QI needs to run on the main thread because the target object
+  // is probably Gecko code that is not thread-safe. Note that this main
+  // thread invocation is *synchronous*.
+  MainThreadInvoker invoker;
+  HRESULT hr;
+  auto runOnMainThread = [&]() -> void {
+    MOZ_ASSERT(NS_IsMainThread());
+    hr = mTarget->QueryInterface(aIid, aOutput);
+  };
+  if (!invoker.Invoke(NS_NewRunnableFunction(runOnMainThread))) {
+    return E_FAIL;
+  }
+  return hr;
+}
+
+HRESULT
+Interceptor::QueryInterface(REFIID riid, void** ppv)
+{
+  return WeakReferenceSupport::QueryInterface(riid, ppv);
+}
+
+HRESULT
+Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface)
+{
+  if (aIid == IID_IInterceptor) {
+    *aOutInterface = static_cast<IInterceptor*>(this);
+    (*aOutInterface)->AddRef();
+    return S_OK;
+  }
+
+  return GetInterceptorForIID(aIid, (void**)aOutInterface);
+}
+
+ULONG
+Interceptor::AddRef()
+{
+  return WeakReferenceSupport::AddRef();
+}
+
+ULONG
+Interceptor::Release()
+{
+  return WeakReferenceSupport::Release();
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Interceptor.h
@@ -0,0 +1,125 @@
+/* -*- 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_interceptor_h
+#define mozilla_mscom_interceptor_h
+
+#include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+#include "mozilla/mscom/Ptr.h"
+#include "mozilla/mscom/WeakRef.h"
+#include "mozilla/RefPtr.h"
+
+#include <callobj.h>
+
+namespace mozilla {
+namespace mscom {
+
+// {8831EB53-A937-42BC-9921-B3E1121FDF86}
+DEFINE_GUID(IID_IInterceptorSink,
+0x8831eb53, 0xa937, 0x42bc, 0x99, 0x21, 0xb3, 0xe1, 0x12, 0x1f, 0xdf, 0x86);
+
+struct IInterceptorSink : public ICallFrameEvents
+{
+  virtual STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) = 0;
+};
+
+// {3710799B-ECA2-4165-B9B0-3FA1E4A9B230}
+DEFINE_GUID(IID_IInterceptor,
+0x3710799b, 0xeca2, 0x4165, 0xb9, 0xb0, 0x3f, 0xa1, 0xe4, 0xa9, 0xb2, 0x30);
+
+struct IInterceptor : public IUnknown
+{
+  virtual STDMETHODIMP GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) = 0;
+  virtual STDMETHODIMP GetInterceptorForIID(REFIID aIid,
+                                            void** aOutInterceptor) = 0;
+};
+
+/**
+ * The COM interceptor is the core functionality in mscom that allows us to
+ * redirect method calls to different threads. It emulates the vtable of a
+ * target interface. When a call is made on this emulated vtable, the call is
+ * packaged up into an instance of the ICallFrame interface which may be passed
+ * to other contexts for execution.
+ *
+ * In order to accomplish this, COM itself provides the CoGetInterceptor
+ * function, which instantiates an ICallInterceptor. Note, however, that
+ * ICallInterceptor only works on a single interface; we need to be able to
+ * interpose QueryInterface calls so that we can instantiate a new
+ * ICallInterceptor for each new interface that is requested.
+ *
+ * We accomplish this by using COM aggregation, which means that the
+ * ICallInterceptor delegates its IUnknown implementation to its outer object
+ * (the mscom::Interceptor we implement and control).
+ */
+class Interceptor final : public WeakReferenceSupport
+                        , public IInterceptor
+{
+public:
+  static HRESULT Create(STAUniquePtr<IUnknown>& aTarget, IInterceptorSink* aSink,
+                        REFIID aIid, void** aOutput);
+
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IInterceptor
+  STDMETHODIMP GetTargetForIID(REFIID aIid, InterceptorTargetPtr& aTarget) override;
+  STDMETHODIMP GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) override;
+
+private:
+  struct MapEntry
+  {
+    MapEntry(REFIID aIid, IUnknown* aInterceptor, IUnknown* aTargetInterface)
+      : mIID(aIid)
+      , mInterceptor(aInterceptor)
+      , mTargetInterface(aTargetInterface)
+    {}
+    IID               mIID;
+    IUnknown*         mInterceptor;
+    IUnknown*         mTargetInterface;
+  };
+
+private:
+  Interceptor(STAUniquePtr<IUnknown>& aTarget, IInterceptorSink* aSink);
+  ~Interceptor();
+  MapEntry* Lookup(REFIID aIid);
+  HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput);
+  HRESULT ThreadSafeQueryInterface(REFIID aIid,
+                                   IUnknown** aOutInterface) override;
+  HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
+
+private:
+  STAUniquePtr<IUnknown>    mTarget;
+  RefPtr<IInterceptorSink>  mEventSink;
+  mozilla::Mutex            mMutex; // Guards mInterceptorMap
+  // Using a nsTArray since the # of interfaces is not going to be very high
+  nsTArray<MapEntry>        mInterceptorMap;
+};
+
+template <typename InterfaceT>
+inline HRESULT
+CreateInterceptor(STAUniquePtr<InterfaceT>& aTargetInterface,
+                  IInterceptorSink* aEventSink,
+                  InterfaceT** aOutInterface)
+{
+  if (!aTargetInterface || !aEventSink) {
+    return E_INVALIDARG;
+  }
+
+  REFIID iidTarget = __uuidof(aTargetInterface);
+
+  STAUniquePtr<IUnknown> targetUnknown(aTargetInterface.release());
+  return Interceptor::Create(targetUnknown, aEventSink, iidTarget,
+                             (void**)aOutInterface);
+}
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_interceptor_h
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/InterceptorLog.cpp
@@ -0,0 +1,456 @@
+/* -*- 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/InterceptorLog.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFileStreams.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+
+#include <callobj.h>
+
+using mozilla::DebugOnly;
+using mozilla::mscom::ArrayData;
+using mozilla::mscom::FindArrayData;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+using mozilla::NewNonOwningRunnableMethod;
+using mozilla::services::GetObserverService;
+using mozilla::StaticAutoPtr;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+namespace {
+
+class ShutdownEvent : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(ShutdownEvent, nsIObserver)
+
+class Logger
+{
+public:
+  explicit Logger(const nsACString& aLeafBaseName);
+  bool IsValid()
+  {
+    MutexAutoLock lock(mMutex);
+    return !!mThread;
+  }
+  void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
+  void LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface);
+  nsresult Shutdown();
+
+private:
+  void OpenFile();
+  void Flush();
+  void CloseFile();
+  void AssertRunningOnLoggerThread();
+  bool VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex = 0);
+  static double GetElapsedTime();
+
+  nsCOMPtr<nsIFile>         mLogFileName;
+  nsCOMPtr<nsIOutputStream> mLogFile; // Only accessed by mThread
+  Mutex                     mMutex; // Guards mThread and mEntries
+  nsCOMPtr<nsIThread>       mThread;
+  nsTArray<nsCString>       mEntries;
+};
+
+Logger::Logger(const nsACString& aLeafBaseName)
+  : mMutex("mozilla::com::InterceptorLog::Logger")
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIFile> logFileName;
+  GeckoProcessType procType = XRE_GetProcessType();
+  nsAutoCString leafName(aLeafBaseName);
+  nsresult rv;
+  if (procType == GeckoProcessType_Default) {
+    leafName.AppendLiteral("-Parent-");
+    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFileName));
+  } else if (procType == GeckoProcessType_Content) {
+    leafName.AppendLiteral("-Content-");
+    rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+                                getter_AddRefs(logFileName));
+  } else {
+    return;
+  }
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  DWORD pid = GetCurrentProcessId();
+  leafName.AppendPrintf("%u.log", pid);
+  // Using AppendNative here because Windows
+  rv = logFileName->AppendNative(leafName);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  mLogFileName.swap(logFileName);
+
+  nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+  nsCOMPtr<nsIObserver> shutdownEvent = new ShutdownEvent();
+  rv = obsSvc->AddObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
+                           false);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> openRunnable(
+      NewNonOwningRunnableMethod(this, &Logger::OpenFile));
+  rv = NS_NewNamedThread("COM Intcpt Log", getter_AddRefs(mThread),
+                         openRunnable);
+  if (NS_FAILED(rv)) {
+    obsSvc->RemoveObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
+  }
+}
+
+void
+Logger::AssertRunningOnLoggerThread()
+{
+#if defined(DEBUG)
+  nsCOMPtr<nsIThread> curThread;
+  if (NS_FAILED(NS_GetCurrentThread(getter_AddRefs(curThread)))) {
+    return;
+  }
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(curThread == mThread);
+#endif
+}
+
+void
+Logger::OpenFile()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFileName && !mLogFile);
+  NS_NewLocalFileOutputStream(getter_AddRefs(mLogFile), mLogFileName,
+                              PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                              PR_IRUSR | PR_IWUSR | PR_IRGRP);
+}
+
+void
+Logger::CloseFile()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFile);
+  if (!mLogFile) {
+    return;
+  }
+  Flush();
+  mLogFile->Close();
+  mLogFile = nullptr;
+}
+
+nsresult
+Logger::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsresult rv = mThread->Dispatch(NewNonOwningRunnableMethod(this,
+                                                             &Logger::CloseFile),
+                                  NS_DISPATCH_NORMAL);
+  NS_WARN_IF(NS_FAILED(rv));
+
+  rv = mThread->Shutdown();
+  NS_WARN_IF(NS_FAILED(rv));
+  return NS_OK;
+}
+
+bool
+Logger::VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex)
+{
+  switch (aVariant.vt) {
+    case VT_DISPATCH: {
+      aOut.AppendPrintf("(IDispatch*) 0x%0p", aVariant.pdispVal);
+      return true;
+    }
+    case VT_DISPATCH | VT_BYREF: {
+      aOut.AppendPrintf("(IDispatch*) 0x%0p", (aVariant.ppdispVal)[aIndex]);
+      return true;
+    }
+    case VT_UNKNOWN: {
+      aOut.AppendPrintf("(IUnknown*) 0x%0p", aVariant.punkVal);
+      return true;
+    }
+    case VT_UNKNOWN | VT_BYREF: {
+      aOut.AppendPrintf("(IUnknown*) 0x%0p", (aVariant.ppunkVal)[aIndex]);
+      return true;
+    }
+    case VT_VARIANT | VT_BYREF: {
+      return VariantToString((aVariant.pvarVal)[aIndex], aOut);
+    }
+    case VT_I4 | VT_BYREF: {
+      aOut.AppendPrintf("%d", aVariant.plVal[aIndex]);
+      return true;
+    }
+    case VT_UI4 | VT_BYREF: {
+      aOut.AppendPrintf("%u", aVariant.pulVal[aIndex]);
+      return true;
+    }
+    case VT_I4: {
+      aOut.AppendPrintf("%d", aVariant.lVal);
+      return true;
+    }
+    case VT_UI4: {
+      aOut.AppendPrintf("%u", aVariant.ulVal);
+      return true;
+    }
+    case VT_EMPTY: {
+      aOut.AppendLiteral("(empty VARIANT)");
+      return true;
+    }
+    case VT_NULL: {
+      aOut.AppendLiteral("(null VARIANT)");
+      return true;
+    }
+    case VT_BSTR: {
+      aOut.AppendPrintf("\"%S\"", aVariant.bstrVal);
+      return true;
+    }
+    case VT_BSTR | VT_BYREF: {
+      aOut.AppendPrintf("\"%S\"", *aVariant.pbstrVal);
+      return true;
+    }
+    default: {
+      aOut.AppendPrintf("(VariantToString failed, VARTYPE == 0x%04hx)",
+                        aVariant.vt);
+      return false;
+    }
+  }
+}
+
+/* static */ double
+Logger::GetElapsedTime()
+{
+  TimeStamp ts = TimeStamp::Now();
+  bool inconsistent;
+  TimeDuration duration = ts - TimeStamp::ProcessCreation(inconsistent);
+  return duration.ToMicroseconds();
+}
+
+void
+Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+{
+  if (FAILED(aResult)) {
+    return;
+  }
+  double elapsed = GetElapsedTime();
+
+  nsPrintfCString line("%fus\t0x%0p\tIUnknown::QueryInterface\t([in] ", elapsed,
+                       aTarget);
+
+  WCHAR buf[39] = {0};
+  if (StringFromGUID2(aIid, buf, mozilla::ArrayLength(buf))) {
+    line.AppendPrintf("%S", buf);
+  } else {
+    line.AppendLiteral("(IID Conversion Failed)");
+  }
+  line.AppendPrintf(", [out] 0x%p)\t0x%08X\n", aInterface, aResult);
+
+  MutexAutoLock lock(mMutex);
+  mEntries.AppendElement(line);
+  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
+                    NS_DISPATCH_NORMAL);
+}
+
+void
+Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+{
+  // (1) Gather info about the call
+  double elapsed = GetElapsedTime();
+
+  CALLFRAMEINFO callInfo;
+  HRESULT hr = aCallFrame->GetInfo(&callInfo);
+  if (FAILED(hr)) {
+    return;
+  }
+
+  PWSTR interfaceName = nullptr;
+  PWSTR methodName = nullptr;
+  hr = aCallFrame->GetNames(&interfaceName, &methodName);
+  if (FAILED(hr)) {
+    return;
+  }
+
+  // (2) Serialize the call
+  nsPrintfCString line("%fus\t0x%p\t%S::%S\t(", elapsed,
+                       aTargetInterface, interfaceName, methodName);
+
+  CoTaskMemFree(interfaceName);
+  interfaceName = nullptr;
+  CoTaskMemFree(methodName);
+  methodName = nullptr;
+
+  // Check for supplemental array data
+  const ArrayData* arrayData = FindArrayData(callInfo.iid, callInfo.iMethod);
+
+  for (ULONG paramIndex = 0; paramIndex < callInfo.cParams; ++paramIndex) {
+    CALLFRAMEPARAMINFO paramInfo;
+    hr = aCallFrame->GetParamInfo(paramIndex, &paramInfo);
+    if (SUCCEEDED(hr)) {
+      line.AppendLiteral("[");
+      if (paramInfo.fIn) {
+        line.AppendLiteral("in");
+      }
+      if (paramInfo.fOut) {
+        line.AppendLiteral("out");
+      }
+      line.AppendLiteral("] ");
+    }
+    VARIANT paramValue;
+    hr = aCallFrame->GetParam(paramIndex, &paramValue);
+    if (SUCCEEDED(hr)) {
+      if (arrayData && paramIndex == arrayData->mArrayParamIndex) {
+        VARIANT lengthParam;
+        hr = aCallFrame->GetParam(arrayData->mLengthParamIndex, &lengthParam);
+        if (SUCCEEDED(hr)) {
+          line.AppendLiteral("{ ");
+          for (LONG i = 0; i < *lengthParam.plVal; ++i) {
+            VariantToString(paramValue, line, i);
+            if (i < *lengthParam.plVal - 1) {
+              line.AppendLiteral(", ");
+            }
+          }
+          line.AppendLiteral(" }");
+        } else {
+          line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
+        }
+      } else {
+        VariantToString(paramValue, line);
+      }
+    } else {
+      line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
+    }
+    if (paramIndex < callInfo.cParams - 1) {
+      line.AppendLiteral(", ");
+    }
+  }
+  line.AppendLiteral(")\t");
+
+  HRESULT callResult = aCallFrame->GetReturnValue();
+  line.AppendPrintf("0x%08X\n", callResult);
+
+  // (3) Enqueue event for logging
+  MutexAutoLock lock(mMutex);
+  mEntries.AppendElement(line);
+  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
+                    NS_DISPATCH_NORMAL);
+}
+
+void
+Logger::Flush()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFile);
+  if (!mLogFile) {
+    return;
+  }
+  nsTArray<nsCString> linesToWrite;
+  { // Scope for lock
+    MutexAutoLock lock(mMutex);
+    linesToWrite.SwapElements(mEntries);
+  }
+
+  for (uint32_t i = 0, len = linesToWrite.Length(); i < len; ++i) {
+    uint32_t bytesWritten;
+    nsCString& line = linesToWrite[i];
+    nsresult rv = mLogFile->Write(line.get(), line.Length(), &bytesWritten);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+}
+
+StaticAutoPtr<Logger> sLogger;
+
+NS_IMETHODIMP
+ShutdownEvent::Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData)
+{
+  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  MOZ_ASSERT(sLogger);
+  NS_WARN_IF(NS_FAILED(sLogger->Shutdown()));
+  nsCOMPtr<nsIObserver> kungFuDeathGrip(this);
+  nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+  obsSvc->RemoveObserver(this, aTopic);
+  return NS_OK;
+}
+} // anonymous namespace
+
+
+static bool
+MaybeCreateLog(const char* aEnvVarName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess());
+  MOZ_ASSERT(!sLogger);
+  const char* leafBaseName = PR_GetEnv(aEnvVarName);
+  if (!leafBaseName) {
+    return false;
+  }
+  nsDependentCString strLeafBaseName(leafBaseName);
+  if (strLeafBaseName.IsEmpty()) {
+    return false;
+  }
+  sLogger = new Logger(strLeafBaseName);
+  if (!sLogger->IsValid()) {
+    sLogger = nullptr;
+    return false;
+  }
+  ClearOnShutdown(&sLogger);
+  return true;
+}
+
+namespace mozilla {
+namespace mscom {
+
+/* static */ bool
+InterceptorLog::Init()
+{
+  static const bool isEnabled = MaybeCreateLog("MOZ_MSCOM_LOG_BASENAME");
+  return isEnabled;
+}
+
+/* static */ void
+InterceptorLog::QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+{
+  if (!sLogger) {
+    return;
+  }
+  sLogger->LogQI(aResult, aTarget, aIid, aInterface);
+}
+
+/* static */ void
+InterceptorLog::Event(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+{
+  if (!sLogger) {
+    return;
+  }
+  sLogger->LogEvent(aCallFrame, aTargetInterface);
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/InterceptorLog.h
@@ -0,0 +1,28 @@
+/* -*- 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_InterceptorLog_h
+#define mozilla_mscom_InterceptorLog_h
+
+struct ICallFrame;
+struct IUnknown;
+
+namespace mozilla {
+namespace mscom {
+
+class InterceptorLog
+{
+public:
+  static bool Init();
+  static void QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
+  static void Event(ICallFrame* aCallFrame, IUnknown* aTarget);
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_InterceptorLog_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -0,0 +1,360 @@
+/* -*- 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/MainThreadHandoff.h"
+
+#include "mozilla/mscom/InterceptorLog.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/mscom/utils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "nsThreadUtils.h"
+
+using mozilla::DebugOnly;
+
+namespace {
+
+class HandoffRunnable : public mozilla::Runnable
+{
+public:
+  explicit HandoffRunnable(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+    : mCallFrame(aCallFrame)
+    , mTargetInterface(aTargetInterface)
+    , mResult(E_UNEXPECTED)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mResult = mCallFrame->Invoke(mTargetInterface);
+    return NS_OK;
+  }
+
+  HRESULT GetResult() const
+  {
+    return mResult;
+  }
+
+private:
+  ICallFrame* mCallFrame;
+  IUnknown*   mTargetInterface;
+  HRESULT     mResult;
+};
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+/* static */ HRESULT
+MainThreadHandoff::Create(IInterceptorSink** aOutput)
+{
+  *aOutput = nullptr;
+  MainThreadHandoff* handoff = new MainThreadHandoff();
+  HRESULT hr = handoff->QueryInterface(IID_IInterceptorSink, (void**) aOutput);
+  handoff->Release();
+  return hr;
+}
+
+MainThreadHandoff::MainThreadHandoff()
+  : mRefCnt(1)
+{
+}
+
+MainThreadHandoff::~MainThreadHandoff()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+HRESULT
+MainThreadHandoff::QueryInterface(REFIID riid, void** ppv)
+{
+  IUnknown* punk = nullptr;
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+
+  if (riid == IID_IUnknown || riid == IID_ICallFrameEvents ||
+      riid == IID_IInterceptorSink) {
+    punk = static_cast<IInterceptorSink*>(this);
+  } else if (riid == IID_ICallFrameWalker) {
+    punk = static_cast<ICallFrameWalker*>(this);
+  }
+
+  *ppv = punk;
+  if (!punk) {
+    return E_NOINTERFACE;
+  }
+
+  punk->AddRef();
+  return S_OK;
+}
+
+ULONG
+MainThreadHandoff::AddRef()
+{
+  return (ULONG) InterlockedIncrement((LONG*)&mRefCnt);
+}
+
+ULONG
+MainThreadHandoff::Release()
+{
+  ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt);
+  if (newRefCnt == 0) {
+    // It is possible for the last Release() call to happen off-main-thread.
+    // If so, we need to dispatch an event to delete ourselves.
+    if (NS_IsMainThread()) {
+      delete this;
+    } else {
+      mozilla::DebugOnly<nsresult> rv =
+        NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void
+        {
+          delete this;
+        }));
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+  return newRefCnt;
+}
+
+HRESULT
+MainThreadHandoff::OnCall(ICallFrame* aFrame)
+{
+  // (1) Get info about the method call
+  HRESULT hr;
+  IID iid;
+  ULONG method;
+  hr = aFrame->GetIIDAndMethod(&iid, &method);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  RefPtr<IInterceptor> interceptor;
+  hr = mInterceptor->Resolve(IID_IInterceptor,
+                             (void**)getter_AddRefs(interceptor));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  InterceptorTargetPtr targetInterface;
+  hr = interceptor->GetTargetForIID(iid, targetInterface);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // (2) Execute the method call syncrhonously on the main thread
+  RefPtr<HandoffRunnable> handoffInfo(new HandoffRunnable(aFrame,
+                                                          targetInterface.get()));
+  if (!mInvoker.Invoke(do_AddRef(handoffInfo))) {
+    MOZ_ASSERT(false);
+    return E_UNEXPECTED;
+  }
+  hr = handoffInfo->GetResult();
+  MOZ_ASSERT(SUCCEEDED(hr));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // (3) Log *before* wrapping outputs so that the log will contain pointers to
+  // the true target interface, not the wrapped ones.
+  InterceptorLog::Event(aFrame, targetInterface.get());
+
+  // (4) Scan the function call for outparams that contain interface pointers.
+  // Those will need to be wrapped with MainThreadHandoff so that they too will
+  // be exeuted on the main thread.
+
+  hr = aFrame->GetReturnValue();
+  if (FAILED(hr)) {
+    // If the call resulted in an error then there's not going to be anything
+    // that needs to be wrapped.
+    return S_OK;
+  }
+
+  // (5) Scan the outputs looking for any outparam interfaces that need wrapping.
+  // NB: WalkFrame does not correctly handle array outparams. It processes the
+  // first element of an array but not the remaining elements (if any).
+  hr = aFrame->WalkFrame(CALLFRAME_WALK_OUT, this);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // (6) Unfortunately ICallFrame::WalkFrame does not correctly handle array
+  // outparams. Instead, we find out whether anybody has called
+  // mscom::RegisterArrayData to supply array parameter information and use it
+  // if available. This is a terrible hack, but it works for the short term. In
+  // the longer term we want to be able to use COM proxy/stub metadata to
+  // resolve array information for us.
+  const ArrayData* arrayData = FindArrayData(iid, method);
+  if (arrayData) {
+    hr = FixArrayElements(aFrame, *arrayData);
+    if (FAILED(hr)) {
+      return hr;
+    }
+  }
+
+  return S_OK;
+}
+
+static PVOID
+ResolveArrayPtr(VARIANT& aVariant)
+{
+  if (!(aVariant.vt & VT_BYREF)) {
+    return nullptr;
+  }
+  return aVariant.byref;
+}
+
+static PVOID*
+ResolveInterfacePtr(PVOID aArrayPtr, VARTYPE aVartype, LONG aIndex)
+{
+  if (aVartype != (VT_VARIANT | VT_BYREF)) {
+    IUnknown** ifaceArray = reinterpret_cast<IUnknown**>(aArrayPtr);
+    return reinterpret_cast<PVOID*>(&ifaceArray[aIndex]);
+  }
+  VARIANT* variantArray = reinterpret_cast<VARIANT*>(aArrayPtr);
+  VARIANT& element = variantArray[aIndex];
+  return &element.byref;
+}
+
+HRESULT
+MainThreadHandoff::FixArrayElements(ICallFrame* aFrame,
+                                    const ArrayData& aArrayData)
+{
+  // Extract the array length
+  VARIANT paramVal;
+  HRESULT hr = aFrame->GetParam(aArrayData.mLengthParamIndex, &paramVal);
+  MOZ_ASSERT(paramVal.vt == (VT_I4 | VT_BYREF) ||
+             paramVal.vt == (VT_UI4 | VT_BYREF));
+  if (FAILED(hr) || (paramVal.vt != (VT_I4 | VT_BYREF) &&
+                     paramVal.vt != (VT_UI4 | VT_BYREF))) {
+    return hr;
+  }
+
+  const LONG arrayLength = *(paramVal.plVal);
+  if (arrayLength <= 1) {
+    // Nothing needs to be processed (we skip index 0)
+    return S_OK;
+  }
+
+  // Extract the array parameter
+  hr = aFrame->GetParam(aArrayData.mArrayParamIndex, &paramVal);
+  if (FAILED(hr)) {
+    return hr;
+  }
+  PVOID arrayPtr = ResolveArrayPtr(paramVal);
+  MOZ_ASSERT(arrayPtr);
+  if (!arrayPtr) {
+    return DISP_E_BADVARTYPE;
+  }
+
+  // Start index is 1 because ICallFrame::WalkFrame already took care of index
+  // 0. We walk the remaining elements of the array and invoke OnWalkInterface
+  // to wrap each one, just as ICallFrame::WalkFrame would do.
+  for (LONG index = 1; index < arrayLength; ++index) {
+    hr = OnWalkInterface(aArrayData.mArrayParamIid,
+                         ResolveInterfacePtr(arrayPtr, paramVal.vt, index),
+                         FALSE, TRUE);
+    if (FAILED(hr)) {
+      return hr;
+    }
+  }
+  return S_OK;
+}
+
+HRESULT
+MainThreadHandoff::SetInterceptor(IWeakReference* aInterceptor)
+{
+  mInterceptor = aInterceptor;
+  return S_OK;
+}
+
+HRESULT
+MainThreadHandoff::OnWalkInterface(REFIID aIid, PVOID* aInterface,
+                                   BOOL aIsInParam, BOOL aIsOutParam)
+{
+  MOZ_ASSERT(aInterface && aIsOutParam);
+  if (!aInterface || !aIsOutParam) {
+    return E_UNEXPECTED;
+  }
+
+  // Adopt aInterface for the time being. We can't touch its refcount off
+  // the main thread, so we'll use STAUniquePtr so that we can safely
+  // Release() it if necessary.
+  STAUniquePtr<IUnknown> origInterface(static_cast<IUnknown*>(*aInterface));
+  *aInterface = nullptr;
+
+  // First make sure that aInterface isn't a proxy - we don't want to wrap
+  // those.
+  if (IsProxy(origInterface.get())) {
+    *aInterface = origInterface.release();
+    return S_OK;
+  }
+
+  RefPtr<IInterceptor> interceptor;
+  HRESULT hr = mInterceptor->Resolve(IID_IInterceptor,
+                                     (void**) getter_AddRefs(interceptor));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // Now make sure that origInterface isn't referring to the same IUnknown
+  // as an interface that we are already managing. We can determine this by
+  // querying (NOT casting!) both objects for IUnknown and then comparing the
+  // resulting pointers.
+  InterceptorTargetPtr existingTarget;
+  hr = interceptor->GetTargetForIID(aIid, existingTarget);
+  if (SUCCEEDED(hr)) {
+    bool areIUnknownsEqual = false;
+
+    // This check must be done on the main thread
+    auto checkFn = [&existingTarget, &origInterface, &areIUnknownsEqual]() -> void {
+      RefPtr<IUnknown> unkExisting;
+      HRESULT hrExisting =
+        existingTarget->QueryInterface(IID_IUnknown,
+                                       (void**)getter_AddRefs(unkExisting));
+      RefPtr<IUnknown> unkNew;
+      HRESULT hrNew =
+        origInterface->QueryInterface(IID_IUnknown,
+                                      (void**)getter_AddRefs(unkNew));
+      areIUnknownsEqual = SUCCEEDED(hrExisting) && SUCCEEDED(hrNew) &&
+                          unkExisting == unkNew;
+    };
+
+    MainThreadInvoker invoker;
+    if (invoker.Invoke(NS_NewRunnableFunction(checkFn)) && areIUnknownsEqual) {
+      // The existing interface and the new interface both belong to the same
+      // target object. Let's just use the existing one.
+      void* intercepted = nullptr;
+      hr = interceptor->GetInterceptorForIID(aIid, &intercepted);
+      if (FAILED(hr)) {
+        return hr;
+      }
+      *aInterface = intercepted;
+      return S_OK;
+    }
+  }
+
+  // Now create a new MainThreadHandoff wrapper...
+  RefPtr<IInterceptorSink> handoff;
+  hr = MainThreadHandoff::Create(getter_AddRefs(handoff));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  RefPtr<IUnknown> wrapped;
+  hr = Interceptor::Create(origInterface, handoff, aIid, getter_AddRefs(wrapped));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // And replace the original interface pointer with the wrapped one.
+  wrapped.forget(reinterpret_cast<IUnknown**>(aInterface));
+
+  return S_OK;
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadHandoff.h
@@ -0,0 +1,71 @@
+/* -*- 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_MainThreadHandoff_h
+#define mozilla_mscom_MainThreadHandoff_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/mscom/Interceptor.h"
+#include "mozilla/mscom/MainThreadInvoker.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace mscom {
+
+struct ArrayData;
+
+class MainThreadHandoff : public IInterceptorSink
+                        , public ICallFrameWalker
+{
+public:
+  static HRESULT Create(IInterceptorSink** aOutput);
+
+  template <typename Interface>
+  static HRESULT WrapInterface(STAUniquePtr<Interface>& aTargetInterface,
+                               Interface** aOutInterface)
+  {
+    MOZ_ASSERT(!IsProxy(aTargetInterface.get()));
+    RefPtr<IInterceptorSink> handoff;
+    HRESULT hr = MainThreadHandoff::Create(getter_AddRefs(handoff));
+    if (FAILED(hr)) {
+      return hr;
+    }
+    return CreateInterceptor(aTargetInterface, handoff, aOutInterface);
+  }
+
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // ICallFrameEvents
+  STDMETHODIMP OnCall(ICallFrame* aFrame) override;
+
+  // IInterceptorSink
+  STDMETHODIMP SetInterceptor(IWeakReference* aInterceptor) override;
+
+  // ICallFrameWalker
+  STDMETHODIMP OnWalkInterface(REFIID aIid, PVOID* aInterface, BOOL aIsInParam,
+                               BOOL aIsOutParam) override;
+
+private:
+  MainThreadHandoff();
+  ~MainThreadHandoff();
+  HRESULT FixArrayElements(ICallFrame* aFrame,
+                           const ArrayData& aArrayData);
+
+private:
+  ULONG                   mRefCnt;
+  MainThreadInvoker       mInvoker;
+  RefPtr<IWeakReference>  mInterceptor;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_MainThreadHandoff_h
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadInvoker.cpp
@@ -0,0 +1,148 @@
+/* -*- 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/MainThreadInvoker.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+#include "private/prpriv.h" // For PR_GetThreadID
+
+#include <winternl.h> // For NTSTATUS and NTAPI
+
+namespace {
+
+class SyncRunnable : public mozilla::Runnable
+{
+public:
+  SyncRunnable(HANDLE aEvent, already_AddRefed<nsIRunnable>&& aRunnable)
+    : mDoneEvent(aEvent)
+    , mRunnable(aRunnable)
+  {
+    MOZ_ASSERT(aEvent);
+    MOZ_ASSERT(mRunnable);
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mRunnable->Run();
+    ::SetEvent(mDoneEvent);
+    return NS_OK;
+  }
+
+private:
+  HANDLE                mDoneEvent;
+  nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID);
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+HANDLE MainThreadInvoker::sMainThread = nullptr;
+StaticRefPtr<nsIRunnable> MainThreadInvoker::sAlertRunnable;
+
+/* static */ bool
+MainThreadInvoker::InitStatics()
+{
+  nsCOMPtr<nsIThread> mainThread;
+  nsresult rv = ::NS_GetMainThread(getter_AddRefs(mainThread));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  PRThread* mainPrThread = nullptr;
+  rv = mainThread->GetPRThread(&mainPrThread);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  PRUint32 tid = ::PR_GetThreadID(mainPrThread);
+  sMainThread = ::OpenThread(SYNCHRONIZE | THREAD_SET_CONTEXT, FALSE, tid);
+  if (!sMainThread) {
+    return false;
+  }
+  NtTestAlertPtr NtTestAlert =
+    reinterpret_cast<NtTestAlertPtr>(
+        ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert"));
+  sAlertRunnable = ::NS_NewRunnableFunction([NtTestAlert]() -> void {
+    // We're using NtTestAlert() instead of SleepEx() so that the main thread
+    // never gives up its quantum if there are no APCs pending.
+    NtTestAlert();
+  }).take();
+  if (sAlertRunnable) {
+    ClearOnShutdown(&sAlertRunnable);
+  }
+  return !!sAlertRunnable;
+}
+
+MainThreadInvoker::MainThreadInvoker()
+  : mDoneEvent(::CreateEvent(nullptr, FALSE, FALSE, nullptr))
+{
+  static const bool gotStatics = InitStatics();
+  MOZ_ASSERT(gotStatics);
+}
+
+MainThreadInvoker::~MainThreadInvoker()
+{
+  if (mDoneEvent) {
+    ::CloseHandle(mDoneEvent);
+  }
+}
+
+bool
+MainThreadInvoker::WaitForCompletion(DWORD aTimeout)
+{
+  HANDLE handles[] = {mDoneEvent, sMainThread};
+  DWORD waitResult = ::WaitForMultipleObjects(ArrayLength(handles), handles,
+                                              FALSE, aTimeout);
+  return waitResult == WAIT_OBJECT_0;
+}
+
+bool
+MainThreadInvoker::Invoke(already_AddRefed<nsIRunnable>&& aRunnable,
+                          DWORD aTimeout)
+{
+  nsCOMPtr<nsIRunnable> runnable(Move(aRunnable));
+  if (!runnable) {
+    return false;
+  }
+  if (NS_IsMainThread()) {
+    runnable->Run();
+    return true;
+  }
+  RefPtr<SyncRunnable> wrappedRunnable(new SyncRunnable(mDoneEvent,
+                                                        runnable.forget()));
+  // Make sure that wrappedRunnable remains valid while sitting in the APC queue
+  wrappedRunnable->AddRef();
+  if (!::QueueUserAPC(&MainThreadAPC, sMainThread,
+                      reinterpret_cast<UINT_PTR>(wrappedRunnable.get()))) {
+    // Enqueue failed so cancel the above AddRef
+    wrappedRunnable->Release();
+    return false;
+  }
+  // We should enqueue a call to NtTestAlert() so that the main thread will
+  // check for APCs during event processing. If we omit this then the main
+  // thread will not check its APC queue until it is idle. Note that failing to
+  // dispatch this event is non-fatal, but it will delay execution of the APC.
+  NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(sAlertRunnable)));
+  return WaitForCompletion(aTimeout);
+}
+
+/* static */ VOID CALLBACK
+MainThreadInvoker::MainThreadAPC(ULONG_PTR aParam)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<SyncRunnable> runnable(already_AddRefed<SyncRunnable>(
+                                  reinterpret_cast<SyncRunnable*>(aParam)));
+  runnable->Run();
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/MainThreadInvoker.h
@@ -0,0 +1,44 @@
+/* -*- 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_MainThreadInvoker_h
+#define mozilla_mscom_MainThreadInvoker_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/StaticPtr.h"
+
+#include <windows.h>
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace mscom {
+
+class MainThreadInvoker
+{
+public:
+  MainThreadInvoker();
+  ~MainThreadInvoker();
+
+  bool WaitForCompletion(DWORD aTimeout = INFINITE);
+  bool Invoke(already_AddRefed<nsIRunnable>&& aRunnable,
+              DWORD aTimeout = INFINITE);
+  HANDLE GetTargetThread() const { return sMainThread; }
+
+private:
+  static bool InitStatics();
+  static VOID CALLBACK MainThreadAPC(ULONG_PTR aParam);
+
+  HANDLE  mDoneEvent;
+
+  static HANDLE sMainThread;
+  static StaticRefPtr<nsIRunnable> sAlertRunnable;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_MainThreadInvoker_h
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/ProxyStream.cpp
@@ -0,0 +1,202 @@
+/* -*- 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 "DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/mscom/ProxyStream.h"
+#include "mozilla/mscom/utils.h"
+
+#include "mozilla/Move.h"
+
+#include <windows.h>
+#include <objbase.h>
+#include <shlwapi.h>
+
+namespace mozilla {
+namespace mscom {
+
+ProxyStream::ProxyStream()
+  : mGlobalLockedBuf(nullptr)
+  , mHGlobal(nullptr)
+  , mBufSize(0)
+{
+}
+
+// GetBuffer() fails with this variant, but that's okay because we're just
+// reconstructing the stream from a buffer anyway.
+ProxyStream::ProxyStream(const BYTE* aInitBuf, const int aInitBufSize)
+  : mStream(InitStream(aInitBuf, static_cast<const UINT>(aInitBufSize)))
+  , mGlobalLockedBuf(nullptr)
+  , mHGlobal(nullptr)
+  , mBufSize(aInitBufSize)
+{
+  if (!aInitBufSize) {
+    // We marshaled a nullptr. Nothing else to do here.
+    return;
+  }
+  // NB: We can't check for a null mStream until after we have checked for
+  // the zero aInitBufSize above. This is because InitStream will also fail
+  // in that case, even though marshaling a nullptr is allowable.
+  MOZ_ASSERT(mStream);
+  if (!mStream) {
+    return;
+  }
+
+  // We need to convert to an interface here otherwise we mess up const
+  // correctness with IPDL. We'll request an IUnknown and then QI the
+  // actual interface later.
+
+  auto marshalFn = [&]() -> void
+  {
+    IUnknown* rawUnmarshaledProxy = nullptr;
+    // OK to forget mStream when calling into this function because the stream
+    // gets released even if the unmarshaling part fails.
+    DebugOnly<HRESULT> hr =
+      ::CoGetInterfaceAndReleaseStream(mStream.forget().take(), IID_IUnknown,
+                                       (void**)&rawUnmarshaledProxy);
+    MOZ_ASSERT(SUCCEEDED(hr));
+    mUnmarshaledProxy.reset(rawUnmarshaledProxy);
+  };
+
+  if (XRE_IsParentProcess()) {
+    // We'll marshal this stuff directly using the current thread, therefore its
+    // proxy will reside in the same apartment as the current thread.
+    marshalFn();
+  } else {
+    // When marshaling in child processes, we want to force the MTA.
+    EnsureMTA mta(marshalFn);
+  }
+}
+
+already_AddRefed<IStream>
+ProxyStream::InitStream(const BYTE* aInitBuf, const UINT aInitBufSize)
+{
+  // Need to link to this as ordinal 12 for Windows XP
+  static DynamicallyLinkedFunctionPtr<decltype(&::SHCreateMemStream)>
+    pSHCreateMemStream(L"shlwapi.dll", reinterpret_cast<const char*>(12));
+  if (!pSHCreateMemStream) {
+    return nullptr;
+  }
+  return already_AddRefed<IStream>(pSHCreateMemStream(aInitBuf, aInitBufSize));
+}
+
+ProxyStream::ProxyStream(ProxyStream&& aOther)
+{
+  *this = mozilla::Move(aOther);
+}
+
+ProxyStream&
+ProxyStream::operator=(ProxyStream&& aOther)
+{
+  mStream = mozilla::Move(aOther.mStream);
+  mGlobalLockedBuf = aOther.mGlobalLockedBuf;
+  aOther.mGlobalLockedBuf = nullptr;
+  mHGlobal = aOther.mHGlobal;
+  aOther.mHGlobal = nullptr;
+  mBufSize = aOther.mBufSize;
+  aOther.mBufSize = 0;
+  return *this;
+}
+
+ProxyStream::~ProxyStream()
+{
+  if (mHGlobal && mGlobalLockedBuf) {
+    DebugOnly<BOOL> result = ::GlobalUnlock(mHGlobal);
+    MOZ_ASSERT(!result && ::GetLastError() == NO_ERROR);
+    // ::GlobalFree() is called implicitly when mStream is released
+  }
+}
+
+const BYTE*
+ProxyStream::GetBuffer(int& aReturnedBufSize) const
+{
+  aReturnedBufSize = 0;
+  if (!mStream) {
+    return nullptr;
+  }
+  if (!mGlobalLockedBuf) {
+    return nullptr;
+  }
+  aReturnedBufSize = mBufSize;
+  return mGlobalLockedBuf;
+}
+
+bool
+ProxyStream::GetInterface(REFIID aIID, void** aOutInterface) const
+{
+  // We should not have a locked buffer on this side
+  MOZ_ASSERT(!mGlobalLockedBuf);
+  MOZ_ASSERT(aOutInterface);
+
+  if (!aOutInterface) {
+    return false;
+  }
+
+  if (!mUnmarshaledProxy) {
+    *aOutInterface = nullptr;
+    return true;
+  }
+
+  HRESULT hr = E_UNEXPECTED;
+  auto qiFn = [&]() -> void
+  {
+    hr = mUnmarshaledProxy->QueryInterface(aIID, aOutInterface);
+  };
+
+  if (XRE_IsParentProcess()) {
+    qiFn();
+  } else {
+    // mUnmarshaledProxy requires that we execute this in the MTA
+    EnsureMTA mta(qiFn);
+  }
+  return SUCCEEDED(hr);
+}
+
+ProxyStream::ProxyStream(REFIID aIID, IUnknown* aObject)
+  : mGlobalLockedBuf(nullptr)
+  , mHGlobal(nullptr)
+  , mBufSize(0)
+{
+  RefPtr<IStream> stream;
+  HGLOBAL hglobal = NULL;
+
+  auto marshalFn = [&]() -> void
+  {
+    HRESULT hr = ::CreateStreamOnHGlobal(nullptr, TRUE, getter_AddRefs(stream));
+    if (FAILED(hr)) {
+      return;
+    }
+
+    hr = ::CoMarshalInterface(stream, aIID, aObject, MSHCTX_LOCAL, nullptr,
+                              MSHLFLAGS_NORMAL);
+    if (FAILED(hr)) {
+      return;
+    }
+
+    hr = ::GetHGlobalFromStream(stream, &hglobal);
+    MOZ_ASSERT(SUCCEEDED(hr));
+  };
+
+  if (XRE_IsParentProcess()) {
+    // We'll marshal this stuff directly using the current thread, therefore its
+    // stub will reside in the same apartment as the current thread.
+    marshalFn();
+  } else {
+    // When marshaling in child processes, we want to force the MTA.
+    EnsureMTA mta(marshalFn);
+  }
+
+  mStream = mozilla::Move(stream);
+  if (hglobal) {
+    mGlobalLockedBuf = reinterpret_cast<BYTE*>(::GlobalLock(hglobal));
+    mHGlobal = hglobal;
+    mBufSize = static_cast<int>(::GlobalSize(hglobal));
+  }
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/ProxyStream.h
@@ -0,0 +1,64 @@
+/* -*- 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_ProxyStream_h
+#define mozilla_mscom_ProxyStream_h
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/mscom/Ptr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace mscom {
+
+class ProxyStream
+{
+public:
+  ProxyStream();
+  ProxyStream(REFIID aIID, IUnknown* aObject);
+  ProxyStream(const BYTE* aInitBuf, const int aInitBufSize);
+
+  ~ProxyStream();
+
+  // Not copyable because this would mess up the COM marshaling.
+  ProxyStream(const ProxyStream& aOther) = delete;
+  ProxyStream& operator=(const ProxyStream& aOther) = delete;
+
+  ProxyStream(ProxyStream&& aOther);
+  ProxyStream& operator=(ProxyStream&& aOther);
+
+  inline bool IsValid() const
+  {
+    // This check must be exclusive OR
+    return (mStream && !mUnmarshaledProxy) || (mUnmarshaledProxy && !mStream);
+  }
+
+  bool GetInterface(REFIID aIID, void** aOutInterface) const;
+  const BYTE* GetBuffer(int& aReturnedBufSize) const;
+
+  bool operator==(const ProxyStream& aOther) const
+  {
+    return this == &aOther;
+  }
+
+private:
+  already_AddRefed<IStream> InitStream(const BYTE* aInitBuf,
+                                       const UINT aInitBufSize);
+
+private:
+  RefPtr<IStream> mStream;
+  BYTE*           mGlobalLockedBuf;
+  HGLOBAL         mHGlobal;
+  int             mBufSize;
+  ProxyUniquePtr<IUnknown> mUnmarshaledProxy;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_ProxyStream_h
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Ptr.h
@@ -0,0 +1,111 @@
+/* -*- 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_Ptr_h
+#define mozilla_mscom_Ptr_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/UniquePtr.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+/**
+ * The glue code in mozilla::mscom often needs to pass around interface pointers
+ * belonging to a different apartment from the current one. We must not touch
+ * the reference counts of those objects on the wrong apartment. By using these
+ * UniquePtr specializations, we may ensure that the reference counts are always
+ * handled correctly.
+ */
+
+namespace mozilla {
+namespace mscom {
+
+namespace detail {
+
+template <typename T>
+struct MainThreadRelease
+{
+  void operator()(T* aPtr)
+  {
+    if (!aPtr) {
+      return;
+    }
+    if (NS_IsMainThread()) {
+      aPtr->Release();
+      return;
+    }
+    DebugOnly<nsresult> rv =
+      NS_DispatchToMainThread(NewNonOwningRunnableMethod(aPtr,
+                                                         &T::Release));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+};
+
+template <typename T>
+struct MTARelease
+{
+  void operator()(T* aPtr)
+  {
+    if (!aPtr) {
+      return;
+    }
+    EnsureMTA([&]() -> void
+    {
+      aPtr->Release();
+    });
+  }
+};
+
+template <typename T>
+struct MTAReleaseInChildProcess
+{
+  void operator()(T* aPtr)
+  {
+    if (!aPtr) {
+      return;
+    }
+    if (XRE_IsParentProcess()) {
+      MOZ_ASSERT(NS_IsMainThread());
+      aPtr->Release();
+      return;
+    }
+    EnsureMTA([&]() -> void
+    {
+      aPtr->Release();
+    });
+  }
+};
+
+struct InterceptorTargetDeleter
+{
+  void operator()(IUnknown* aPtr)
+  {
+    // We intentionally do not touch the refcounts of interceptor targets!
+  }
+};
+
+} // namespace detail
+
+template <typename T>
+using STAUniquePtr = mozilla::UniquePtr<T, detail::MainThreadRelease<T>>;
+
+template <typename T>
+using MTAUniquePtr = mozilla::UniquePtr<T, detail::MTARelease<T>>;
+
+template <typename T>
+using ProxyUniquePtr = mozilla::UniquePtr<T, detail::MTAReleaseInChildProcess<T>>;
+
+using InterceptorTargetPtr =
+  mozilla::UniquePtr<IUnknown, detail::InterceptorTargetDeleter>;
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Ptr_h
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Registration.cpp
@@ -0,0 +1,332 @@
+/* -*- 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/. */
+
+// COM registration data structures are built with C code, so we need to
+// simulate that in our C++ code by defining CINTERFACE before including
+// anything else that could possibly pull in Windows header files.
+#define CINTERFACE
+
+#include "mozilla/mscom/Registration.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Pair.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTArray.h"
+
+#include <oaidl.h>
+#include <objidl.h>
+#include <rpcproxy.h>
+#include <shlwapi.h>
+
+/* This code MUST NOT use any non-inlined internal Mozilla APIs, as it will be
+   compiled into DLLs that COM may load into non-Mozilla processes! */
+
+namespace {
+
+// This function is defined in generated code for proxy DLLs but is not declared
+// in rpcproxy.h, so we need this typedef.
+typedef void (RPC_ENTRY *GetProxyDllInfoFnPtr)(const ProxyFileInfo*** aInfo,
+                                               const CLSID** aId);
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace mscom {
+
+static bool
+BuildLibPath(RegistrationFlags aFlags, wchar_t* aBuffer, size_t aBufferLen,
+             const wchar_t* aLeafName)
+{
+  if (aFlags == RegistrationFlags::eUseBinDirectory) {
+    HMODULE thisModule = nullptr;
+    if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                           GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                           reinterpret_cast<LPCTSTR>(&RegisterProxy),
+                           &thisModule)) {
+      return false;
+    }
+    DWORD fileNameResult = GetModuleFileName(thisModule, aBuffer, aBufferLen);
+    if (!fileNameResult || (fileNameResult == aBufferLen &&
+          ::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
+      return false;
+    }
+    if (!PathRemoveFileSpec(aBuffer)) {
+      return false;
+    }
+  } else if (aFlags == RegistrationFlags::eUseSystemDirectory) {
+    UINT result = GetSystemDirectoryW(aBuffer, static_cast<UINT>(aBufferLen));
+    if (!result || result > aBufferLen) {
+      return false;
+    }
+  } else {
+    return false;
+  }
+
+  if (!PathAppend(aBuffer, aLeafName)) {
+    return false;
+  }
+  return true;
+}
+
+UniquePtr<RegisteredProxy>
+RegisterProxy(const wchar_t* aLeafName, RegistrationFlags aFlags)
+{
+  wchar_t modulePathBuf[MAX_PATH + 1] = {0};
+  if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf),
+                    aLeafName)) {
+    return nullptr;
+  }
+
+  HMODULE proxyDll = LoadLibrary(modulePathBuf);
+  if (!proxyDll) {
+    return nullptr;
+  }
+
+  auto DllGetClassObjectFn = reinterpret_cast<LPFNGETCLASSOBJECT>(
+      GetProcAddress(proxyDll, "DllGetClassObject"));
+  if (!DllGetClassObjectFn) {
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  auto GetProxyDllInfoFn = reinterpret_cast<GetProxyDllInfoFnPtr>(
+      GetProcAddress(proxyDll, "GetProxyDllInfo"));
+  if (!GetProxyDllInfoFn) {
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  const ProxyFileInfo** proxyInfo = nullptr;
+  const CLSID* proxyClsid = nullptr;
+  GetProxyDllInfoFn(&proxyInfo, &proxyClsid);
+  if (!proxyInfo || !proxyClsid) {
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  IUnknown* classObject = nullptr;
+  HRESULT hr = DllGetClassObjectFn(*proxyClsid, IID_IUnknown,
+                                   (void**) &classObject);
+  if (FAILED(hr)) {
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  DWORD regCookie;
+  hr = CoRegisterClassObject(*proxyClsid, classObject, CLSCTX_INPROC_SERVER,
+                             REGCLS_MULTIPLEUSE, &regCookie);
+  if (FAILED(hr)) {
+    classObject->lpVtbl->Release(classObject);
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  ITypeLib* typeLib = nullptr;
+  hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib);
+  MOZ_ASSERT(SUCCEEDED(hr));
+  if (FAILED(hr)) {
+    CoRevokeClassObject(regCookie);
+    classObject->lpVtbl->Release(classObject);
+    FreeLibrary(proxyDll);
+    return nullptr;
+  }
+
+  // RegisteredProxy takes ownership of proxyDll, classObject, and typeLib
+  // references
+  auto result(MakeUnique<RegisteredProxy>(reinterpret_cast<uintptr_t>(proxyDll),
+                                          classObject, regCookie, typeLib));
+
+  while (*proxyInfo) {
+    const ProxyFileInfo& curInfo = **proxyInfo;
+    for (unsigned short i = 0, e = curInfo.TableSize; i < e; ++i) {
+      hr = CoRegisterPSClsid(*(curInfo.pStubVtblList[i]->header.piid),
+                             *proxyClsid);
+      if (FAILED(hr)) {
+        return nullptr;
+      }
+    }
+    ++proxyInfo;
+  }
+
+  return result;
+}
+
+UniquePtr<RegisteredProxy>
+RegisterTypelib(const wchar_t* aLeafName, RegistrationFlags aFlags)
+{
+  wchar_t modulePathBuf[MAX_PATH + 1] = {0};
+  if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf),
+                    aLeafName)) {
+    return nullptr;
+  }
+
+  ITypeLib* typeLib = nullptr;
+  HRESULT hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib);
+  if (FAILED(hr)) {
+    return nullptr;
+  }
+
+  // RegisteredProxy takes ownership of typeLib reference
+  auto result(MakeUnique<RegisteredProxy>(typeLib));
+  return result;
+}
+
+RegisteredProxy::RegisteredProxy(uintptr_t aModule, IUnknown* aClassObject,
+                                 uint32_t aRegCookie, ITypeLib* aTypeLib)
+  : mModule(aModule)
+  , mClassObject(aClassObject)
+  , mRegCookie(aRegCookie)
+  , mTypeLib(aTypeLib)
+{
+  MOZ_ASSERT(aClassObject);
+  MOZ_ASSERT(aTypeLib);
+  AddToRegistry(this);
+}
+
+RegisteredProxy::RegisteredProxy(ITypeLib* aTypeLib)
+  : mModule(0)
+  , mClassObject(nullptr)
+  , mRegCookie(0)
+  , mTypeLib(aTypeLib)
+{
+  MOZ_ASSERT(aTypeLib);
+  AddToRegistry(this);
+}
+
+RegisteredProxy::~RegisteredProxy()
+{
+  DeleteFromRegistry(this);
+  if (mTypeLib) {
+    mTypeLib->lpVtbl->Release(mTypeLib);
+  }
+  if (mClassObject) {
+    ::CoRevokeClassObject(mRegCookie);
+    mClassObject->lpVtbl->Release(mClassObject);
+  }
+  if (mModule) {
+    ::FreeLibrary(reinterpret_cast<HMODULE>(mModule));
+  }
+}
+
+RegisteredProxy::RegisteredProxy(RegisteredProxy&& aOther)
+{
+  *this = mozilla::Forward<RegisteredProxy>(aOther);
+}
+
+RegisteredProxy&
+RegisteredProxy::operator=(RegisteredProxy&& aOther)
+{
+  mModule = aOther.mModule;
+  aOther.mModule = 0;
+  mClassObject = aOther.mClassObject;
+  aOther.mClassObject = nullptr;
+  mRegCookie = aOther.mRegCookie;
+  aOther.mRegCookie = 0;
+  mTypeLib = aOther.mTypeLib;
+  aOther.mTypeLib = nullptr;
+  return *this;
+}
+
+HRESULT
+RegisteredProxy::GetTypeInfoForInterface(REFIID aIid,
+                                         ITypeInfo** aOutTypeInfo) const
+{
+  if (!aOutTypeInfo) {
+    return E_INVALIDARG;
+  }
+  if (!mTypeLib) {
+    return E_UNEXPECTED;
+  }
+  return mTypeLib->lpVtbl->GetTypeInfoOfGuid(mTypeLib, aIid, aOutTypeInfo);
+}
+
+static StaticAutoPtr<nsTArray<RegisteredProxy*>> sRegistry;
+static StaticAutoPtr<Mutex> sRegMutex;
+static StaticAutoPtr<nsTArray<Pair<const ArrayData*, size_t>>> sArrayData;
+
+static Mutex&
+GetMutex()
+{
+  static Mutex& mutex = []() -> Mutex& {
+    if (!sRegMutex) {
+      sRegMutex = new Mutex("RegisteredProxy::sRegMutex");
+      ClearOnShutdown(&sRegMutex, ShutdownPhase::ShutdownThreads);
+    }
+    return *sRegMutex;
+  }();
+  return mutex;
+}
+
+/* static */ bool
+RegisteredProxy::Find(REFIID aIid, ITypeInfo** aTypeInfo)
+{
+  MutexAutoLock lock(GetMutex());
+  nsTArray<RegisteredProxy*>& registry = *sRegistry;
+  for (uint32_t idx = 0, len = registry.Length(); idx < len; ++idx) {
+    if (SUCCEEDED(registry[idx]->GetTypeInfoForInterface(aIid, aTypeInfo))) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/* static */ void
+RegisteredProxy::AddToRegistry(RegisteredProxy* aProxy)
+{
+  MutexAutoLock lock(GetMutex());
+  if (!sRegistry) {
+    sRegistry = new nsTArray<RegisteredProxy*>();
+    ClearOnShutdown(&sRegistry);
+  }
+  sRegistry->AppendElement(aProxy);
+}
+
+/* static */ void
+RegisteredProxy::DeleteFromRegistry(RegisteredProxy* aProxy)
+{
+  MutexAutoLock lock(GetMutex());
+  sRegistry->RemoveElement(aProxy);
+}
+
+void
+RegisterArrayData(const ArrayData* aArrayData, size_t aLength)
+{
+  MutexAutoLock lock(GetMutex());
+  if (!sArrayData) {
+    sArrayData = new nsTArray<Pair<const ArrayData*, size_t>>();
+    ClearOnShutdown(&sArrayData, ShutdownPhase::ShutdownThreads);
+  }
+  sArrayData->AppendElement(MakePair(aArrayData, aLength));
+}
+
+const ArrayData*
+FindArrayData(REFIID aIid, ULONG aMethodIndex)
+{
+  MutexAutoLock lock(GetMutex());
+  if (!sArrayData) {
+    return nullptr;
+  }
+  for (uint32_t outerIdx = 0, outerLen = sArrayData->Length();
+       outerIdx < outerLen; ++outerIdx) {
+    auto& data = sArrayData->ElementAt(outerIdx);
+    for (size_t innerIdx = 0, innerLen = data.second(); innerIdx < innerLen;
+         ++innerIdx) {
+      const ArrayData* array = data.first();
+      if (aIid == array[innerIdx].mIid &&
+          aMethodIndex == array[innerIdx].mMethodIndex) {
+        return &array[innerIdx];
+      }
+    }
+  }
+  return nullptr;
+}
+
+} // namespace mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/Registration.h
@@ -0,0 +1,132 @@
+/* -*- 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_Registration_h
+#define mozilla_mscom_Registration_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include <objbase.h>
+
+struct ITypeInfo;
+struct ITypeLib;
+
+namespace mozilla {
+namespace mscom {
+
+/**
+ * Assumptions:
+ * (1) The DLL exports GetProxyDllInfo. This is not exported by default; it must
+ *     be specified in the EXPORTS section of the DLL's module definition file.
+ */
+class RegisteredProxy
+{
+public:
+  RegisteredProxy(uintptr_t aModule, IUnknown* aClassObject,
+                  uint32_t aRegCookie, ITypeLib* aTypeLib);
+  explicit RegisteredProxy(ITypeLib* aTypeLib);
+  RegisteredProxy(RegisteredProxy&& aOther);
+  RegisteredProxy& operator=(RegisteredProxy&& aOther);
+
+  ~RegisteredProxy();
+
+  HRESULT GetTypeInfoForInterface(REFIID aIid, ITypeInfo** aOutTypeInfo) const;
+
+  static bool Find(REFIID aIid, ITypeInfo** aOutTypeInfo);
+
+private:
+  RegisteredProxy() = delete;
+  RegisteredProxy(RegisteredProxy&) = delete;
+  RegisteredProxy& operator=(RegisteredProxy&) = delete;
+
+  static void AddToRegistry(RegisteredProxy* aProxy);
+  static void DeleteFromRegistry(RegisteredProxy* aProxy);
+
+private:
+  // Not using Windows types here: We shouldn't #include windows.h
+  // since it might pull in COM code which we want to do very carefully in
+  // Registration.cpp.
+  uintptr_t mModule;
+  IUnknown* mClassObject;
+  uint32_t  mRegCookie;
+  ITypeLib* mTypeLib;
+};
+
+enum class RegistrationFlags
+{
+  eUseBinDirectory,
+  eUseSystemDirectory
+};
+
+// For DLL files. Assumes corresponding TLB is embedded in resources.
+UniquePtr<RegisteredProxy> RegisterProxy(const wchar_t* aLeafName,
+                                         RegistrationFlags aFlags =
+                                           RegistrationFlags::eUseBinDirectory);
+// For standalone TLB files.
+UniquePtr<RegisteredProxy> RegisterTypelib(const wchar_t* aLeafName,
+                                           RegistrationFlags aFlags =
+                                             RegistrationFlags::eUseBinDirectory);
+
+/**
+ * The COM interceptor uses type library information to build its interface
+ * proxies. Unfortunately type libraries do not encode size_is and length_is
+ * annotations that have been specified in IDL. This structure allows us to
+ * explicitly declare such relationships so that the COM interceptor may
+ * be made aware of them.
+ */
+struct ArrayData
+{
+  ArrayData(REFIID aIid, ULONG aMethodIndex, ULONG aArrayParamIndex,
+            VARTYPE aArrayParamType, REFIID aArrayParamIid,
+            ULONG aLengthParamIndex)
+    : mIid(aIid)
+    , mMethodIndex(aMethodIndex)
+    , mArrayParamIndex(aArrayParamIndex)
+    , mArrayParamType(aArrayParamType)
+    , mArrayParamIid(aArrayParamIid)
+    , mLengthParamIndex(aLengthParamIndex)
+  {
+  }
+  ArrayData(const ArrayData& aOther)
+  {
+    *this = aOther;
+  }
+  ArrayData& operator=(const ArrayData& aOther)
+  {
+    mIid = aOther.mIid;
+    mMethodIndex = aOther.mMethodIndex;
+    mArrayParamIndex = aOther.mArrayParamIndex;
+    mArrayParamType = aOther.mArrayParamType;
+    mArrayParamIid = aOther.mArrayParamIid;
+    mLengthParamIndex = aOther.mLengthParamIndex;
+    return *this;
+  }
+  IID     mIid;
+  ULONG   mMethodIndex;
+  ULONG   mArrayParamIndex;
+  VARTYPE mArrayParamType;
+  IID     mArrayParamIid;
+  ULONG   mLengthParamIndex;
+};
+
+void RegisterArrayData(const ArrayData* aArrayData, size_t aLength);
+
+template <size_t N>
+inline void
+RegisterArrayData(const ArrayData (&aData)[N])
+{
+  RegisterArrayData(aData, N);
+}
+
+const ArrayData*
+FindArrayData(REFIID aIid, ULONG aMethodIndex);
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_Registration_h
+
--- a/ipc/mscom/Utils.cpp
+++ b/ipc/mscom/Utils.cpp
@@ -1,23 +1,56 @@
 /* -*- 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/. */
 
+// We need Windows 7 headers
+#ifdef NTDDI_VERSION
+#undef NTDDI_VERSION
+#endif
+#define NTDDI_VERSION 0x06010000
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0601
+
+#include "DynamicallyLinkedFunctionPtr.h"
 #include "mozilla/mscom/Utils.h"
 #include "mozilla/RefPtr.h"
 
 #include <objidl.h>
 
 namespace mozilla {
 namespace mscom {
 
 bool
+IsCurrentThreadMTA()
+{
+  static DynamicallyLinkedFunctionPtr<decltype(&::CoGetApartmentType)>
+    pCoGetApartmentType(L"ole32.dll", "CoGetApartmentType");
+
+  // There isn't really a thread-safe way to query this on Windows XP. In that
+  // case, we'll just return false since that assumption does no harm.
+  if (!pCoGetApartmentType) {
+    return false;
+  }
+
+  APTTYPE aptType;
+  APTTYPEQUALIFIER aptTypeQualifier;
+  HRESULT hr = pCoGetApartmentType(&aptType, &aptTypeQualifier);
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  return aptType == APTTYPE_MTA;
+}
+
+bool
 IsProxy(IUnknown* aUnknown)
 {
   if (!aUnknown) {
     return false;
   }
 
   // Only proxies implement this interface, so if it is present then we must
   // be dealing with a proxied object.
--- a/ipc/mscom/Utils.h
+++ b/ipc/mscom/Utils.h
@@ -7,15 +7,16 @@
 #ifndef mozilla_mscom_Utils_h
 #define mozilla_mscom_Utils_h
 
 struct IUnknown;
 
 namespace mozilla {
 namespace mscom {
 
+bool IsCurrentThreadMTA();
 bool IsProxy(IUnknown* aUnknown);
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_Utils_h
 
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.cpp
@@ -0,0 +1,193 @@
+/* -*- 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/mscom/WeakRef.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+namespace mscom {
+
+WeakReferenceSupport::WeakReferenceSupport(Flags aFlags)
+  : mRefCnt(1)
+  , mFlags(aFlags)
+{
+  ::InitializeCriticalSectionAndSpinCount(&mCS, 4000);
+}
+
+WeakReferenceSupport::~WeakReferenceSupport()
+{
+  MOZ_ASSERT(mWeakRefs.IsEmpty());
+  ::DeleteCriticalSection(&mCS);
+}
+
+HRESULT
+WeakReferenceSupport::QueryInterface(REFIID riid, void** ppv)
+{
+  AutoCriticalSection lock(&mCS);
+  RefPtr<IUnknown> punk;
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+  *ppv = nullptr;
+
+  // Raise the refcount for stabilization purposes during aggregation
+  RefPtr<IUnknown> kungFuDeathGrip(static_cast<IUnknown*>(this));
+
+  if (riid == IID_IUnknown || riid == IID_IWeakReferenceSource) {
+    punk = static_cast<IUnknown*>(this);
+  } else {
+    HRESULT hr = ThreadSafeQueryInterface(riid, getter_AddRefs(punk));
+    if (FAILED(hr)) {
+      return hr;
+    }
+  }
+
+  if (!punk) {
+    return E_NOINTERFACE;
+  }
+
+  punk.forget(ppv);
+  return S_OK;
+}
+
+ULONG
+WeakReferenceSupport::AddRef()
+{
+  AutoCriticalSection lock(&mCS);
+  return ++mRefCnt;
+}
+
+ULONG
+WeakReferenceSupport::Release()
+{
+  ULONG newRefCnt;
+  { // Scope for lock
+    AutoCriticalSection lock(&mCS);
+    newRefCnt = --mRefCnt;
+    if (newRefCnt == 0) {
+      ClearWeakRefs();
+    }
+  }
+  if (newRefCnt == 0) {
+    if (mFlags != Flags::eDestroyOnMainThread || NS_IsMainThread()) {
+      delete this;
+    } else {
+      // It is possible for the last Release() call to happen off-main-thread.
+      // If so, we need to dispatch an event to delete ourselves.
+      mozilla::DebugOnly<nsresult> rv =
+        NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void
+        {
+          delete this;
+        }));
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+  return newRefCnt;
+}
+
+void
+WeakReferenceSupport::ClearWeakRefs()
+{
+  for (uint32_t i = 0, len = mWeakRefs.Length(); i < len; ++i) {
+    mWeakRefs[i]->Clear();
+    mWeakRefs[i]->Release();
+  }
+  mWeakRefs.Clear();
+}
+
+HRESULT
+WeakReferenceSupport::GetWeakReference(IWeakReference** aOutWeakRef)
+{
+  if (!aOutWeakRef) {
+    return E_INVALIDARG;
+  }
+  *aOutWeakRef = nullptr;
+
+  AutoCriticalSection lock(&mCS);
+  RefPtr<WeakRef> weakRef = MakeAndAddRef<WeakRef>(this);
+
+  HRESULT hr = weakRef->QueryInterface(IID_IWeakReference, (void**)aOutWeakRef);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  mWeakRefs.AppendElement(weakRef.get());
+  weakRef->AddRef();
+  return S_OK;
+}
+
+WeakRef::WeakRef(WeakReferenceSupport* aSupport)
+  : mRefCnt(1)
+  , mMutex("mozilla::mscom::WeakRef::mMutex")
+  , mSupport(aSupport)
+{
+  MOZ_ASSERT(aSupport);
+}
+
+HRESULT
+WeakRef::QueryInterface(REFIID riid, void** ppv)
+{
+  IUnknown* punk = nullptr;
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+
+  if (riid == IID_IUnknown || riid == IID_IWeakReference) {
+    punk = static_cast<IUnknown*>(this);
+  }
+
+  *ppv = punk;
+  if (!punk) {
+    return E_NOINTERFACE;
+  }
+
+  punk->AddRef();
+  return S_OK;
+}
+
+ULONG
+WeakRef::AddRef()
+{
+  return (ULONG) InterlockedIncrement((LONG*)&mRefCnt);
+}
+
+ULONG
+WeakRef::Release()
+{
+  ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt);
+  if (newRefCnt == 0) {
+    delete this;
+  }
+  return newRefCnt;
+}
+
+HRESULT
+WeakRef::Resolve(REFIID aIid, void** aOutStrongReference)
+{
+  MutexAutoLock lock(mMutex);
+  if (!mSupport) {
+    return E_FAIL;
+  }
+  return mSupport->QueryInterface(aIid, aOutStrongReference);
+}
+
+void
+WeakRef::Clear()
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mSupport);
+  mSupport = nullptr;
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/WeakRef.h
@@ -0,0 +1,104 @@
+/* -*- 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_WeakRef_h
+#define mozilla_mscom_WeakRef_h
+
+#include <guiddef.h>
+#include <Unknwn.h>
+
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+/**
+ * Thread-safe weak references for COM that works pre-Windows 8 and do not
+ * require WinRT.
+ */
+
+namespace mozilla {
+namespace mscom {
+
+// {F841AEFA-064C-49A4-B73D-EBD14A90F012}
+DEFINE_GUID(IID_IWeakReference,
+0xf841aefa, 0x64c, 0x49a4, 0xb7, 0x3d, 0xeb, 0xd1, 0x4a, 0x90, 0xf0, 0x12);
+
+struct IWeakReference : public IUnknown
+{
+  virtual STDMETHODIMP Resolve(REFIID aIid, void** aOutStringReference) = 0;
+};
+
+// {87611F0C-9BBB-4F78-9D43-CAC5AD432CA1}
+DEFINE_GUID(IID_IWeakReferenceSource,
+0x87611f0c, 0x9bbb, 0x4f78, 0x9d, 0x43, 0xca, 0xc5, 0xad, 0x43, 0x2c, 0xa1);
+
+struct IWeakReferenceSource : public IUnknown
+{
+  virtual STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) = 0;
+};
+
+class WeakRef;
+
+class WeakReferenceSupport : public IWeakReferenceSource
+{
+public:
+  enum class Flags
+  {
+    eNone = 0,
+    eDestroyOnMainThread = 1
+  };
+
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IWeakReferenceSource
+  STDMETHODIMP GetWeakReference(IWeakReference** aOutWeakRef) override;
+
+protected:
+  explicit WeakReferenceSupport(Flags aFlags);
+  virtual ~WeakReferenceSupport();
+
+  virtual HRESULT ThreadSafeQueryInterface(REFIID aIid,
+                                           IUnknown** aOutInterface) = 0;
+
+private:
+  void ClearWeakRefs();
+
+private:
+  // Using a raw CRITICAL_SECTION here because it can be reentered
+  CRITICAL_SECTION    mCS;
+  ULONG               mRefCnt;
+  nsTArray<WeakRef*>  mWeakRefs;
+  Flags               mFlags;
+};
+
+class WeakRef : public IWeakReference
+{
+public:
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IWeakReference
+  STDMETHODIMP Resolve(REFIID aIid, void** aOutStrongReference) override;
+
+  explicit WeakRef(WeakReferenceSupport* aSupport);
+
+  void Clear();
+
+private:
+  ULONG                 mRefCnt;
+  mozilla::Mutex        mMutex; // Protects mSupport
+  WeakReferenceSupport* mSupport;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_WeakRef_h
+
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -1,20 +1,45 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.mscom += [
     'COMApartmentRegion.h',
+    'COMPtrHolder.h',
+    'EnsureMTA.h',
+    'Interceptor.h',
+    'InterceptorLog.h',
+    'MainThreadHandoff.h',
+    'MainThreadInvoker.h',
     'MainThreadRuntime.h',
+    'ProxyStream.h',
+    'Ptr.h',
+    'Registration.h',
     'Utils.h',
+    'WeakRef.h',
+]
+
+SOURCES += [
+    'Interceptor.cpp',
+    'Registration.cpp',
+    'Utils.cpp',
+    'WeakRef.cpp',
 ]
 
 UNIFIED_SOURCES += [
+    'EnsureMTA.cpp',
+    'InterceptorLog.cpp',
+    'MainThreadHandoff.cpp',
+    'MainThreadInvoker.cpp',
     'MainThreadRuntime.cpp',
-    'Utils.cpp',
+    'ProxyStream.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/xpcom/build',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -839,16 +839,17 @@ PropertySupportsVariant(nsCSSProperty aP
         supported = VARIANT_URL;
         break;
 
       case eCSSProperty_grid_column_start:
       case eCSSProperty_grid_column_end:
       case eCSSProperty_grid_row_start:
       case eCSSProperty_grid_row_end:
       case eCSSProperty_font_weight:
+      case eCSSProperty_initial_letter:
         supported = VARIANT_NUMBER;
         break;
 
       default:
         supported = 0;
         break;
     }
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -999,16 +999,18 @@ protected:
   bool ParseGridShorthandAutoProps();
   bool ParseGridLine(nsCSSValue& aValue);
   bool ParseGridColumnRowStartEnd(nsCSSProperty aPropID);
   bool ParseGridColumnRow(nsCSSProperty aStartPropID,
                           nsCSSProperty aEndPropID);
   bool ParseGridArea();
   bool ParseGridGap();
 
+  bool ParseInitialLetter();
+
   // parsing 'align/justify-items/self' from the css-align spec
   bool ParseAlignJustifyPosition(nsCSSValue& aResult,
                                  const KTableEntry aTable[]);
   bool ParseJustifyItems();
   bool ParseAlignItems();
   bool ParseAlignJustifySelf(nsCSSProperty aPropID);
   // parsing 'align/justify-content' from the css-align spec
   bool ParseAlignJustifyContent(nsCSSProperty aPropID);
@@ -1240,16 +1242,26 @@ protected:
   {
     return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr);
   }
   bool ParseNonNegativeNumber(nsCSSValue& aValue)
   {
     return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr);
   }
 
+  // Helpers for some common ParseSingleTokenOneOrLargerVariant calls.
+  bool ParseOneOrLargerInteger(nsCSSValue& aValue)
+  {
+    return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr);
+  }
+  bool ParseOneOrLargerNumber(nsCSSValue& aValue)
+  {
+    return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr);
+  }
+
   // http://dev.w3.org/csswg/css-values/#custom-idents
   // Parse an identifier that is none of:
   // * a CSS-wide keyword
   // * "default"
   // * a keyword in |aExcludedKeywords|
   // * a keyword in |aPropertyKTable|
   //
   // |aExcludedKeywords| is an array of nsCSSKeyword
@@ -9774,16 +9786,43 @@ CSSParserImpl::ParseGridGap()
     return false;
   }
   AppendValue(eCSSProperty_grid_row_gap, first);
   AppendValue(eCSSProperty_grid_column_gap,
               result == CSSParseResult::NotFound ? first : second);
   return true;
 }
 
+// normal | [<number> <integer>?]
+bool
+CSSParserImpl::ParseInitialLetter()
+{
+  nsCSSValue value;
+  // 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone
+  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL,
+                               nullptr)) {
+    nsCSSValue first, second;
+    if (!ParseOneOrLargerNumber(first)) {
+      return false;
+    }
+
+    if (!ParseOneOrLargerInteger(second)) {
+      AppendValue(eCSSProperty_initial_letter, first);
+      return true;
+    } else {
+      RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
+      val->Item(0) = first;
+      val->Item(1) = second;
+      value.SetArrayValue(val, eCSSUnit_Array);
+    }
+  }
+  AppendValue(eCSSProperty_initial_letter, value);
+  return true;
+}
+
 // [ $aTable && <overflow-position>? ] ?
 // $aTable is for <content-position> or <self-position>
 bool
 CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult,
                                          const KTableEntry aTable[])
 {
   nsCSSValue pos, overflowPos;
   int32_t value = 0;
@@ -11558,27 +11597,27 @@ CSSParserImpl::ParsePropertyByFunction(n
                               eCSSProperty_grid_row_end);
   case eCSSProperty_grid_area:
     return ParseGridArea();
   case eCSSProperty_grid_gap:
     return ParseGridGap();
   case eCSSProperty_image_region:
     return ParseRect(eCSSProperty_image_region);
   case eCSSProperty_align_content:
+  case eCSSProperty_justify_content:
     return ParseAlignJustifyContent(aPropID);
   case eCSSProperty_align_items:
     return ParseAlignItems();
   case eCSSProperty_align_self:
+  case eCSSProperty_justify_self:
     return ParseAlignJustifySelf(aPropID);
-  case eCSSProperty_justify_content:
-    return ParseAlignJustifyContent(aPropID);
+  case eCSSProperty_initial_letter:
+    return ParseInitialLetter();
   case eCSSProperty_justify_items:
     return ParseJustifyItems();
-  case eCSSProperty_justify_self:
-    return ParseAlignJustifySelf(aPropID);
   case eCSSProperty_list_style:
     return ParseListStyle();
   case eCSSProperty_margin:
     return ParseMargin();
   case eCSSProperty_object_position:
     return ParseObjectPosition();
   case eCSSProperty_outline:
     return ParseOutline();
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2263,16 +2263,27 @@ CSS_PROP_TEXT(
     hyphens,
     Hyphens,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kHyphensKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
+CSS_PROP_TEXTRESET(
+    initial-letter,
+    initial_letter,
+    InitialLetter,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_APPLIES_TO_FIRST_LETTER,
+    "layout.css.initial-letter.enabled",
+    0,
+    nullptr,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None)
 CSS_PROP_VISIBILITY(
     image-orientation,
     image_orientation,
     ImageOrientation,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION,
     "layout.css.image-orientation.enabled",
     0,
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1037,18 +1037,18 @@ const KTableEntry nsCSSProps::kBoxDecora
 };
 
 const KTableEntry nsCSSProps::kBoxShadowTypeKTable[] = {
   { eCSSKeyword_inset, uint8_t(StyleBoxShadowType::Inset) },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kBoxSizingKTable[] = {
-  { eCSSKeyword_content_box,  uint8_t(StyleBoxSizing::Content) },
-  { eCSSKeyword_border_box,   uint8_t(StyleBoxSizing::Border) },
+  { eCSSKeyword_content_box,  StyleBoxSizing::Content },
+  { eCSSKeyword_border_box,   StyleBoxSizing::Border },
   { eCSSKeyword_UNKNOWN,      -1 }
 };
 
 const KTableEntry nsCSSProps::kCaptionSideKTable[] = {
   { eCSSKeyword_top,                  NS_STYLE_CAPTION_SIDE_TOP },
   { eCSSKeyword_right,                NS_STYLE_CAPTION_SIDE_RIGHT },
   { eCSSKeyword_bottom,               NS_STYLE_CAPTION_SIDE_BOTTOM },
   { eCSSKeyword_left,                 NS_STYLE_CAPTION_SIDE_LEFT },
@@ -2292,23 +2292,23 @@ const KTableEntry nsCSSProps::kDominantB
 
 const KTableEntry nsCSSProps::kFillRuleKTable[] = {
   { eCSSKeyword_nonzero, NS_STYLE_FILL_RULE_NONZERO },
   { eCSSKeyword_evenodd, NS_STYLE_FILL_RULE_EVENODD },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kClipShapeSizingKTable[] = {
-  { eCSSKeyword_content_box,   uint8_t(StyleClipShapeSizing::Content) },
-  { eCSSKeyword_padding_box,   uint8_t(StyleClipShapeSizing::Padding) },
-  { eCSSKeyword_border_box,    uint8_t(StyleClipShapeSizing::Border) },
-  { eCSSKeyword_margin_box,    uint8_t(StyleClipShapeSizing::Margin) },
-  { eCSSKeyword_fill_box,      uint8_t(StyleClipShapeSizing::Fill) },
-  { eCSSKeyword_stroke_box,    uint8_t(StyleClipShapeSizing::Stroke) },
-  { eCSSKeyword_view_box,      uint8_t(StyleClipShapeSizing::View) },
+  { eCSSKeyword_content_box,   StyleClipShapeSizing::Content },
+  { eCSSKeyword_padding_box,   StyleClipShapeSizing::Padding },
+  { eCSSKeyword_border_box,    StyleClipShapeSizing::Border },
+  { eCSSKeyword_margin_box,    StyleClipShapeSizing::Margin },
+  { eCSSKeyword_fill_box,      StyleClipShapeSizing::Fill },
+  { eCSSKeyword_stroke_box,    StyleClipShapeSizing::Stroke },
+  { eCSSKeyword_view_box,      StyleClipShapeSizing::View },
   { eCSSKeyword_UNKNOWN,       -1 }
 };
 
 const KTableEntry nsCSSProps::kShapeRadiusKTable[] = {
   { eCSSKeyword_closest_side, NS_RADIUS_CLOSEST_SIDE },
   { eCSSKeyword_farthest_side, NS_RADIUS_FARTHEST_SIDE },
   { eCSSKeyword_UNKNOWN, -1 }
 };
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -6,16 +6,18 @@
 /*
  * methods for dealing with CSS properties and tables of the keyword
  * values they accept
  */
 
 #ifndef nsCSSProps_h___
 #define nsCSSProps_h___
 
+#include <limits>
+#include <type_traits>
 #include "nsString.h"
 #include "nsCSSProperty.h"
 #include "nsStyleStructFwd.h"
 #include "nsCSSKeywords.h"
 #include "mozilla/CSSEnabledState.h"
 #include "mozilla/UseCounter.h"
 
 // Length of the "--" prefix on custom names (such as custom property names,
@@ -324,21 +326,59 @@ enum nsStyleAnimType {
 
   // RefPtr<nsCSSShadowArray> values
   eStyleAnimType_Shadow,
 
   // property not animatable
   eStyleAnimType_None
 };
 
+namespace mozilla {
+
+// Type trait that determines whether the integral or enum type Type can fit
+// within the integral type Storage without loss.
+template<typename T, typename Storage>
+struct IsEnumFittingWithin
+  : IntegralConstant<
+      bool,
+      std::is_integral<Storage>::value &&
+      std::numeric_limits<typename std::underlying_type<T>::type>::min() >=
+        std::numeric_limits<Storage>::min() &&
+      std::numeric_limits<typename std::underlying_type<T>::type>::max() <=
+        std::numeric_limits<Storage>::max()
+    >
+{};
+
+} // namespace mozilla
+
 class nsCSSProps {
 public:
   typedef mozilla::CSSEnabledState EnabledState;
 
-  struct KTableEntry {
+  struct KTableEntry
+  {
+    // KTableEntry objects can be initialized either with an int16_t value
+    // or a value of an enumeration type that can fit within an int16_t.
+
+    KTableEntry(nsCSSKeyword aKeyword, int16_t aValue)
+      : mKeyword(aKeyword)
+      , mValue(aValue)
+    {
+    }
+
+    template<typename T,
+             typename = typename std::enable_if<std::is_enum<T>::value>::type>
+    KTableEntry(nsCSSKeyword aKeyword, T aValue)
+      : mKeyword(aKeyword)
+      , mValue(static_cast<int16_t>(aValue))
+    {
+      static_assert(mozilla::IsEnumFittingWithin<T, int16_t>::value,
+                    "aValue must be an enum that fits within mValue");
+    }
+
     nsCSSKeyword mKeyword;
     int16_t mValue;
   };
 
   static void AddRefTable(void);
   static void ReleaseTable(void);
 
   // Looks up the property with name aProperty and returns its corresponding
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3539,16 +3539,35 @@ nsComputedDOMStyle::DoGetImageRegion()
     leftVal->SetAppUnits(list->mImageRegion.x);
     val->SetRect(domRect);
   }
 
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetInitialLetter()
+{
+  const nsStyleTextReset* textReset = StyleTextReset();
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+  if (textReset->mInitialLetterSink == 0) {
+    val->SetIdent(eCSSKeyword_normal);
+    return val.forget();
+  } else {
+    RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+    val->SetNumber(textReset->mInitialLetterSize);
+    valueList->AppendCSSValue(val.forget());
+    RefPtr<nsROCSSPrimitiveValue> second = new nsROCSSPrimitiveValue;
+    second->SetNumber(textReset->mInitialLetterSink);
+    valueList->AppendCSSValue(second.forget());
+    return valueList.forget();
+  }
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetLineHeight()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
 
   nscoord lineHeight;
   if (GetLineHeightCoord(lineHeight)) {
     val->SetAppUnits(lineHeight);
   } else {
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -399,16 +399,17 @@ private:
 
   /* List properties */
   already_AddRefed<CSSValue> DoGetListStyleImage();
   already_AddRefed<CSSValue> DoGetListStylePosition();
   already_AddRefed<CSSValue> DoGetListStyleType();
   already_AddRefed<CSSValue> DoGetImageRegion();
 
   /* Text Properties */
+  already_AddRefed<CSSValue> DoGetInitialLetter();
   already_AddRefed<CSSValue> DoGetLineHeight();
   already_AddRefed<CSSValue> DoGetRubyAlign();
   already_AddRefed<CSSValue> DoGetRubyPosition();
   already_AddRefed<CSSValue> DoGetTextAlign();
   already_AddRefed<CSSValue> DoGetTextAlignLast();
   already_AddRefed<CSSValue> DoGetTextCombineUpright();
   already_AddRefed<CSSValue> DoGetTextDecoration();
   already_AddRefed<CSSValue> DoGetTextDecorationColor();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -148,16 +148,17 @@ COMPUTED_STYLE_PROP(grid_row_gap,       
 COMPUTED_STYLE_PROP(grid_row_start,                GridRowStart)
 COMPUTED_STYLE_PROP(grid_template_areas,           GridTemplateAreas)
 COMPUTED_STYLE_PROP(grid_template_columns,         GridTemplateColumns)
 COMPUTED_STYLE_PROP(grid_template_rows,            GridTemplateRows)
 COMPUTED_STYLE_PROP(height,                        Height)
 COMPUTED_STYLE_PROP(hyphens,                       Hyphens)
 COMPUTED_STYLE_PROP(image_orientation,             ImageOrientation)
 COMPUTED_STYLE_PROP(ime_mode,                      IMEMode)
+COMPUTED_STYLE_PROP(initial_letter,                InitialLetter)
 COMPUTED_STYLE_PROP(isolation,                     Isolation)
 COMPUTED_STYLE_PROP(justify_content,               JustifyContent)
 COMPUTED_STYLE_PROP(justify_items,                 JustifyItems)
 COMPUTED_STYLE_PROP(justify_self,                  JustifySelf)
 COMPUTED_STYLE_PROP(left,                          Left)
 COMPUTED_STYLE_PROP(letter_spacing,                LetterSpacing)
 COMPUTED_STYLE_PROP(line_height,                   LineHeight)
 //// COMPUTED_STYLE_PROP(list_style,               ListStyle)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4972,16 +4972,46 @@ nsRuleNode::ComputeTextResetData(void* a
   }
 
   // unicode-bidi: enum, inherit, initial
   SetValue(*aRuleData->ValueForUnicodeBidi(), text->mUnicodeBidi, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
            parentText->mUnicodeBidi,
            NS_STYLE_UNICODE_BIDI_NORMAL);
 
+  // initial-letter: normal, number, array(number, integer?), initial
+  const nsCSSValue* initialLetterValue = aRuleData->ValueForInitialLetter();
+  if (initialLetterValue->GetUnit() == eCSSUnit_Null) {
+    // We don't want to change anything in this case.
+  } else if (initialLetterValue->GetUnit() == eCSSUnit_Inherit) {
+    conditions.SetUncacheable();
+    text->mInitialLetterSink = parentText->mInitialLetterSink;
+    text->mInitialLetterSize = parentText->mInitialLetterSize;
+  } else if (initialLetterValue->GetUnit() == eCSSUnit_Initial ||
+             initialLetterValue->GetUnit() == eCSSUnit_Unset ||
+             initialLetterValue->GetUnit() == eCSSUnit_Normal) {
+    // Use invalid values in initial-letter property to mean normal. So we can
+    // determine whether it is normal by checking mInitialLetterSink == 0.
+    text->mInitialLetterSink = 0;
+    text->mInitialLetterSize = 0.0f;
+  } else if (initialLetterValue->GetUnit() == eCSSUnit_Array) {
+    const nsCSSValue& firstValue = initialLetterValue->GetArrayValue()->Item(0);
+    const nsCSSValue& secondValue = initialLetterValue->GetArrayValue()->Item(1);
+    MOZ_ASSERT(firstValue.GetUnit() == eCSSUnit_Number &&
+               secondValue.GetUnit() == eCSSUnit_Integer,
+               "unexpected value unit");
+    text->mInitialLetterSize = firstValue.GetFloatValue();
+    text->mInitialLetterSink = secondValue.GetIntValue();
+  } else if (initialLetterValue->GetUnit() == eCSSUnit_Number) {
+    text->mInitialLetterSize = initialLetterValue->GetFloatValue();
+    text->mInitialLetterSink = NSToCoordFloorClamped(text->mInitialLetterSize);
+  } else {
+    MOZ_ASSERT_UNREACHABLE("unknown unit for initial-letter");
+  }
+
   COMPUTE_END_RESET(TextReset, text)
 }
 
 const void*
 nsRuleNode::ComputeUserInterfaceData(void* aStartStruct,
                                      const nsRuleData* aRuleData,
                                      nsStyleContext* aContext,
                                      nsRuleNode* aHighestNode,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3571,16 +3571,18 @@ nsStyleContent::AllocateContents(uint32_
 
 // --------------------
 // nsStyleTextReset
 //
 
 nsStyleTextReset::nsStyleTextReset(StyleStructContext aContext)
   : mTextDecorationLine(NS_STYLE_TEXT_DECORATION_LINE_NONE)
   , mUnicodeBidi(NS_STYLE_UNICODE_BIDI_NORMAL)
+  , mInitialLetterSink(0)
+  , mInitialLetterSize(0.0f)
   , mTextDecorationStyle(NS_STYLE_TEXT_DECORATION_STYLE_SOLID | BORDER_COLOR_FOREGROUND)
   , mTextDecorationColor(NS_RGB(0, 0, 0))
 {
   MOZ_COUNT_CTOR(nsStyleTextReset);
 }
 
 nsStyleTextReset::nsStyleTextReset(const nsStyleTextReset& aSource)
 {
@@ -3591,17 +3593,19 @@ nsStyleTextReset::nsStyleTextReset(const
 nsStyleTextReset::~nsStyleTextReset()
 {
   MOZ_COUNT_DTOR(nsStyleTextReset);
 }
 
 nsChangeHint
 nsStyleTextReset::CalcDifference(const nsStyleTextReset& aNewData) const
 {
-  if (mUnicodeBidi != aNewData.mUnicodeBidi) {
+  if (mUnicodeBidi != aNewData.mUnicodeBidi ||
+      mInitialLetterSink != aNewData.mInitialLetterSink ||
+      mInitialLetterSize != aNewData.mInitialLetterSize) {
     return NS_STYLE_HINT_REFLOW;
   }
 
   uint8_t lineStyle = GetDecorationStyle();
   uint8_t otherLineStyle = aNewData.GetDecorationStyle();
   if (mTextDecorationLine != aNewData.mTextDecorationLine ||
       lineStyle != otherLineStyle) {
     // Changes to our text-decoration line can impact our overflow area &
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1972,16 +1972,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
            nsChangeHint_ReflowChangesSizeOrPosition |
            nsChangeHint_ClearAncestorIntrinsics;
   }
 
   nsStyleTextOverflow mTextOverflow;    // [reset] enum, string
 
   uint8_t mTextDecorationLine;          // [reset] see nsStyleConsts.h
   uint8_t mUnicodeBidi;                 // [reset] see nsStyleConsts.h
+  nscoord mInitialLetterSink;           // [reset] 0 means normal
+  float mInitialLetterSize;             // [reset] 0.0f means normal
 protected:
   uint8_t mTextDecorationStyle;         // [reset] see nsStyleConsts.h
 
   nscolor mTextDecorationColor;         // [reset] the colors to use for a decoration lines, not used at currentColor
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText
 {
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -6656,16 +6656,27 @@ if (IsCSSPropertyPrefEnabled("layout.css
       "0deg from-image",
       "from-image 0deg",
       "flip from-image",
       "from-image flip",
     ]
   };
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) {
+  gCSSProperties["initial-letter"] = {
+    domProp: "initialLetter",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "normal" ],
+    other_values: [ "2", "2.5", "3.7 2", "4 3" ],
+    invalid_values: [ "-3", "3.7 -2", "25%", "16px", "1 0", "0", "0 1" ]
+  };
+}
+
 if (IsCSSPropertyPrefEnabled("layout.css.osx-font-smoothing.enabled")) {
   gCSSProperties["-moz-osx-font-smoothing"] = {
     domProp: "MozOsxFontSmoothing",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto" ],
     other_values: [ "grayscale" ],
     invalid_values: [ "none", "subpixel-antialiased", "antialiased" ]
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -62,29 +62,29 @@ ClearKeySession::Init(uint32_t aCreateSe
 
   if (aInitDataType == "cenc") {
     ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
   } else if (aInitDataType == "keyids") {
     std::string sessionType;
     ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds, sessionType);
     if (sessionType != ClearKeyUtils::SessionTypeToString(mSessionType)) {
       const char message[] = "Session type specified in keyids init data doesn't match session type.";
-      mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
+      mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, message, strlen(message));
       return;
     }
   } else if (aInitDataType == "webm" && aInitDataSize == 16) {
     // "webm" initData format is simply the raw bytes of the keyId.
     vector<uint8_t> keyId;
     keyId.assign(aInitData, aInitData+aInitDataSize);
     mKeyIds.push_back(keyId);
   }
 
   if (!mKeyIds.size()) {
     const char message[] = "Couldn't parse init data";
-    mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
+    mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, message, strlen(message));
     return;
   }
 
   mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
 
   mCallback->ResolvePromise(aPromiseId);
 }
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2409,16 +2409,19 @@ pref("layout.css.visited_links_enabled",
 pref("layout.css.dpi", -1);
 
 // Set the number of device pixels per CSS pixel. A value <= 0 means choose
 // automatically based on user settings for the platform (e.g., "UI scale factor"
 // on Mac). A positive value is used as-is. This effectively controls the size
 // of a CSS "px". This is only used for windows on the screen, not for printing.
 pref("layout.css.devPixelsPerPx", "-1.0");
 
+// Is support for CSS initial-letter property enabled?
+pref("layout.css.initial-letter.enabled", false);
+
 // Is support for CSS Masking features enabled?
 pref("layout.css.masking.enabled", true);
 
 // Is support for mix-blend-mode enabled?
 pref("layout.css.mix-blend-mode.enabled", true);
 
 // Is support for isolation enabled?
 pref("layout.css.isolation.enabled", true);
--- a/netwerk/locales/en-US/necko.properties
+++ b/netwerk/locales/en-US/necko.properties
@@ -36,14 +36,14 @@ DirFileLabel=File:
 PhishingAuth=You are about to visit “%1$S”. This site may be attempting to trick you into thinking you are visiting a different site. Use extreme caution.
 PhishingAuthAccept=I understand and will be very careful
 SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
 AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.
 
 TrackingUriBlocked=The resource at “%1$S” was blocked because tracking protection is enabled.
 
 # LOCALIZATION NOTE (APIDeprecationWarning):
-# %1$S is the deprected API; %2$S is the API function that should be used.
+# %1$S is the deprecated API; %2$S is the API function that should be used.
 APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’
 
 # LOCALIZATION NOTE (nsICookieManagerDeprecated): don't localize originAttributes.
 # %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
 nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
--- a/python/mozbuild/mozbuild/base.py
+++ b/python/mozbuild/mozbuild/base.py
@@ -750,17 +750,17 @@ class MachCommandBase(MozbuildObject):
                 for line in e.output:
                     print(line)
 
             sys.exit(1)
 
         # Always keep a log of the last command, but don't do that for mach
         # invokations from scripts (especially not the ones done by the build
         # system itself).
-        if (self.log_manager and self.log_manager.terminal and
+        if (os.isatty(sys.stdout.fileno()) and
                 not getattr(self, 'NO_AUTO_LOG', False)):
             self._ensure_state_subdir_exists('.')
             logfile = self._get_state_filename('last_log.json')
             try:
                 fd = open(logfile, "wb")
                 self.log_manager.add_json_handler(fd)
             except Exception as e:
                 self.log(logging.WARNING, 'mach', {'error': e},
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -568,51 +568,16 @@ class Build(MachCommandBase):
             try:
                 browser = webbrowser.get().open_new_tab(server.url)
             except Exception:
                 print('Please open %s in a browser.' % server.url)
 
         print('Hit CTRL+c to stop server.')
         server.run()
 
-    CLOBBER_CHOICES = ['objdir', 'python']
-    @Command('clobber', category='build',
-        description='Clobber the tree (delete the object directory).')
-    @CommandArgument('what', default=['objdir'], nargs='*',
-        help='Target to clobber, must be one of {{{}}} (default objdir).'.format(
-             ', '.join(CLOBBER_CHOICES)))
-    def clobber(self, what):
-        invalid = set(what) - set(self.CLOBBER_CHOICES)
-        if invalid:
-            print('Unknown clobber target(s): {}'.format(', '.join(invalid)))
-            return 1
-
-        ret = 0
-        if 'objdir' in what:
-            try:
-                self.remove_objdir()
-            except OSError as e:
-                if sys.platform.startswith('win'):
-                    if isinstance(e, WindowsError) and e.winerror in (5,32):
-                        self.log(logging.ERROR, 'file_access_error', {'error': e},
-                            "Could not clobber because a file was in use. If the "
-                            "application is running, try closing it. {error}")
-                        return 1
-                raise
-
-        if 'python' in what:
-            if os.path.isdir(mozpath.join(self.topsrcdir, '.hg')):
-                cmd = ['hg', 'purge', '--all', '-I', 'glob:**.py[co]']
-            elif os.path.isdir(mozpath.join(self.topsrcdir, '.git')):
-                cmd = ['git', 'clean', '-f', '-x', '*.py[co]']
-            else:
-                cmd = ['find', '.', '-type', 'f', '-name', '*.py[co]', '-delete']
-            ret = subprocess.call(cmd, cwd=self.topsrcdir)
-        return ret
-
     @Command('build-backend', category='build',
         description='Generate a backend used to build the tree.')
     @CommandArgument('-d', '--diff', action='store_true',
         help='Show a diff of changes.')
     # It would be nice to filter the choices below based on
     # conditions, but that is for another day.
     @CommandArgument('-b', '--backend', nargs='+', choices=sorted(backends),
         help='Which backend to build.')
@@ -653,36 +618,74 @@ class Doctor(MachCommandBase):
         help='Attempt to fix found problems.')
     def doctor(self, fix=None):
         self._activate_virtualenv()
         from mozbuild.doctor import Doctor
         doctor = Doctor(self.topsrcdir, self.topobjdir, fix)
         return doctor.check_all()
 
 @CommandProvider
+class Clobber(MachCommandBase):
+    NO_AUTO_LOG = True
+    CLOBBER_CHOICES = ['objdir', 'python']
+    @Command('clobber', category='build',
+        description='Clobber the tree (delete the object directory).')
+    @CommandArgument('what', default=['objdir'], nargs='*',
+        help='Target to clobber, must be one of {{{}}} (default objdir).'.format(
+             ', '.join(CLOBBER_CHOICES)))
+    def clobber(self, what):
+        invalid = set(what) - set(self.CLOBBER_CHOICES)
+        if invalid:
+            print('Unknown clobber target(s): {}'.format(', '.join(invalid)))
+            return 1
+
+        ret = 0
+        if 'objdir' in what:
+            try:
+                self.remove_objdir()
+            except OSError as e:
+                if sys.platform.startswith('win'):
+                    if isinstance(e, WindowsError) and e.winerror in (5,32):
+                        self.log(logging.ERROR, 'file_access_error', {'error': e},
+                            "Could not clobber because a file was in use. If the "
+                            "application is running, try closing it. {error}")
+                        return 1
+                raise
+
+        if 'python' in what:
+            if os.path.isdir(mozpath.join(self.topsrcdir, '.hg')):
+                cmd = ['hg', 'purge', '--all', '-I', 'glob:**.py[co]']
+            elif os.path.isdir(mozpath.join(self.topsrcdir, '.git')):
+                cmd = ['git', 'clean', '-f', '-x', '*.py[co]']
+            else:
+                cmd = ['find', '.', '-type', 'f', '-name', '*.py[co]', '-delete']
+            ret = subprocess.call(cmd, cwd=self.topsrcdir)
+        return ret
+
+@CommandProvider
 class Logs(MachCommandBase):
     """Provide commands to read mach logs."""
     NO_AUTO_LOG = True
 
     @Command('show-log', category='post-build',
         description='Display mach logs')
     @CommandArgument('log_file', nargs='?', type=argparse.FileType('rb'),
         help='Filename to read log data from. Defaults to the log of the last '
              'mach command.')
     def show_log(self, log_file=None):
         if not log_file:
             path = self._get_state_filename('last_log.json')
             log_file = open(path, 'rb')
 
-        if self.log_manager.terminal:
+        if os.isatty(sys.stdout.fileno()):
             env = dict(os.environ)
             if 'LESS' not in env:
                 # Sensible default flags if none have been set in the user
                 # environment.
-                env['LESS'] = 'FRX'
+                env[b'LESS'] = b'FRX'
             less = subprocess.Popen(['less'], stdin=subprocess.PIPE, env=env)
             # Various objects already have a reference to sys.stdout, so we
             # can't just change it, we need to change the file descriptor under
             # it to redirect to less's input.
             # First keep a copy of the sys.stdout file descriptor.
             output_fd = os.dup(sys.stdout.fileno())
             os.dup2(less.stdin.fileno(), sys.stdout.fileno())
 
--- a/taskcluster/ci/legacy/tasks/tests/mozharness-gecko.yml
+++ b/taskcluster/ci/legacy/tasks/tests/mozharness-gecko.yml
@@ -1,17 +1,17 @@
 ---
 $inherits:
     from: 'tasks/lint.yml'
     variables:
       build_product: 'lint'
       build_name: 'mozharness-tox'
       build_type: 'opt'
 
-docker-image: desktop-test
+docker-image: lint
 task:
   metadata:
     name: '[TC] - Mozharness Tox'
     description: 'Mozharness integration tests'
 
   scopes:
     - 'docker-worker:cache:level-{{level}}-{{project}}-dotcache'
 
@@ -21,23 +21,24 @@ task:
       path: 'public/image.tar'
       taskId:
         task-reference: "<docker-image>"
 
     cache:
       level-{{level}}-{{project}}-dotcache: '/home/worker/.cache'
 
     command:
+      - /home/worker/bin/checkout-gecko-and-run
+      - /home/worker/workspace/gecko
       - bash
       - -cx
       - >
-          tc-vcs checkout ./gecko {{base_repository}} {{head_repository}} {{head_rev}} {{head_ref}} &&
-          cd gecko/testing/mozharness &&
-          pip install tox &&
-          tox -e py27-hg3.7
+          cd /home/worker/workspace/gecko/testing/mozharness &&
+          /usr/bin/pip2 install tox &&
+          /home/worker/.local/bin/tox -e py27-hg3.7
   extra:
     extra:
         build_product: '{{build_product}}'
     locations:
         build: null
         tests: null
     treeherder:
         machine:
--- a/taskcluster/taskgraph/task/legacy.py
+++ b/taskcluster/taskgraph/task/legacy.py
@@ -164,16 +164,40 @@ def remove_caches_from_task(task):
                 try:
                     scopes.remove(scope)
                 except ValueError:
                     raise ValueError("scope '{}' not in {}".format(scope, scopes))
     except KeyError:
         pass
 
 
+def remove_coalescing_from_task(task):
+    r"""Remove coalescing route and supersederUrl from job task
+
+    :param task: task definition.
+    """
+    patterns = [
+        re.compile("^coalesce.v1.builds.*pgo$"),
+    ]
+
+    try:
+        payload = task["task"]["payload"]
+        routes = task["task"]["routes"]
+        removable_routes = [route for route in list(routes)
+                            if any([p.match(route) for p in patterns])]
+        if removable_routes:
+            # we remove supersederUrl only when we have also routes to remove
+            payload.pop("supersederUrl")
+
+        for route in removable_routes:
+            routes.remove(route)
+    except KeyError:
+        pass
+
+
 def query_vcs_info(repository, revision):
     """Query the pushdate and pushid of a repository/revision.
 
     This is intended to be used on hg.mozilla.org/mozilla-central and
     similar. It may or may not work for other hg repositories.
     """
     if not repository or not revision:
         logger.warning('cannot query vcs info because vcs info not provided')
@@ -469,19 +493,20 @@ class LegacyTask(base.Task):
                 tier = task_extra['treeherder'].get('tier', 1)
                 if tier != 1:
                     # Only tier 1 jobs use the build time as rank. Everything
                     # else gets rank 0 until it is promoted to tier 1.
                     task_extra['index']['rank'] = 0
 
             set_interactive_task(build_task, interactive)
 
-            # try builds don't use cache
+            # try builds don't use cache nor coalescing
             if project == "try":
                 remove_caches_from_task(build_task)
+                remove_coalescing_from_task(build_task)
                 set_expiration(build_task, TRY_EXPIRATION)
 
             decorate_task_treeherder_routes(build_task['task'],
                                             build_parameters['project'],
                                             build_parameters['head_rev'],
                                             build_parameters['pushlog_id'])
             decorate_task_json_routes(build_task['task'],
                                       json_routes,
--- a/testing/docker/rust-build/Dockerfile
+++ b/testing/docker/rust-build/Dockerfile
@@ -10,24 +10,28 @@ RUN yum upgrade -y
 RUN yum clean all
 
 # Install tooltool directly from github.
 RUN mkdir /builds
 ADD https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py /build/tooltool.py
 RUN chmod +rx /build/tooltool.py
 
 # Add build scripts.
-ADD             fetch_rust.sh build_rust.sh upload_rust.sh /build/
-ADD             repack_rust.sh /build/
+ADD             fetch_rust.sh build_rust.sh /build/
+ADD             fetch_cargo.sh build_cargo.sh /build/
+ADD             package_rust.sh upload_rust.sh /build/
+ADD             repack_rust.py /build/
 RUN             chmod +x /build/*
 
 # Create user for doing the build.
 ENV USER worker
 ENV HOME /home/${USER}
 
 RUN useradd -d ${HOME} -m ${USER}
 
 # Set up the user's tree
 WORKDIR ${HOME}
 
 # Invoke our build scripts by default, but allow other commands.
 USER ${USER}
-ENTRYPOINT /build/fetch_rust.sh && /build/build_rust.sh && /build/upload_rust.sh
+ENTRYPOINT /build/fetch_rust.sh && /build/build_rust.sh && \
+  /build/fetch_cargo.sh && /build/build_cargo.sh && \
+  /build/package_rust.sh && /build/upload_rust.sh
--- a/testing/docker/rust-build/VERSION
+++ b/testing/docker/rust-build/VERSION
@@ -1,1 +1,1 @@
-0.2.1
+0.3.0
new file mode 100644
--- /dev/null
+++ b/testing/docker/rust-build/build_cargo.sh
@@ -0,0 +1,20 @@
+#!/bin/bash -vex
+
+set -x -e
+
+: WORKSPACE ${WORKSPACE:=/home/worker}
+
+set -v
+
+# Configure and build cargo.
+
+if test $(uname -s) = "Darwin"; then
+  export MACOSX_DEPLOYMENT_TARGET=10.7
+fi
+
+pushd ${WORKSPACE}/cargo
+./configure --prefix=${WORKSPACE}/rustc --local-rust-root=${WORKSPACE}/rustc
+make
+make dist
+make install
+popd
--- a/testing/docker/rust-build/build_rust.sh
+++ b/testing/docker/rust-build/build_rust.sh
@@ -4,27 +4,23 @@ set -x -e
 
 : WORKSPACE ${WORKSPACE:=/home/worker}
 
 CORES=$(nproc || grep -c ^processor /proc/cpuinfo || sysctl -n hw.ncpu)
 
 set -v
 
 # Configure and build rust.
-OPTIONS="--enable-llvm-static-stdcpp --disable-docs --release-channel=stable"
+OPTIONS="--enable-llvm-static-stdcpp --disable-docs"
+OPTIONS+="--enable-debuginfo"
+OPTIONS+="--release-channel=stable"
 x32="i686-unknown-linux-gnu"
 x64="x86_64-unknown-linux-gnu"
 arm="arm-linux-androideabi"
 
 mkdir -p ${WORKSPACE}/rust-build
 pushd ${WORKSPACE}/rust-build
 ${WORKSPACE}/rust/configure --prefix=${WORKSPACE}/rustc \
   --target=${x64},${x32} ${OPTIONS}
 make -j ${CORES}
 make dist
 make install
 popd
-
-# Package the toolchain for upload.
-pushd ${WORKSPACE}
-tar cvJf rustc.tar.xz rustc/*
-/build/tooltool.py add --visibility=public --unpack rustc.tar.xz
-popd
--- a/testing/docker/rust-build/build_rust_mac.sh
+++ b/testing/docker/rust-build/build_rust_mac.sh
@@ -1,30 +1,36 @@
 #!/bin/bash -vex
 
+set -e
+
 : WORKSPACE ${WORKSPACE:=$PWD}
+: TOOLTOOL ${TOOLTOOL:=python $WORKSPACE/tooltool.py}
 
 CORES=$(nproc || grep -c ^processor /proc/cpuinfo || sysctl -n hw.ncpu)
 echo Building on $CORES cpus...
 
-OPTIONS="--disable-elf-tls --disable-docs"
+OPTIONS="--enable-debuginfo --disable-docs"
 TARGETS="x86_64-apple-darwin,i686-apple-darwin"
 
 PREFIX=${WORKSPACE}/rustc
 
+set -v
+
 mkdir -p ${WORKSPACE}/gecko-rust-mac
 pushd ${WORKSPACE}/gecko-rust-mac
 
 export MACOSX_DEPLOYMENT_TARGET=10.7
 ${WORKSPACE}/rust/configure --prefix=${PREFIX} --target=${TARGETS} ${OPTIONS}
 make -j ${CORES}
 
 rm -rf ${PREFIX}
 mkdir ${PREFIX}
 make dist
 make install
 popd
 
 # Package the toolchain for upload.
 pushd ${WORKSPACE}
+rustc/bin/rustc --version
 tar cvjf rustc.tar.bz2 rustc/*
-python tooltool.py add --visibility=public --unpack rustc.tar.bz2
+${TOOLTOOL} add --visibility=public --unpack rustc.tar.bz2
 popd
new file mode 100644
--- /dev/null
+++ b/testing/docker/rust-build/fetch_cargo.sh
@@ -0,0 +1,21 @@
+#!/bin/bash -vex
+
+set -x -e
+
+# Inputs, with defaults
+
+: REPOSITORY   ${REPOSITORY:=https://github.com/rust-lang/cargo}
+: BRANCH       ${BRANCH:=master}
+
+: WORKSPACE    ${WORKSPACE:=/home/worker}
+
+set -v
+
+# Check out rust sources
+SRCDIR=${WORKSPACE}/cargo
+git clone --recursive $REPOSITORY -b $BRANCH ${SRCDIR}
+
+# Report version
+VERSION=$(git -C ${SRCDIR} describe --tags --dirty)
+COMMIT=$(git -C ${SRCDIR} rev-parse HEAD)
+echo "cargo ${VERSION} (commit ${COMMIT})" | tee cargo-version
new file mode 100644
--- /dev/null
+++ b/testing/docker/rust-build/package_rust.sh
@@ -0,0 +1,13 @@
+#!/bin/bash -vex
+
+set -x -e
+
+: WORKSPACE ${WORKSPACE:=/home/worker}
+
+set -v
+
+# Package the toolchain for upload.
+pushd ${WORKSPACE}
+tar cvJf rustc.tar.xz rustc/*
+/build/tooltool.py add --visibility=public --unpack rustc.tar.xz
+popd
new file mode 100644
--- /dev/null
+++ b/testing/docker/rust-build/repack_rust.py
@@ -0,0 +1,177 @@
+#!/bin/env python
+'''
+This script downloads and repacks official rust language builds
+with the necessary tool and target support for the Firefox
+build environment.
+'''
+
+import os.path
+import requests
+import subprocess
+import toml
+
+def fetch_file(url):
+  '''Download a file from the given url if it's not already present.'''
+  filename = os.path.basename(url)
+  if os.path.exists(filename):
+    return
+  r = requests.get(url, stream=True)
+  r.raise_for_status()
+  with open(filename, 'wb') as fd:
+    for chunk in r.iter_content(4096):
+      fd.write(chunk)
+
+def fetch(url):
+  '''Download and verify a package url.'''
+  base = os.path.basename(url)
+  print('Fetching %s...' % base)
+  fetch_file(url + '.asc')
+  fetch_file(url)
+  fetch_file(url + '.sha256')
+  fetch_file(url + '.asc.sha256')
+  print('Verifying %s...' % base)
+  subprocess.check_call(['shasum', '-c', base + '.sha256'])
+  subprocess.check_call(['shasum', '-c', base + '.asc.sha256'])
+  subprocess.check_call(['gpg', '--verify', base + '.asc', base])
+  subprocess.check_call(['keybase', 'pgp', 'verify',
+      '-d', base + '.asc',
+      '-i', base,
+  ])
+
+def install(filename, target):
+  '''Run a package's installer script against the given target directory.'''
+  print(' Unpacking %s...' % filename)
+  subprocess.check_call(['tar', 'xf', filename])
+  basename = filename.split('.tar')[0]
+  print(' Installing %s...' % basename)
+  install_cmd = [os.path.join(basename, 'install.sh')]
+  install_cmd += ['--prefix=' + os.path.abspath(target)]
+  install_cmd += ['--disable-ldconfig']
+  subprocess.check_call(install_cmd)
+  print(' Cleaning %s...' % basename)
+  subprocess.check_call(['rm', '-rf', basename])
+
+def package(manifest, pkg, target):
+  '''Pull out the package dict for a particular package and target
+  from the given manifest.'''
+  version = manifest['pkg'][pkg]['version']
+  info = manifest['pkg'][pkg]['target'][target]
+  return (version, info)
+
+def fetch_package(manifest, pkg, host):
+  version, info = package(manifest, pkg, host)
+  print('%s %s\n  %s\n  %s' % (pkg, version, info['url'], info['hash']))
+  if not info['available']:
+    print('%s marked unavailable for %s' % (pkg, host))
+    raise AssertionError
+  fetch(info['url'])
+  return info
+
+def fetch_std(manifest, targets):
+  stds = []
+  for target in targets:
+      info = fetch_package(manifest, 'rust-std', target)
+      stds.append(info)
+  return stds
+
+def tar_for_host(host):
+  if 'linux' in host:
+      tar_options = 'cJf'
+      tar_ext = '.tar.xz'
+  else:
+      tar_options = 'cjf'
+      tar_ext = '.tar.bz2'
+  return tar_options, tar_ext
+
+def repack(host, targets, channel='stable', suffix=''):
+  print("Repacking rust for %s..." % host)
+  url = 'https://static.rust-lang.org/dist/channel-rust-' + channel + '.toml'
+  req = requests.get(url)
+  req.raise_for_status()
+  manifest = toml.loads(req.content)
+  if manifest['manifest-version'] != '2':
+    print('ERROR: unrecognized manifest version %s.' % manifest['manifest-version'])
+    return
+  print('Using manifest for rust %s as of %s.' % (channel, manifest['date']))
+  print('Fetching packages...')
+  rustc = fetch_package(manifest, 'rustc', host)
+  cargo = fetch_package(manifest, 'cargo', host)
+  stds = fetch_std(manifest, targets)
+  print('Installing packages...')
+  tar_basename = 'rustc-' + host
+  if suffix:
+      tar_basename += '-' + suffix
+  tar_basename += '-repack'
+  install_dir = 'rustc'
+  subprocess.check_call(['rm', '-rf', install_dir])
+  install(os.path.basename(rustc['url']), install_dir)
+  install(os.path.basename(cargo['url']), install_dir)
+  for std in stds:
+    install(os.path.basename(std['url']), install_dir)
+    pass
+  print('Tarring %s...' % tar_basename)
+  tar_options, tar_ext = tar_for_host(host)
+  subprocess.check_call(['tar', tar_options, tar_basename + tar_ext, install_dir])
+  subprocess.check_call(['rm', '-rf', install_dir])
+
+def repack_cargo(host, channel='nightly'):
+  print("Repacking cargo for %s..." % host)
+  # Cargo doesn't seem to have a .toml manifest.
+  base_url = 'https://static.rust-lang.org/cargo-dist/'
+  req = requests.get(os.path.join(base_url, 'channel-cargo-' + channel))
+  req.raise_for_status()
+  file = ''
+  for line in req.iter_lines():
+      if line.find(host) != -1:
+          file = line.strip()
+  if not file:
+      print('No manifest entry for %s!' % host)
+      return
+  manifest = {
+          'date': req.headers['Last-Modified'],
+          'pkg': {
+              'cargo': {
+                  'version': channel,
+                  'target': {
+                      host: {
+                          'url': os.path.join(base_url, file),
+                          'hash': None,
+                          'available': True,
+                      },
+                  },
+              },
+          },
+  }
+  print('Using manifest for cargo %s.' % channel)
+  print('Fetching packages...')
+  cargo = fetch_package(manifest, 'cargo', host)
+  print('Installing packages...')
+  install_dir = 'cargo'
+  subprocess.check_call(['rm', '-rf', install_dir])
+  install(os.path.basename(cargo['url']), install_dir)
+  tar_basename = 'cargo-%s-repack' % host
+  print('Tarring %s...' % tar_basename)
+  tar_options, tar_ext = tar_for_host(host)
+  subprocess.check_call(['tar', tar_options, tar_basename + tar_ext, install_dir])
+  subprocess.check_call(['rm', '-rf', install_dir])
+
+# rust platform triples
+android="armv7-linux-androideabi"
+linux64="x86_64-unknown-linux-gnu"
+linux32="i686-unknown-linux-gnu"
+mac64="x86_64-apple-darwin"
+mac32="i686-apple-darwin"
+win64="x86_64-pc-windows-msvc"
+win32="i686-pc-windows-msvc"
+
+if __name__ == '__main__':
+  repack(mac64, [mac64, mac32])
+  repack(win32, [win32])
+  repack(win64, [win64])
+  repack(linux64, [linux64, linux32])
+  repack(linux64, [linux64, mac64, mac32], suffix='mac-cross')
+  repack(linux64, [linux64, android], suffix='android-cross')
+  repack_cargo(mac64)
+  repack_cargo(win32)
+  repack_cargo(win64)
+  repack_cargo(linux64)
deleted file mode 100644
--- a/testing/docker/rust-build/repack_rust.sh
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/bin/bash -vex
-
-set -e
-
-# Set verbose options on taskcluster for logging.
-test -n "$TASK_ID" && set -x
-
-# Inputs, with defaults
-
-: RUST_URL        ${RUST_URL:=https://static.rust-lang.org/dist/}
-: RUST_CHANNEL    ${RUST_CHANNEL:=stable}
-
-: WORKSPACE       ${WORKSPACE:=/home/worker}
-
-die() {
-  echo "ERROR: $@"
-  exit 1
-}
-
-fetch() {
-  echo "Fetching $1..."
-  curl -Os ${RUST_URL}${1}.asc
-  curl -Os ${RUST_URL}${1}
-  curl -Os ${RUST_URL}${1}.sha256
-  curl -Os ${RUST_URL}${1}.asc.sha256
-}
-
-verify() {
-  echo "Verifying $1..."
-  shasum -c ${1}.sha256
-  shasum -c ${1}.asc.sha256
-  gpg --verify ${1}.asc ${1}
-  keybase verify ${1}.asc
-}
-
-fetch_rustc() {
-  arch=$1
-  echo "Retrieving rustc build for $arch..."
-  pkg=$(cat ${IDX} | grep ^rustc | grep $arch)
-  test -n "${pkg}" || die "No rustc build for $arch in the manifest."
-  test 1 == $(echo ${pkg} | wc -w) ||
-    die "Multiple rustc builds for $arch in the manifest."
-  fetch ${pkg}
-  verify ${pkg}
-}
-
-fetch_std() {
-  echo "Retrieving rust-std builds for:"
-  for arch in $@; do
-    echo "  $arch"
-  done
-  for arch in $@; do
-    pkg=$(cat ${IDX} | grep rust-std | grep $arch)
-    test -n "${pkg}" || die "No rust-std builds for $arch in the manifest."
-    test 1 == $(echo ${pkg} | wc -w) ||
-      die "Multiple rust-std builds for $arch in the manifest."
-    fetch ${pkg}
-    verify ${pkg}
-  done
-}
-
-install_rustc() {
-  pkg=$(cat ${IDX} | grep ^rustc | grep $1)
-  base=${pkg%%.tar.*}
-  echo "Installing $base..."
-  tar xf ${pkg}
-  ${base}/install.sh ${INSTALL_OPTS}
-  rm -rf ${base}
-}
-
-install_std() {
-  for arch in $@; do
-    for pkg in $(cat ${IDX} | grep rust-std | grep $arch); do
-      base=${pkg%%.tar.*}
-      echo "Installing $base..."
-      tar xf ${pkg}
-      ${base}/install.sh ${INSTALL_OPTS}
-      rm -rf ${base}
-    done
-  done
-}
-
-check() {
-  if test -x ${TARGET}/bin/rustc; then
-    file ${TARGET}/bin/rustc
-    ${TARGET}/bin/rustc --version
-  elif test -x ${TARGET}/bin/rustc.exe; then
-    file ${TARGET}/bin/rustc.exe
-    ${TARGET}/bin/rustc.exe --version
-  else
-    die "ERROR: Couldn't fine rustc executable"
-  fi
-  echo "Installed components:"
-  for component in $(cat ${TARGET}/lib/rustlib/components); do
-    echo "  $component"
-  done
-  echo
-}
-
-test -n "$TASK_ID" && set -v
-
-linux64="x86_64-unknown-linux-gnu"
-linux32="i686-unknown-linux-gnu"
-
-android="arm-linux-androideabi"
-
-win64="x86_64-pc-windows-msvc"
-win32="i686-pc-windows-msvc"
-win32_i586="i586-pc-windows-msvc"
-
-# Fetch the manifest
-
-IDX=channel-rustc-${RUST_CHANNEL}
-
-fetch ${IDX}
-verify ${IDX}
-
-TARGET=rustc
-INSTALL_OPTS="--prefix=${PWD}/${TARGET} --disable-ldconfig"
-
-# Repack the linux64 builds.
-
-fetch_rustc $linux64
-fetch_std $linux64 $linux32
-
-rm -rf ${TARGET}
-
-install_rustc $linux64
-install_std $linux64 $linux32
-
-tar cJf rustc-$linux64-repack.tar.xz ${TARGET}/*
-check ${TARGET}
-
-# Repack the win64 builds.
-
-fetch_rustc $win64
-fetch_std $win64
-
-rm -rf ${TARGET}
-
-install_rustc $win64
-install_std $win64
-
-tar cjf rustc-$win64-repack.tar.bz2 ${TARGET}/*
-check ${TARGET}
-
-# Repack the win32 builds.
-
-fetch_rustc $win32
-fetch_std $win32 $win32_i586
-
-rm -rf ${TARGET}
-
-install_rustc $win32
-install_std $win32 $win32_i586
-
-tar cjf rustc-$win32-repack.tar.bz2 ${TARGET}/*
-check ${TARGET}
-
-rm -rf ${TARGET}
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -158,16 +158,19 @@ user_pref("layout.css.report_errors", tr
 
 // Enable CSS Grid for testing
 user_pref("layout.css.grid.enabled", true);
 user_pref("layout.css.grid-template-subgrid-value.enabled", true);
 
 // Enable CSS 'contain' for testing
 user_pref("layout.css.contain.enabled", true);
 
+// Enable CSS initial-letter for testing
+user_pref("layout.css.initial-letter.enabled", true);
+
 // Enable CSS object-fit & object-position for testing
 user_pref("layout.css.object-fit-and-position.enabled", true);
 
 // Enable webkit prefixed CSS features for testing
 user_pref("layout.css.prefixes.webkit", true);
 
 // Enable -webkit-{min|max}-device-pixel-ratio media queries for testing
 user_pref("layout.css.prefixes.device-pixel-ratio-webkit", true);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5794,24 +5794,24 @@
     "alert_emails": ["cpearce@mozilla.com"],
     "expires_in_version": "53",
     "kind": "count",
     "description": "How many times the audio MFT decoder returns success but output nothing.",
     "bug_numbers": [1176071]
   },
   "VIDEO_CAN_CREATE_AAC_DECODER": {
     "alert_emails": ["cpearce@mozilla.com"],
-    "expires_in_version": "50",
+    "expires_in_version": "58",
     "kind": "boolean",
     "description": "Whether at startup we report we can playback MP4 (AAC) audio. This is single value is recorded at every startup.",
     "releaseChannelCollection": "opt-out"
   },
   "VIDEO_CAN_CREATE_H264_DECODER": {
     "alert_emails": ["cpearce@mozilla.com"],
-    "expires_in_version": "50",
+    "expires_in_version": "58",
     "kind": "boolean",
     "description": "Whether at startup we report we can playback MP4 (H.264) video. This is single value is recorded at every startup.",
     "releaseChannelCollection": "opt-out"
   },
   "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG": {
     "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 128,
@@ -8276,46 +8276,16 @@
   "VIDEO_UNLOAD_STATE": {
     "alert_emails": ["ajones@mozilla.com"],
     "expires_in_version": "55",
     "kind": "enumerated",
     "n_values": 5,
     "description": "HTML Media Element state when unloading. ended = 0, paused = 1, stalled = 2, seeking = 3, other = 4",
     "bug_numbers": [1261955, 1261955]
   },
-  "VIDEO_ADOBE_GMP_DISAPPEARED": {
-    "alert_emails": ["cpearce@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "flag",
-    "description": "Whether or not the Adobe EME GMP was expected to be resident on disk but mysteriously isn't.",
-    "releaseChannelCollection": "opt-out"
-  },
-  "VIDEO_ADOBE_GMP_MISSING_FILES": {
-    "alert_emails": ["cpearce@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "enumerated",
-    "n_values" : 8,
-    "description": "Adobe EME GMP files missing (0=none, or sum of: 1=library, 2=info, 4=voucher)",
-    "releaseChannelCollection": "opt-out"
-  },
-  "VIDEO_OPENH264_GMP_DISAPPEARED": {
-    "alert_emails": ["cpearce@mozilla.com", "rjesup@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "flag",
-    "description": "Whether or not the OpenH264 GMP was expected to be resident on disk but mysteriously isn't.",
-    "releaseChannelCollection": "opt-out"
-  },
-  "VIDEO_OPENH264_GMP_MISSING_FILES": {
-    "alert_emails": ["cpearce@mozilla.com", "rjesup@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "enumerated",
-    "n_values" : 4,
-    "description": "OpenH264 GMP files missing (0=none, or sum of: 1=library, 2=info)",
-    "releaseChannelCollection": "opt-out"
-  },
   "VIDEO_VP9_BENCHMARK_FPS": {
     "alert_emails": ["ajones@mozilla.com"],
     "expires_in_version": "55",
     "bug_numbers": [1230265],
     "kind": "linear",
     "high": 1000,
     "n_buckets": 100,
     "description": "720p VP9 decode benchmark measurement in frames per second",
@@ -9366,40 +9336,16 @@
     "expires_in_version": "70",
     "kind": "exponential",
     "low": 1,
     "high": 10000000,
     "n_buckets": 20,
     "keyed": true,
     "description": "Contiguous time spent by an add-on blocking the main loop by performing a blocking cross-process call (microseconds, keyed by add-on ID)."
   },
-  "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON": {
-    "alert_emails": ["edwin@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "enumerated",
-    "n_values": 10,
-    "releaseChannelCollection": "opt-out",
-    "description": "Reason for Adobe CDM failing to update. (1 = GMP_INVALID; 2 = GMP_HIDDEN; 3 = GMP_DISABLED; 4 = GMP_UPDATE_DISABLED)"
-  },
-  "VIDEO_EME_ADOBE_HIDDEN_REASON": {
-    "alert_emails": ["edwin@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "enumerated",
-    "n_values": 10,
-    "releaseChannelCollection": "opt-out",
-    "description": "Reason for Adobe CDM being hidden. (1 = UNSUPPORTED; 2 = EME_DISABLED)"
-  },
-  "VIDEO_EME_ADOBE_UNSUPPORTED_REASON": {
-    "alert_emails": ["edwin@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "enumerated",
-    "n_values": 10,
-    "releaseChannelCollection": "opt-out",
-    "description": "Reason for reporting the Adobe CDM to be unsupported. (1 = NOT_WINDOWS; 2 = WINDOWS_VERSION)"
-  },
   "VIDEO_EME_REQUEST_SUCCESS_LATENCY_MS": {
     "alert_emails": ["cpearce@mozilla.com"],
     "expires_in_version": "55",
     "kind": "exponential",
     "high": 60000,
     "n_buckets": 60,
     "releaseChannelCollection": "opt-out",
     "description": "Time spent waiting for a navigator.requestMediaKeySystemAccess call to succeed."
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -2220,35 +2220,28 @@
     "URLCLASSIFIER_CL_UPDATE_TIME",
     "URLCLASSIFIER_LC_COMPLETIONS",
     "URLCLASSIFIER_LC_PREFIXES",
     "URLCLASSIFIER_LOOKUP_TIME",
     "URLCLASSIFIER_PS_CONSTRUCT_TIME",
     "URLCLASSIFIER_PS_FAILURE",
     "URLCLASSIFIER_PS_FALLOCATE_TIME",
     "URLCLASSIFIER_PS_FILELOAD_TIME",
-    "VIDEO_ADOBE_GMP_DISAPPEARED",
-    "VIDEO_ADOBE_GMP_MISSING_FILES",
     "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG",
     "VIDEO_CANPLAYTYPE_H264_LEVEL",
     "VIDEO_CANPLAYTYPE_H264_PROFILE",
     "VIDEO_CAN_CREATE_AAC_DECODER",
     "VIDEO_CAN_CREATE_H264_DECODER",
     "VIDEO_DECODED_H264_SPS_CONSTRAINT_SET_FLAG",
     "VIDEO_DECODED_H264_SPS_LEVEL",
     "VIDEO_DECODED_H264_SPS_PROFILE",
-    "VIDEO_EME_ADOBE_HIDDEN_REASON",
-    "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON",
-    "VIDEO_EME_ADOBE_UNSUPPORTED_REASON",
     "VIDEO_EME_PLAY_SUCCESS",
     "VIDEO_EME_REQUEST_FAILURE_LATENCY_MS",
     "VIDEO_EME_REQUEST_SUCCESS_LATENCY_MS",
     "VIDEO_H264_SPS_MAX_NUM_REF_FRAMES",
-    "VIDEO_OPENH264_GMP_DISAPPEARED",
-    "VIDEO_OPENH264_GMP_MISSING_FILES",
     "VIEW_SOURCE_EXTERNAL_RESULT_BOOLEAN",
     "VIEW_SOURCE_IN_BROWSER_OPENED_BOOLEAN",
     "VIEW_SOURCE_IN_WINDOW_OPENED_BOOLEAN",
     "WEAVE_COMPLETE_SUCCESS_COUNT",
     "WEAVE_CONFIGURED",
     "WEAVE_CONFIGURED_MASTER_PASSWORD",
     "WEAVE_START_COUNT",
     "WEBCRYPTO_ALG",
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -546,19 +546,18 @@
       function assertMatches(aTest, aMatches) {
         is(aMatches[1], String(aTest.current),
           "Currently highlighted match should be at " + aTest.current);
         is(aMatches[2], String(aTest.total),
           "Total amount of matches should be " + aTest.total);
       }
 
       for (let test of tests) {
-        promise = promiseMatchesCountResult();
-        gFindBar.clear();
-        yield promise;
+        gFindBar._findField.select();
+        gFindBar._findField.focus();
 
         promise = promiseMatchesCountResult();
         yield enterStringIntoFindField(test.text, false);
         yield promise;
         let matches = foundMatches.value.match(regex);
         if (!test.total) {
           ok(!matches, "No message should be shown when 0 matches are expected");
         } else {
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -548,17 +548,18 @@
 
           this._setHighlightAll(aHighlight, aFromPrefObserver);
 
           let word = this._findField.value;
           // Bug 429723. Don't attempt to highlight ""
           if (aHighlight && !word)
             return;
 
-          this.browser.finder.highlight(aHighlight, word);
+          this.browser.finder.highlight(aHighlight, word,
+            this._findMode == this.FIND_LINKS);
 
           // Update the matches count
           this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
         ]]></body>
       </method>
 
       <!--
         - Updates the highlight-all mode of the findbar and its UI.
--- a/toolkit/locales/en-US/chrome/global/filepicker.properties
+++ b/toolkit/locales/en-US/chrome/global/filepicker.properties
@@ -47,9 +47,9 @@ errorCreateNewDirTitle=Error creating %S
 errorCreateNewDirMessage=Directory %S could not be created
 errorCreateNewDirIsFileMessage=Directory cannot be created, %S is a file
 errorCreateNewDirPermissionMessage=Directory cannot be created, %S not writable
 
 promptNewDirTitle=Create new directory
 promptNewDirMessage=Directory name:
 
 errorPathProblemTitle=Unknown Error
-errorPathProblemMessage=An unknown error occured (path %S)
+errorPathProblemMessage=An unknown error occurred (path %S)
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -35,17 +35,26 @@ function Finder(docShell) {
   this._highlighter = null;
 
   docShell.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebProgress)
           .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
 }
 
 Finder.prototype = {
+  get iterator() {
+    if (this._iterator)
+      return this._iterator;
+    this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
+    return this._iterator;
+  },
+
   destroy: function() {
+    if (this._iterator)
+      this._iterator.reset();
     if (this._highlighter) {
       this._highlighter.clear();
       this._highlighter.hide();
     }
     this.listeners = [];
     this._docShell.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIWebProgress)
       .removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
@@ -82,16 +91,22 @@ Finder.prototype = {
 
       linkURL = TextToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
     }
 
     options.linkURL = linkURL;
     options.rect = this._getResultRect();
     options.searchString = this._searchString;
 
+    if (!this.iterator.continueRunning({
+      linksOnly: options.linksOnly,
+      word: options.searchString
+    })) {
+      this.iterator.stop();
+    }
     this.highlighter.update(options);
 
     for (let l of this._listeners) {
       try {
         l.onFindResult(options);
       } catch (ex) {}
     }
   },
@@ -145,17 +160,18 @@ Finder.prototype = {
   fastFind: function (aSearchString, aLinksOnly, aDrawOutline) {
     this._lastFindResult = this._fastFind.find(aSearchString, aLinksOnly);
     let searchString = this._fastFind.searchString;
     this._notify({
       searchString,
       result: this._lastFindResult,
       findBackwards: false,
       findAgain: false,
-      drawOutline: aDrawOutline
+      drawOutline: aDrawOutline,
+      linksOnly: aLinksOnly
     });
   },
 
   /**
    * Repeat the previous search. Should only be called after a previous
    * call to Finder.fastFind.
    *
    * @param aFindBackwards Controls the search direction:
@@ -165,18 +181,19 @@ Finder.prototype = {
    */
   findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) {
     this._lastFindResult = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
     let searchString = this._fastFind.searchString;
     this._notify({
       searchString,
       result: this._lastFindResult,
       findBackwards: aFindBackwards,
-      fidnAgain: true,
-      drawOutline: aDrawOutline
+      findAgain: true,
+      drawOutline: aDrawOutline,
+      linksOnly: aLinksOnly
     });
   },
 
   /**
    * Forcibly set the search string of the find clipboard to the currently
    * selected text in the window, on supported platforms (i.e. OSX).
    */
   setSearchStringToSelection: function() {
@@ -185,20 +202,18 @@ Finder.prototype = {
     // Empty strings are rather useless to search for.
     if (!searchString.length)
       return null;
 
     this.clipboardSearchString = searchString;
     return searchString;
   },
 
-  highlight: Task.async(function* (aHighlight, aWord) {
-    this.highlighter.maybeAbort();
-
-    let found = yield this.highlighter.highlight(aHighlight, aWord, null);
+  highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) {
+    let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly);
     this.highlighter.notifyFinished(aHighlight);
     if (aHighlight) {
       let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
                          : Ci.nsITypeAheadFind.FIND_NOTFOUND;
       this._notify({
         searchString: aWord,
         result,
         findBackwards: false,
@@ -351,189 +366,58 @@ Finder.prototype = {
       try {
         l.onMatchesCountResult(result);
       } catch (ex) {}
     }
   },
 
   requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
     if (this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
-        this.searchString == "") {
-      return this._notifyMatchesCount({
+        this.searchString == "" || !aWord) {
+      this._notifyMatchesCount({
         total: 0,
         current: 0
       });
-    }
-    let window = this._getWindow();
-    let result = this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, window);
-
-    // Count matches in (i)frames AFTER searching through the main window.
-    for (let frame of result._framesToCount) {
-      // We've reached our limit; no need to do more work.
-      if (result.total == -1 || result.total == aMatchLimit)
-        break;
-      this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, frame, result);
+      return;
     }
 
-    // The `_currentFound` and `_framesToCount` properties are only used for
-    // internal bookkeeping between recursive calls.
-    delete result._currentFound;
-    delete result._framesToCount;
-
-    this._notifyMatchesCount(result);
-    return undefined;
-  },
-
-  /**
-   * Counts the number of matches for the searched word in the passed window's
-   * content.
-   * @param aWord
-   *        the word to search for.
-   * @param aMatchLimit
-   *        the maximum number of matches shown (for speed reasons).
-   * @param aLinksOnly
-   *        whether we should only search through links.
-   * @param aWindow
-   *        the window to search in. Passing undefined will search the
-   *        current content window. Optional.
-   * @param aStats
-   *        the Object that is returned by this function. It may be passed as an
-   *        argument here in the case of a recursive call.
-   * @returns an object stating the number of matches and a vector for the current match.
-   */
-  _countMatchesInWindow: function(aWord, aMatchLimit, aLinksOnly, aWindow = null, aStats = null) {
-    aWindow = aWindow || this._getWindow();
-    aStats = aStats || {
+    let window = this._getWindow();
+    let result = {
       total: 0,
       current: 0,
-      _framesToCount: new Set(),
       _currentFound: false
     };
-
-    // If we already reached our max, there's no need to do more work!
-    if (aStats.total == -1 || aStats.total == aMatchLimit) {
-      aStats.total = -1;
-      return aStats;
-    }
-
-    this._collectFrames(aWindow, aStats);
-
     let foundRange = this._fastFind.getFoundRange();
 
-    for(let range of this._findIterator(aWord, aWindow)) {
-      if (!aLinksOnly || this._rangeStartsInLink(range)) {
-        ++aStats.total;
-        if (!aStats._currentFound) {
-          ++aStats.current;
-          aStats._currentFound = (foundRange &&
+    this.iterator.start({
+      finder: this,
+      limit: aMatchLimit,
+      linksOnly: aLinksOnly,
+      onRange: range => {
+        ++result.total;
+        if (!result._currentFound) {
+          ++result.current;
+          result._currentFound = (foundRange &&
             range.startContainer == foundRange.startContainer &&
             range.startOffset == foundRange.startOffset &&
             range.endContainer == foundRange.endContainer &&
             range.endOffset == foundRange.endOffset);
         }
-      }
-      if (aStats.total == aMatchLimit) {
-        aStats.total = -1;
-        break;
-      }
-    }
-
-    return aStats;
-  },
-
-  /**
-   * Basic wrapper around nsIFind that provides a generator yielding
-   * a range each time an occurence of `aWord` string is found.
-   *
-   * @param aWord
-   *        the word to search for.
-   * @param aWindow
-   *        the window to search in.
-   */
-  _findIterator: function* (aWord, aWindow) {
-    let doc = aWindow.document;
-    let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
-               doc.body : doc.documentElement;
-
-    if (!body)
-      return;
-
-    let searchRange = doc.createRange();
-    searchRange.selectNodeContents(body);
-
-    let startPt = searchRange.cloneRange();
-    startPt.collapse(true);
-
-    let endPt = searchRange.cloneRange();
-    endPt.collapse(false);
-
-    let retRange = null;
-
-    let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
-                   .createInstance()
-                   .QueryInterface(Ci.nsIFind);
-    finder.caseSensitive = this._fastFind.caseSensitive;
-    finder.entireWord = this._fastFind.entireWord;
+      },
+      useCache: true,
+      word: aWord
+    }).then(() => {
+      // The `_currentFound` property is only used for internal bookkeeping.
+      delete result._currentFound;
 
-    while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
-      yield retRange;
-      startPt = retRange.cloneRange();
-      startPt.collapse(false);
-    }
-  },
+      if (result.total == aMatchLimit)
+        result.total = -1;
 
-  /**
-   * Helper method for `_countMatchesInWindow` that recursively collects all
-   * visible (i)frames inside a window.
-   *
-   * @param aWindow
-   *        the window to extract the (i)frames from.
-   * @param aStats
-   *        Object that contains a Set called '_framesToCount'
-   */
-  _collectFrames: function(aWindow, aStats) {
-    if (!aWindow.frames || !aWindow.frames.length)
-      return;
-    // Casting `aWindow.frames` to an Iterator doesn't work, so we're stuck with
-    // a plain, old for-loop.
-    for (let i = 0, l = aWindow.frames.length; i < l; ++i) {
-      let frame = aWindow.frames[i];
-      // Don't count matches in hidden frames.
-      let frameEl = frame && frame.frameElement;
-      if (!frameEl)
-        continue;
-      // Construct a range around the frame element to check its visiblity.
-      let range = aWindow.document.createRange();
-      range.setStart(frameEl, 0);
-      range.setEnd(frameEl, 0);
-      if (!this._fastFind.isRangeVisible(range, this._getDocShell(range), true))
-        continue;
-      // All good, so add it to the set to count later.
-      if (!aStats._framesToCount.has(frame))
-        aStats._framesToCount.add(frame);
-      this._collectFrames(frame, aStats);
-    }
-  },
-
-  /**
-   * Helper method to extract the docShell reference from a Window or Range object.
-   *
-   * @param aWindowOrRange
-   *        Window object to query. May also be a Range, from which the owner
-   *        window will be queried.
-   * @returns nsIDocShell
-   */
-  _getDocShell: function(aWindowOrRange) {
-    let window = aWindowOrRange;
-    // Ranges may also be passed in, so fetch its window.
-    if (aWindowOrRange instanceof Ci.nsIDOMRange)
-      window = aWindowOrRange.startContainer.ownerDocument.defaultView;
-    return window.QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIWebNavigation)
-                 .QueryInterface(Ci.nsIDocShell);
+      this._notifyMatchesCount(result);
+    });
   },
 
   _getWindow: function () {
     return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
   },
 
   /**
    * Get the bounding selection rect in CSS px relative to the origin of the
@@ -639,61 +523,26 @@ Finder.prototype = {
                           .QueryInterface(Ci.nsIDocShell);
 
     let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsISelectionDisplay)
                              .QueryInterface(Ci.nsISelectionController);
     return controller;
   },
 
-  /**
-   * Determines whether a range is inside a link.
-   * @param aRange
-   *        the range to check
-   * @returns true if the range starts in a link
-   */
-  _rangeStartsInLink: function(aRange) {
-    let isInsideLink = false;
-    let node = aRange.startContainer;
-
-    if (node.nodeType == node.ELEMENT_NODE) {
-      if (node.hasChildNodes) {
-        let childNode = node.item(aRange.startOffset);
-        if (childNode)
-          node = childNode;
-      }
-    }
-
-    const XLink_NS = "http://www.w3.org/1999/xlink";
-    const HTMLAnchorElement = (node.ownerDocument || node).defaultView.HTMLAnchorElement;
-    do {
-      if (node instanceof HTMLAnchorElement) {
-        isInsideLink = node.hasAttribute("href");
-        break;
-      } else if (typeof node.hasAttributeNS == "function" &&
-                 node.hasAttributeNS(XLink_NS, "href")) {
-        isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
-        break;
-      }
-
-      node = node.parentNode;
-    } while (node);
-
-    return isInsideLink;
-  },
-
   // Start of nsIWebProgressListener implementation.
 
   onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
     if (!aWebProgress.isTopLevel)
       return;
 
     // Avoid leaking if we change the page.
     this._previousLink = null;
     this.highlighter.onLocationChange();
+    this.iterator.reset();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference])
 };
 
 function GetClipboardSearchString(aLoadContext) {
   let searchString = "";
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -9,17 +9,16 @@ this.EXPORTED_SYMBOLS = ["FinderHighligh
 const { interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
 
-const kHighlightIterationSizeMax = 100;
 const kModalHighlightRepaintFreqMs = 10;
 const kModalHighlightPref = "findbar.modalHighlight";
 const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
   "font-size-adjust", "font-stretch", "font-variant", "font-weight", "letter-spacing",
   "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
 const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
   let parts = prop.split("-");
   return parts.shift() + parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -96,16 +95,23 @@ const kXULNS = "http://www.mozilla.org/k
  * @param {Finder} finder Finder.jsm instance
  */
 function FinderHighlighter(finder) {
   this.finder = finder;
   this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
 }
 
 FinderHighlighter.prototype = {
+  get iterator() {
+    if (this._iterator)
+      return this._iterator;
+    this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
+    return this._iterator;
+  },
+
   get modalStyleSheet() {
     if (!this._modalStyleSheet) {
       this._modalStyleSheet = kModalStyle.replace(/(\.|#)findbar-/g,
         "$1" + kModalIdPrefix + "-findbar-");
     }
     return this._modalStyleSheet;
   },
 
@@ -126,83 +132,52 @@ FinderHighlighter.prototype = {
     for (let l of this.finder._listeners) {
       try {
         l.onHighlightFinished(highlight);
       } catch (ex) {}
     }
   },
 
   /**
-   * Whilst the iterator is running, it's possible to abort it. This may be useful
-   * if the word to highlight was updated in the meantime.
-   */
-  maybeAbort() {
-    this.clear();
-    if (!this._abortHighlight) {
-      return;
-    }
-    this._abortHighlight();
-  },
-
-  /**
-   * Uses the iterator in Finder.jsm to find all the words to highlight and makes
-   * sure not to block the thread whilst running.
-   *
-   * @param {String}       word    Needle to search for and highlight when found
-   * @param {nsIDOMWindow} window  Window object, whose DOM tree should be traversed
-   * @param {Function}     onFind  Callback invoked for each found occurrence
-   * @yield {Promise} that resolves once the iterator has finished
-   */
-  iterator: Task.async(function* (word, window, onFind) {
-    let count = 0;
-    for (let range of this.finder._findIterator(word, window)) {
-      onFind(range);
-      if (++count >= kHighlightIterationSizeMax) {
-        count = 0;
-        // Sleep for the rest of this cycle.
-        yield new Promise(resolve => resolve());
-      }
-    }
-  }),
-
-  /**
    * Toggle highlighting all occurrences of a word in a page. This method will
    * be called recursively for each (i)frame inside a page.
    *
-   * @param {Booolean}     highlight Whether highlighting should be turned on
-   * @param {String}       word      Needle to search for and highlight when found
-   * @param {nsIDOMWindow} window    Window object, whose DOM tree should be traversed
-   * @yield {Promise} that resolves once the operation has finished
+   * @param {Booolean} highlight Whether highlighting should be turned on
+   * @param {String}   word      Needle to search for and highlight when found
+   * @param {Boolean}  linksOnly Only consider nodes that are links for the search
+   * @yield {Promise}  that resolves once the operation has finished
    */
-  highlight: Task.async(function* (highlight, word, window) {
-    let finderWindow = this.finder._getWindow();
-    window = window || finderWindow;
-    let found = false;
-    for (let i = 0; window.frames && i < window.frames.length; i++) {
-      if (yield this.highlight(highlight, word, window.frames[i])) {
-        found = true;
-      }
-    }
-
+  highlight: Task.async(function* (highlight, word, linksOnly) {
+    let window = this.finder._getWindow();
     let controller = this.finder._getSelectionController(window);
     let doc = window.document;
+    let found = false;
+
+    this.clear();
+
     if (!controller || !doc || !doc.documentElement) {
       // Without the selection controller,
       // we are unable to (un)highlight any matches
       return found;
     }
 
     if (highlight) {
-      yield this.iterator(word, window, range => {
-        this.highlightRange(range, controller, finderWindow);
-        found = true;
+      yield this.iterator.start({
+        linksOnly, word,
+        finder: this.finder,
+        onRange: range => {
+          this.highlightRange(range, controller, window);
+          found = true;
+        },
+        useCache: true
       });
     } else {
       this.hide(window);
       this.clear();
+      this.iterator.reset();
 
       // Removing the highlighting always succeeds, so return true.
       found = true;
     }
 
     return found;
   }),
 
@@ -553,17 +528,16 @@ FinderHighlighter.prototype = {
     for (let dims of range.getClientRects()) {
       rects.add({
         height: dims.bottom - dims.top,
         width: dims.right - dims.left,
         y: dims.top + scrollY,
         x: dims.left + scrollX
       });
     }
-    range.collapse();
 
     if (!this._modalHighlightRectsMap)
       this._modalHighlightRectsMap = new Map();
     this._modalHighlightRectsMap.set(range, rects);
 
     this.show(window);
     // We don't repaint the mask right away, but pass it off to a render loop of
     // sorts.
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/FinderIterator.jsm
@@ -0,0 +1,492 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["FinderIterator"];
+
+const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+const kIterationSizeMax = 100;
+
+/**
+ * FinderIterator singleton. See the documentation for the `start()` method to
+ * learn more.
+ */
+this.FinderIterator = {
+  _currentParams: null,
+  _listeners: new Map(),
+  _catchingUp: new Set(),
+  _previousParams: null,
+  _previousRanges: [],
+  _spawnId: 0,
+  ranges: [],
+  running: false,
+
+  // Expose `kIterationSizeMax` to the outside world for unit tests to use.
+  get kIterationSizeMax() { return kIterationSizeMax },
+
+  /**
+   * Start iterating the active Finder docShell, using the options below. When
+   * it already started at the request of another consumer, we first yield the
+   * results we already collected before continuing onward to yield fresh results.
+   * We make sure to pause every `kIterationSizeMax` iterations to make sure we
+   * don't block the host process too long. In the case of a break like this, we
+   * yield `undefined`, instead of a range.
+   * Upon re-entrance after a break, we check if `stop()` was called during the
+   * break and if so, we stop iterating.
+   * Results are also passed to the `onRange` callback method, along with a flag
+   * that specifies if the result comes from the cache or is fresh. The callback
+   * also adheres to the `limit` flag.
+   * The returned promise is resolved when 1) the limit is reached, 2) when all
+   * the ranges have been found or 3) when `stop()` is called whilst iterating.
+   *
+   * @param {Finder}   options.finder      Currently active Finder instance
+   * @param {Number}   [options.limit]     Limit the amount of results to be
+   *                                       passed back. Optional, defaults to no
+   *                                       limit.
+   * @param {Boolean}  [options.linksOnly] Only yield ranges that are inside a
+   *                                       hyperlink (used by QuickFind).
+   *                                       Optional, defaults to `false`.
+   * @param {Function} options.onRange     Callback invoked when a range is found
+   * @param {Boolean}  [options.useCache]  Whether to allow results already
+   *                                       present in the cache or demand fresh.
+   *                                       Optional, defaults to `false`.
+   * @param {String}   options.word        Word to search for
+   * @return {Promise}
+   */
+  start({ finder, limit, linksOnly, onRange, useCache, word }) {
+    // Take care of default values for non-required options.
+    if (typeof limit != "number")
+      limit = -1;
+    if (typeof linksOnly != "boolean")
+      linksOnly = false;
+    if (typeof useCache != "boolean")
+      useCache = false;
+
+    // Validate the options.
+    if (!finder)
+      throw new Error("Missing required option 'finder'");
+    if (!word)
+      throw new Error("Missing required option 'word'");
+    if (typeof onRange != "function")
+      throw new TypeError("Missing valid, required option 'onRange'");
+
+    // Don't add the same listener twice.
+    if (this._listeners.has(onRange))
+      throw new Error("Already listening to iterator results");
+
+    let window = finder._getWindow();
+    let resolver;
+    let promise = new Promise(resolve => resolver = resolve);
+    let iterParams = { linksOnly, useCache, word };
+
+    this._listeners.set(onRange, { limit, onEnd: resolver });
+
+    // If we're not running anymore and we're requesting the previous result, use it.
+    if (!this.running && this._previousResultAvailable(iterParams)) {
+      this._yieldPreviousResult(onRange, window);
+      return promise;
+    }
+
+    if (this.running) {
+      // Double-check if we're not running the iterator with a different set of
+      // parameters, otherwise throw an error with the most common reason.
+      if (!this._areParamsEqual(this._currentParams, iterParams))
+        throw new Error(`We're currently iterating over '${this._currentParams.word}', not '${word}'`);
+
+      // if we're still running, yield the set we have built up this far.
+      this._yieldIntermediateResult(onRange, window);
+
+      return promise;
+    }
+
+    // Start!
+    this.running = true;
+    this._currentParams = iterParams;
+    this._findAllRanges(finder, window, ++this._spawnId);
+
+    return promise;
+  },
+
+  /**
+   * Stop the currently running iterator as soon as possible and optionally cache
+   * the result for later.
+   *
+   * @param {Boolean} [cachePrevious] Whether to save the result for later.
+   *                                  Optional.
+   */
+  stop(cachePrevious = false) {
+    if (!this.running)
+      return;
+
+    if (cachePrevious) {
+      this._previousRanges = [].concat(this.ranges);
+      this._previousParams = Object.assign({}, this._currentParams);
+    } else {
+      this._previousRanges = [];
+      this._previousParams = null;
+    }
+
+    this._catchingUp.clear();
+    this._currentParams = null;
+    this.ranges = [];
+    this.running = false;
+
+    for (let [, { onEnd }] of this._listeners)
+      onEnd();
+    this._listeners.clear();
+  },
+
+  /**
+   * Reset the internal state of the iterator. Typically this would be called
+   * when the docShell is not active anymore, which makes the current and cached
+   * previous result invalid.
+   * If the iterator is running, it will be stopped as soon as possible.
+   */
+  reset() {
+    this._catchingUp.clear();
+    this._currentParams = this._previousParams = null;
+    this._previousRanges = [];
+    this.ranges = [];
+    this.running = false;
+
+    for (let [, { onEnd }] of this._listeners)
+      onEnd();
+    this._listeners.clear();
+  },
+
+  /**
+   * Check if the currently running iterator parameters are the same as the ones
+   * passed through the arguments. When `true`, we can keep it running as-is and
+   * the consumer should stop the iterator when `false`.
+   *
+   * @param  {Boolean} options.linksOnly Whether to search for the word to be
+   *                                     present in links only
+   * @param  {String}  options.word      The word being searched for
+   * @return {Boolean}
+   */
+  continueRunning({ linksOnly, word }) {
+    return (this.running &&
+      this._currentParams.linksOnly === linksOnly &&
+      this._currentParams.word == word);
+  },
+
+  /**
+   * Internal; check if an iteration request is available in the previous result
+   * that we cached.
+   *
+   * @param  {Boolean} options.linksOnly Whether to search for the word to be
+   *                                     present in links only
+   * @param  {Boolean} options.useCache  Whether the consumer wants to use the
+   *                                     cached previous result at all
+   * @param  {String}  options.word      The word being searched for
+   * @return {Boolean}
+   */
+  _previousResultAvailable({ linksOnly, useCache, word }) {
+    return !!(useCache &&
+      this._areParamsEqual(this._previousParams, { word, linksOnly }) &&
+      this._previousRanges.length);
+  },
+
+  /**
+   * Internal; compare if two sets of iterator parameters are equivalent.
+   *
+   * @param  {Object} paramSet1 First set of params (left hand side)
+   * @param  {Object} paramSet2 Second set of params (right hand side)
+   * @return {Boolean}
+   */
+  _areParamsEqual(paramSet1, paramSet2) {
+    return (!!paramSet1 && !!paramSet2 &&
+      paramSet1.linksOnly === paramSet2.linksOnly &&
+      paramSet1.word == paramSet2.word);
+  },
+
+  /**
+   * Internal; iterate over a predefined set of ranges that have been collected
+   * before.
+   * Also here, we make sure to pause every `kIterationSizeMax` iterations to
+   * make sure we don't block the host process too long. In the case of a break
+   * like this, we yield `undefined`, instead of a range.
+   *
+   * @param {Function}     onRange     Callback invoked when a range is found
+   * @param {Array}        rangeSource Set of ranges to iterate over
+   * @param {nsIDOMWindow} window      The window object is only really used
+   *                                   for access to `setTimeout`
+   * @yield {nsIDOMRange}
+   */
+  _yieldResult: function* (onRange, rangeSource, window) {
+    // We keep track of the number of iterations to allow a short pause between
+    // every `kIterationSizeMax` number of iterations.
+    let iterCount = 0;
+    let { limit, onEnd } = this._listeners.get(onRange);
+    let ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
+    for (let range of ranges) {
+      try {
+        range.startContainer;
+      } catch (ex) {
+        // Don't yield dead objects, so use the escape hatch.
+        if (ex.message.includes("dead object"))
+          return;
+      }
+
+      // Pass a flag that is `true` when we're returning the result from a
+      // cached previous iteration.
+      onRange(range, !this.running);
+      yield range;
+
+      if (++iterCount >= kIterationSizeMax) {
+        iterCount = 0;
+        // Make sure to save the current limit for later.
+        this._listeners.set(onRange, { limit, onEnd });
+        // Sleep for the rest of this cycle.
+        yield new Promise(resolve => window.setTimeout(resolve, 0));
+        // After a sleep, the set of ranges may have updated.
+        ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
+      }
+
+      if (limit !== -1 && --limit === 0) {
+        // We've reached our limit; no need to do more work.
+        this._listeners.delete(onRange);
+        onEnd();
+        return;
+      }
+    }
+
+    // Save the updated limit globally.
+    this._listeners.set(onRange, { limit, onEnd });
+  },
+
+  /**
+   * Internal; iterate over the set of previously found ranges. Meanwhile it'll
+   * mark the listener as 'catching up', meaning it will not receive fresh
+   * results from a running iterator.
+   *
+   * @param {Function}     onRange Callback invoked when a range is found
+   * @param {nsIDOMWindow} window  The window object is only really used
+   *                               for access to `setTimeout`
+   * @yield {nsIDOMRange}
+   */
+  _yieldPreviousResult: Task.async(function* (onRange, window) {
+    this._catchingUp.add(onRange);
+    yield* this._yieldResult(onRange, this._previousRanges, window);
+    this._catchingUp.delete(onRange);
+    let { onEnd } = this._listeners.get(onRange);
+    if (onEnd) {
+      onEnd();
+      this._listeners.delete(onRange);
+    }
+  }),
+
+  /**
+   * Internal; iterate over the set of already found ranges. Meanwhile it'll
+   * mark the listener as 'catching up', meaning it will not receive fresh
+   * results from the running iterator.
+   *
+   * @param {Function}     onRange Callback invoked when a range is found
+   * @param {nsIDOMWindow} window  The window object is only really used
+   *                               for access to `setTimeout`
+   * @yield {nsIDOMRange}
+   */
+  _yieldIntermediateResult: Task.async(function* (onRange, window) {
+    this._catchingUp.add(onRange);
+    yield* this._yieldResult(onRange, this.ranges, window);
+    this._catchingUp.delete(onRange);
+  }),
+
+  /**
+   * Internal; see the documentation of the start() method above.
+   *
+   * @param {Finder}       finder  Currently active Finder instance
+   * @param {nsIDOMWindow} window  The window to search in
+   * @param {Number}       spawnId Since `stop()` is synchronous and this method
+   *                               is not, this identifier is used to learn if
+   *                               it's supposed to still continue after a pause.
+   * @yield {nsIDOMRange}
+   */
+  _findAllRanges: Task.async(function* (finder, window, spawnId) {
+    // First we collect all frames we need to search through, whilst making sure
+    // that the parent window gets dibs.
+    let frames = [window].concat(this._collectFrames(window, finder));
+    let { linksOnly, word } = this._currentParams;
+    let iterCount = 0;
+    for (let frame of frames) {
+      for (let range of this._iterateDocument(word, frame, finder)) {
+        // Between iterations, for example after a sleep of one cycle, we could
+        // have gotten the signal to stop iterating. Make sure we do here.
+        if (!this.running || spawnId !== this._spawnId)
+          return;
+
+        // Deal with links-only mode here.
+        if (linksOnly && this._rangeStartsInLink(range))
+          continue;
+
+        this.ranges.push(range);
+
+        // Call each listener with the range we just found.
+        for (let [onRange, { limit, onEnd }] of this._listeners) {
+          if (this._catchingUp.has(onRange))
+            continue;
+
+          onRange(range);
+
+          if (limit !== -1 && --limit === 0) {
+            // We've reached our limit; no need to do more work for this listener.
+            this._listeners.delete(onRange);
+            onEnd();
+            continue;
+          }
+
+          // Save the updated limit globally.
+          this._listeners.set(onRange, { limit, onEnd });
+        }
+
+        yield range;
+
+        if (++iterCount >= kIterationSizeMax) {
+          iterCount = 0;
+          // Sleep for the rest of this cycle.
+          yield new Promise(resolve => window.setTimeout(resolve, 0));
+        }
+      }
+    }
+
+    // When the iterating has finished, make sure we reset and save the state
+    // properly.
+    this.stop(true);
+  }),
+
+  /**
+   * Internal; basic wrapper around nsIFind that provides a generator yielding
+   * a range each time an occurence of `word` string is found.
+   *
+   * @param {String}       word   The word to search for
+   * @param {nsIDOMWindow} window The window to search in
+   * @param {Finder}       finder The Finder instance
+   * @yield {nsIDOMRange}
+   */
+  _iterateDocument: function* (word, window, finder) {
+    let doc = window.document;
+    let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
+               doc.body : doc.documentElement;
+
+    if (!body)
+      return;
+
+    let searchRange = doc.createRange();
+    searchRange.selectNodeContents(body);
+
+    let startPt = searchRange.cloneRange();
+    startPt.collapse(true);
+
+    let endPt = searchRange.cloneRange();
+    endPt.collapse(false);
+
+    let retRange = null;
+
+    let nsIFind = Cc["@mozilla.org/embedcomp/rangefind;1"]
+                    .createInstance()
+                    .QueryInterface(Ci.nsIFind);
+    nsIFind.caseSensitive = finder._fastFind.caseSensitive;
+    nsIFind.entireWord = finder._fastFind.entireWord;
+
+    while ((retRange = nsIFind.Find(word, searchRange, startPt, endPt))) {
+      yield retRange;
+      startPt = retRange.cloneRange();
+      startPt.collapse(false);
+    }
+  },
+
+  /**
+   * Internal; helper method for the iterator that recursively collects all
+   * visible (i)frames inside a window.
+   *
+   * @param  {nsIDOMWindow} window The window to extract the (i)frames from
+   * @param  {Finder}       finder The Finder instance
+   * @return {Array}        Stack of frames to iterate over
+   */
+  _collectFrames(window, finder) {
+    let frames = [];
+    if (!window.frames || !window.frames.length)
+      return frames;
+
+    // Casting `window.frames` to an Iterator doesn't work, so we're stuck with
+    // a plain, old for-loop.
+    for (let i = 0, l = window.frames.length; i < l; ++i) {
+      let frame = window.frames[i];
+      // Don't count matches in hidden frames.
+      let frameEl = frame && frame.frameElement;
+      if (!frameEl)
+        continue;
+      // Construct a range around the frame element to check its visiblity.
+      let range = window.document.createRange();
+      range.setStart(frameEl, 0);
+      range.setEnd(frameEl, 0);
+      if (!finder._fastFind.isRangeVisible(range, this._getDocShell(range), true))
+        continue;
+      // All conditions pass, so push the current frame and its children on the
+      // stack.
+      frames.push(frame, ...this._collectFrames(frame, finder));
+    }
+
+    return frames;
+  },
+
+  /**
+   * Internal; helper method to extract the docShell reference from a Window or
+   * Range object.
+   *
+   * @param  {nsIDOMRange} windowOrRange Window object to query. May also be a
+   *                                     Range, from which the owner window will
+   *                                     be queried.
+   * @return {nsIDocShell}
+   */
+  _getDocShell(windowOrRange) {
+    let window = windowOrRange;
+    // Ranges may also be passed in, so fetch its window.
+    if (windowOrRange instanceof Ci.nsIDOMRange)
+      window = windowOrRange.startContainer.ownerDocument.defaultView;
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShell);
+  },
+
+  /**
+   * Internal; determines whether a range is inside a link.
+   *
+   * @param  {nsIDOMRange} range the range to check
+   * @return {Boolean}     True if the range starts in a link
+   */
+  _rangeStartsInLink(range) {
+    let isInsideLink = false;
+    let node = range.startContainer;
+
+    if (node.nodeType == node.ELEMENT_NODE) {
+      if (node.hasChildNodes) {
+        let childNode = node.item(range.startOffset);
+        if (childNode)
+          node = childNode;
+      }
+    }
+
+    const XLink_NS = "http://www.w3.org/1999/xlink";
+    const HTMLAnchorElement = (node.ownerDocument || node).defaultView.HTMLAnchorElement;
+    do {
+      if (node instanceof HTMLAnchorElement) {
+        isInsideLink = node.hasAttribute("href");
+        break;
+      } else if (typeof node.hasAttributeNS == "function" &&
+                 node.hasAttributeNS(XLink_NS, "href")) {
+        isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
+        break;
+      }
+
+      node = node.parentNode;
+    } while (node);
+
+    return isInsideLink;
+  }
+};
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -203,47 +203,35 @@ GMPInstallManager.prototype = {
     try {
       let gmpAddons = yield this.checkForAddons();
       this._updateLastCheck();
       log.info("Found " + gmpAddons.length + " addons advertised.");
       let addonsToInstall = gmpAddons.filter(function(gmpAddon) {
         log.info("Found addon: " + gmpAddon.toString());
 
         if (!gmpAddon.isValid) {
-          GMPUtils.maybeReportTelemetry(gmpAddon.id,
-                                        "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON",
-                                        GMPInstallFailureReason.GMP_INVALID);
           log.info("Addon |" + gmpAddon.id + "| is invalid.");
           return false;
         }
 
         if (GMPUtils.isPluginHidden(gmpAddon)) {
-          GMPUtils.maybeReportTelemetry(gmpAddon.id,
-                                        "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON",
-                                        GMPInstallFailureReason.GMP_HIDDEN);
           log.info("Addon |" + gmpAddon.id + "| has been hidden.");
           return false;
         }
 
         if (gmpAddon.isInstalled) {
           log.info("Addon |" + gmpAddon.id + "| already installed.");
           return false;
         }
 
         let addonUpdateEnabled = false;
         if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) {
           if (!this._isAddonEnabled(gmpAddon.id)) {
-            GMPUtils.maybeReportTelemetry(gmpAddon.id,
-                                          "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON",
-                                          GMPInstallFailureReason.GMP_DISABLED);
             log.info("GMP |" + gmpAddon.id + "| has been disabled; skipping check.");
           } else if (!this._isAddonUpdateEnabled(gmpAddon.id)) {
-            GMPUtils.maybeReportTelemetry(gmpAddon.id,
-                                          "VIDEO_EME_ADOBE_INSTALL_FAILED_REASON",
-                                          GMPInstallFailureReason.GMP_UPDATE_DISABLED);
             log.info("Auto-update is off for " + gmpAddon.id +
                      ", skipping check.");
           } else {
             addonUpdateEnabled = true;
           }
         } else {
           // Currently, we only support installs of OpenH264 and EME plugins.
           log.info("Auto-update is off for unknown plugin '" + gmpAddon.id +
--- a/toolkit/modules/GMPUtils.jsm
+++ b/toolkit/modules/GMPUtils.jsm
@@ -43,26 +43,20 @@ this.GMPUtils = {
    */
   isPluginHidden: function(aPlugin) {
     if (!aPlugin.isEME) {
       return false;
     }
 
     if (!this._isPluginSupported(aPlugin) ||
         !this._isPluginVisible(aPlugin)) {
-      this.maybeReportTelemetry(aPlugin.id,
-                                "VIDEO_EME_ADOBE_HIDDEN_REASON",
-                                GMPPluginHiddenReason.UNSUPPORTED);
       return true;
     }
 
     if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
-      this.maybeReportTelemetry(aPlugin.id,
-                                "VIDEO_EME_ADOBE_HIDDEN_REASON",
-                                GMPPluginHiddenReason.EME_DISABLED);
       return true;
     }
 
     return false;
   },
 
   /**
    * Checks whether or not a given plugin is supported by the current OS.
@@ -70,20 +64,16 @@ this.GMPUtils = {
    *          The plugin to check.
    */
   _isPluginSupported: function(aPlugin) {
     if (this._isPluginForceSupported(aPlugin)) {
       return true;
     }
     if (aPlugin.id == EME_ADOBE_ID) {
       if (Services.appinfo.OS != "WINNT") {
-        // Non-Windows OSes currently unsupported by Adobe EME
-        this.maybeReportTelemetry(aPlugin.id,
-                                  "VIDEO_EME_ADOBE_UNSUPPORTED_REASON",
-                                  GMPPluginUnsupportedReason.NOT_WINDOWS);
       }
       // Windows Vista and later only supported by Adobe EME.
       return AppConstants.isPlatformAndVersionAtLeast("win", "6");
     } else if (aPlugin.id == WIDEVINE_ID) {
       // The Widevine plugin is available for Windows versions Vista and later,
       // Mac OSX, and Linux.
       return AppConstants.isPlatformAndVersionAtLeast("win", "6") ||
              AppConstants.platform == "macosx" ||
@@ -109,41 +99,16 @@ this.GMPUtils = {
    * in automated tests to override the checks that prevent GMPs running on an
    * unsupported platform.
    * @param   aPlugin
    *          The plugin to check.
    */
   _isPluginForceSupported: function(aPlugin) {
     return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, false, aPlugin.id);
   },
-
-  /**
-   * Report telemetry value, but only for Adobe CDM and only once per key
-   * per session.
-   */
-  maybeReportTelemetry: function(aPluginId, key, value) {
-    if (aPluginId != EME_ADOBE_ID) {
-      // Only report for Adobe CDM.
-      return;
-    }
-
-    if (!this.reportedKeys) {
-      this.reportedKeys = [];
-    }
-    if (this.reportedKeys.indexOf(key) >= 0) {
-      // Only report each key once per session.
-      return;
-    }
-    this.reportedKeys.push(key);
-
-    let hist = Services.telemetry.getHistogramById(key);
-    if (hist) {
-      hist.add(value);
-    }
-  },
 };
 
 /**
  * Manages preferences for GMP addons
  */
 this.GMPPrefs = {
   KEY_EME_ENABLED:              "media.eme.enabled",
   KEY_PLUGIN_ENABLED:           "media.{0}.enabled",
--- a/toolkit/modules/RemoteFinder.jsm
+++ b/toolkit/modules/RemoteFinder.jsm
@@ -145,19 +145,20 @@ RemoteFinder.prototype = {
 
   findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) {
     this._browser.messageManager.sendAsyncMessage("Finder:FindAgain",
                                                   { findBackwards: aFindBackwards,
                                                     linksOnly: aLinksOnly,
                                                     drawOutline: aDrawOutline });
   },
 
-  highlight: function (aHighlight, aWord) {
+  highlight: function (aHighlight, aWord, aLinksOnly) {
     this._browser.messageManager.sendAsyncMessage("Finder:Highlight",
                                                   { highlight: aHighlight,
+                                                    linksOnly: aLinksOnly,
                                                     word: aWord });
   },
 
   enableSelection: function () {
     this._browser.messageManager.sendAsyncMessage("Finder:EnableSelection");
   },
 
   removeSelection: function () {
@@ -287,17 +288,17 @@ RemoteFinderListener.prototype = {
         this._finder.fastFind(data.searchString, data.linksOnly, data.drawOutline);
         break;
 
       case "Finder:FindAgain":
         this._finder.findAgain(data.findBackwards, data.linksOnly, data.drawOutline);
         break;
 
       case "Finder:Highlight":
-        this._finder.highlight(data.highlight, data.word);
+        this._finder.highlight(data.highlight, data.word, data.linksOnly);
         break;
 
       case "Finder:EnableSelection":
         this._finder.enableSelection();
         break;
 
       case "Finder:RemoveSelection":
         this._finder.removeSelection();
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -35,16 +35,17 @@ EXTRA_JS_MODULES += [
     'Color.jsm',
     'Console.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'FinderHighlighter.jsm',
+    'FinderIterator.jsm',
     'Geometry.jsm',
     'GMPInstallManager.jsm',
     'GMPUtils.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
     'InlineSpellCheckerContent.jsm',
     'Integration.jsm',
     'LoadContextInfo.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_FinderIterator.js
@@ -0,0 +1,184 @@
+const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+const { FinderIterator } = Cu.import("resource://gre/modules/FinderIterator.jsm", {});
+Cu.import("resource://gre/modules/Promise.jsm");
+
+var gFindResults = [];
+// Stub the method that instantiates nsIFind and does all the interaction with
+// the docShell to be searched through.
+FinderIterator._iterateDocument = function* (word, window, finder) {
+  for (let range of gFindResults)
+    yield range;
+};
+
+FinderIterator._rangeStartsInLink = fakeRange => fakeRange.startsInLink;
+
+function FakeRange(textContent, startsInLink = false) {
+  this.startContainer = {};
+  this.startsInLink = startsInLink;
+  this.toString = () => textContent;
+}
+
+var gMockWindow = {
+  setTimeout(cb, delay) {
+    Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
+      .initWithCallback(cb, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+  }
+};
+
+var gMockFinder = {
+  _getWindow() { return gMockWindow; }
+};
+
+function prepareIterator(findText, rangeCount) {
+  gFindResults = [];
+  for (let i = rangeCount; --i >= 0;)
+    gFindResults.push(new FakeRange(findText));
+}
+
+add_task(function* test_start() {
+  let findText = "test";
+  let rangeCount = 300;
+  prepareIterator(findText, rangeCount);
+
+  let count = 0;
+  yield FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => {
+      ++count;
+      Assert.equal(range.toString(), findText, "Text content should match");
+    },
+    word: findText
+  });
+
+  Assert.equal(rangeCount, count, "Amount of ranges yielded should match!");
+  Assert.ok(!FinderIterator.running, "Running state should match");
+  Assert.equal(FinderIterator._previousRanges.length, rangeCount, "Ranges cache should match");
+});
+
+add_task(function* test_valid_arguments() {
+  let findText = "foo";
+  let rangeCount = 20;
+  prepareIterator(findText, rangeCount);
+
+  let count = 0;
+
+  yield FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count,
+    word: findText
+  });
+
+  let params = FinderIterator._previousParams;
+  Assert.ok(!params.linksOnly, "Default for linksOnly is false");
+  Assert.ok(!params.useCache, "Default for useCache is false");
+  Assert.equal(params.word, findText, "Words should match");
+
+  count = 0;
+  Assert.throws(() => FinderIterator.start({
+    onRange: range => ++count,
+    word: findText
+  }), /Missing required option 'finder'/, "Should throw when missing an argument");
+  FinderIterator.reset();
+
+  Assert.throws(() => FinderIterator.start({
+    finder: gMockFinder,
+    word: findText
+  }), /Missing valid, required option 'onRange'/, "Should throw when missing an argument");
+  FinderIterator.reset();
+
+  Assert.throws(() => FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count
+  }), /Missing required option 'word'/, "Should throw when missing an argument");
+  FinderIterator.reset();
+
+  Assert.equal(count, 0, "No ranges should've been counted");
+});
+
+add_task(function* test_stop() {
+  let findText = "bar";
+  let rangeCount = 120;
+  prepareIterator(findText, rangeCount);
+
+  let count = 0;
+  let whenDone = FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count,
+    word: findText
+  });
+
+  FinderIterator.stop();
+
+  yield whenDone;
+
+  Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
+});
+
+add_task(function* test_reset() {
+  let findText = "tik";
+  let rangeCount = 142;
+  prepareIterator(findText, rangeCount);
+
+  let count = 0;
+  let whenDone = FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count,
+    word: findText
+  });
+
+  Assert.ok(FinderIterator.running, "Yup, running we are");
+  Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
+  Assert.equal(FinderIterator.ranges.length, 100,
+    "Number of ranges should match `kIterationSizeMax`");
+
+  FinderIterator.reset();
+
+  Assert.ok(!FinderIterator.running, "Nope, running we are not");
+  Assert.equal(FinderIterator.ranges.length, 0, "No ranges after reset");
+  Assert.equal(FinderIterator._previousRanges.length, 0, "No ranges after reset");
+
+  yield whenDone;
+
+  Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
+});
+
+add_task(function* test_parallel_starts() {
+  let findText = "tak";
+  let rangeCount = 2143;
+  prepareIterator(findText, rangeCount);
+
+  // Start off the iterator.
+  let count = 0;
+  let whenDone = FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count,
+    word: findText
+  });
+
+  // Start again after a few milliseconds.
+  yield new Promise(resolve => gMockWindow.setTimeout(resolve, 2));
+  Assert.ok(FinderIterator.running, "We ought to be running here");
+
+  let count2 = 0;
+  let whenDone2 = FinderIterator.start({
+    finder: gMockFinder,
+    onRange: range => ++count2,
+    word: findText
+  });
+
+  // Let the iterator run for a little while longer before we assert the world.
+  yield new Promise(resolve => gMockWindow.setTimeout(resolve, 10));
+  FinderIterator.stop();
+
+  Assert.ok(!FinderIterator.running, "Stop means stop");
+
+  yield whenDone;
+  yield whenDone2;
+
+  Assert.greater(count, FinderIterator.kIterationSizeMax, "At least one range should've been found");
+  Assert.less(count, rangeCount, "Not all ranges should've been found");
+  Assert.greater(count2, FinderIterator.kIterationSizeMax, "At least one range should've been found");
+  Assert.less(count2, rangeCount, "Not all ranges should've been found");
+
+  Assert.less(count2, count, "The second start was later, so should have fewer results");
+});
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -14,16 +14,17 @@ skip-if = toolkit == 'android'
 [test_CanonicalJSON.js]
 [test_client_id.js]
 skip-if = toolkit == 'android'
 [test_Color.js]
 [test_DeferredTask.js]
 skip-if = toolkit == 'android'
 [test_FileUtils.js]
 skip-if = toolkit == 'android'
+[test_FinderIterator.js]
 [test_GMPInstallManager.js]
 skip-if = toolkit == 'android'
 [test_Http.js]
 skip-if = toolkit == 'android'
 [test_Integration.js]
 [test_jsesc.js]
 skip-if = toolkit == 'android'
 [test_Log.js]
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -50,34 +50,30 @@ const GMP_PLUGINS = [
     name:            "openH264_name",
     description:     "openH264_description2",
     // The following licenseURL is part of an awful hack to include the OpenH264
     // license without having bug 624602 fixed yet, and intentionally ignores
     // localisation.
     licenseURL:      "chrome://mozapps/content/extensions/OpenH264-license.txt",
     homepageURL:     "http://www.openh264.org/",
     optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
-    missingKey:      "VIDEO_OPENH264_GMP_DISAPPEARED",
-    missingFilesKey: "VIDEO_OPENH264_GMP_MISSING_FILES",
   },
   {
     id:              EME_ADOBE_ID,
     name:            "eme-adobe_name",
     description:     "eme-adobe_description",
     // The following learnMoreURL is another hack to be able to support a SUMO page for this
     // feature.
     get learnMoreURL() {
       return Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
     },
     licenseURL:      "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM_EULA/index.html",
     homepageURL:     "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM",
     optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
     isEME:           true,
-    missingKey:      "VIDEO_ADOBE_GMP_DISAPPEARED",
-    missingFilesKey: "VIDEO_ADOBE_GMP_MISSING_FILES",
   },
   {
     id:              WIDEVINE_ID,
     name:            "widevine_description",
     // Describe the purpose of both CDMs in the same way.
     description:     "eme-adobe_description",
     licenseURL:      "https://www.google.com/policies/privacy/",
     homepageURL:     "https://www.widevine.com/",
@@ -86,18 +82,16 @@ const GMP_PLUGINS = [
   }];
 XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS);
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 XPCOMUtils.defineLazyGetter(this, "gmpService",
   () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
 
-XPCOMUtils.defineLazyGetter(this, "telemetryService", () => Services.telemetry);
-
 var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                        .getService(Ci.nsIMessageListenerManager);
 
 var gLogger;
 var gLogAppenderDump = null;
 
 function configureLogging() {
   if (!gLogger) {
@@ -158,23 +152,16 @@ GMPWrapper.prototype = {
       this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
                                    this._plugin.id,
                                    GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
                                                 null, this._plugin.id));
     }
     return this._gmpPath;
   },
 
-  get missingKey() {
-    return this._plugin.missingKey;
-  },
-  get missingFilesKey() {
-    return this._plugin.missingFilesKey;
-  },
-
   get id() { return this._plugin.id; },
   get type() { return "plugin"; },
   get isGMPlugin() { return true; },
   get name() { return this._plugin.name; },
   get creator() { return null; },
   get homepageURL() { return this._plugin.homepageURL; },
 
   get description() { return this._plugin.description; },
@@ -490,66 +477,47 @@ GMPWrapper.prototype = {
     let libName = AppConstants.DLL_PREFIX + id + AppConstants.DLL_SUFFIX;
     let infoName;
     if (this._plugin.id == WIDEVINE_ID) {
       infoName = "manifest.json";
     } else {
       infoName = id + ".info";
     }
 
-    return {
-      libraryMissing: !fileExists(this.gmpPath, libName),
-      infoMissing: !fileExists(this.gmpPath, infoName),
-      voucherMissing: this._plugin.id == EME_ADOBE_ID
-                      && !fileExists(this.gmpPath, id + ".voucher"),
-    };
+    return fileExists(this.gmpPath, libName) &&
+           fileExists(this.gmpPath, infoName) &&
+           (this._plugin.id != EME_ADOBE_ID || fileExists(this.gmpPath, id + ".voucher"));
   },
 
   validate: function() {
     if (!this.isInstalled) {
       // Not installed -> Valid.
-      return { installed: false, valid: true };
+      return {
+        installed: false,
+        valid: true
+      };
     }
 
     let abi = GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, this._plugin.id);
     if (abi != UpdateUtils.ABI) {
       // ABI doesn't match. Possibly this is a profile migrated across platforms
       // or from 32 -> 64 bit.
       return {
         installed: true,
         mismatchedABI: true,
         valid: false
       };
     }
 
     // Installed -> Check if files are missing.
-    let status = this._arePluginFilesOnDisk();
-    status.installed = true;
-    status.mismatchedABI = false;
-    status.valid = true;
-    status.missing = [];
-    status.telemetry = 0;
-
-    if (status.libraryMissing) {
-      status.valid = false;
-      status.missing.push('library');
-      status.telemetry += 1;
-    }
-    if (status.infoMissing) {
-      status.valid = false;
-      status.missing.push('info');
-      status.telemetry += 2;
-    }
-    if (status.voucherMissing) {
-      status.valid = false;
-      status.missing.push('voucher');
-      status.telemetry += 4;
-    }
-
-    return status;
+    let filesOnDisk = this._arePluginFilesOnDisk();
+    return {
+      installed: true,
+      valid: filesOnDisk
+    };
   },
 };
 
 var GMPProvider = {
   get name() { return "GMPProvider"; },
 
   _plugins: null,
 
@@ -572,25 +540,19 @@ var GMPProvider = {
       if (gmpPath && isEnabled) {
         let validation = wrapper.validate();
         if (validation.mismatchedABI) {
           this._log.info("startup - gmp " + plugin.id +
                          " mismatched ABI, uninstalling");
           wrapper.uninstallPlugin();
           continue;
         }
-        if (validation.installed && wrapper.missingFilesKey) {
-          telemetryService.getHistogramById(wrapper.missingFilesKey).add(validation.telemetry);
-        }
         if (!validation.valid) {
           this._log.info("startup - gmp " + plugin.id +
-                         " missing [" + validation.missing + "], uninstalling");
-          if (wrapper.missingKey) {
-            telemetryService.getHistogramById(wrapper.missingKey).add(true);
-          }
+                         " invalid, uninstalling");
           wrapper.uninstallPlugin();
           continue;
         }
         this._log.info("startup - adding gmp directory " + gmpPath);
         try {
           gmpService.addPluginDirectory(gmpPath);
         } catch (e) {
           if (e.name != 'NS_ERROR_NOT_AVAILABLE')
@@ -692,18 +654,16 @@ var GMPProvider = {
       let plugin = {
         id: aPlugin.id,
         name: pluginsBundle.GetStringFromName(aPlugin.name),
         description: pluginsBundle.GetStringFromName(aPlugin.description),
         homepageURL: aPlugin.homepageURL,
         optionsURL: aPlugin.optionsURL,
         wrapper: null,
         isEME: aPlugin.isEME,
-        missingKey: aPlugin.missingKey,
-        missingFilesKey: aPlugin.missingFilesKey,
       };
       plugin.fullDescription = this.generateFullDescription(aPlugin);
       plugin.wrapper = new GMPWrapper(plugin);
       this._plugins.set(plugin.id, plugin);
     }
   },
 
   ensureProperCDMInstallState: function() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -281,71 +281,40 @@ add_task(function* test_pluginRegistrati
         }
       },
       removeAndDeletePluginDirectory: path => {
         if (!removedPaths.includes(path)) {
           removedPaths.push(path);
         }
       },
     };
-
-    let reportedKeys = {};
+    GMPScope.gmpService = MockGMPService;
 
-    let MockTelemetry = {
-      getHistogramById: key => {
-        return {
-          add: value => {
-            reportedKeys[key] = value;
-          }
-        }
-      }
-    };
-
-    GMPScope.gmpService = MockGMPService;
-    GMPScope.telemetryService = MockTelemetry;
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true);
 
     // Test that plugin registration fails if the plugin dynamic library and
     // info files are not present.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                        TEST_VERSION);
     clearPaths();
     yield promiseRestartManager();
     Assert.equal(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, [file.path]);
 
-    // Test that the GMPProvider tried to report via telemetry that the
-    // addon's lib files are missing.
-    if (addon.missingKey) {
-      Assert.strictEqual(reportedKeys[addon.missingKey], true);
-    }
-    if (addon.missingFilesKey) {
-      Assert.strictEqual(reportedKeys[addon.missingFilesKey],
-                         addon.missingFilesKey != "VIDEO_ADOBE_GMP_MISSING_FILES"
-                         ? (1+2) : (1+2+4));
-    }
-    reportedKeys = {};
-
     // Create dummy GMP library/info files, and test that plugin registration
     // succeeds during startup, now that we've added GMP info/lib files.
     createMockPluginFilesIfNeeded(file, addon.id);
 
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                        TEST_VERSION);
     clearPaths();
     yield promiseRestartManager();
     Assert.notEqual(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, []);
 
-    // Test that the GMPProvider tried to report via telemetry that the
-    // addon's lib files are NOT missing.
-    if (addon.missingFilesKey) {
-      Assert.strictEqual(reportedKeys[addon.missingFilesKey], 0);
-    }
-
     // Setting the ABI to something invalid should cause plugin to be removed at startup.
     clearPaths();
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ABI, addon.id), "invalid-ABI");
     yield promiseRestartManager();
     Assert.equal(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Setting the ABI to expected ABI should cause registration at startup.
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -24,17 +24,17 @@ struct LookAndFeelInt
 
 namespace mozilla {
 
 class LookAndFeel
 {
 public:
   // When modifying this list, also modify nsXPLookAndFeel::sColorPrefs
   // in widget/xpwidgts/nsXPLookAndFeel.cpp.
-  enum ColorID {
+  enum ColorID : uint8_t {
 
     // WARNING : NO NEGATIVE VALUE IN THIS ENUMERATION
     // see patch in bug 57757 for more information
 
     eColorID_WindowBackground,
     eColorID_WindowForeground,
     eColorID_WidgetBackground,
     eColorID_WidgetForeground,
--- a/widget/gtk/WidgetStyleCache.cpp
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -442,16 +442,18 @@ GetCssNodeStyleInternal(WidgetNodeType a
     case MOZ_GTK_SCROLLED_WINDOW:
       // TODO - create from CSS node
       return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
                                      GTK_STYLE_CLASS_FRAME);
     case MOZ_GTK_TEXT_VIEW:
       // TODO - create from CSS node
       return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
                                      GTK_STYLE_CLASS_VIEW);
+    case MOZ_GTK_FRAME_BORDER:
+      return CreateChildCSSNode("border", MOZ_GTK_FRAME);
     default:
       // TODO - create style from style path
       GtkWidget* widget = GetWidget(aNodeType);
       return gtk_widget_get_style_context(widget);
   }
 
   MOZ_ASSERT(style, "missing style context for node type");
   sStyleStorage[aNodeType] = style;
@@ -508,16 +510,18 @@ GetWidgetStyleInternal(WidgetNodeType aN
       return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON,
                                      GTK_STYLE_CLASS_ENTRY);
     case MOZ_GTK_SCROLLED_WINDOW:
       return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
                                      GTK_STYLE_CLASS_FRAME);
     case MOZ_GTK_TEXT_VIEW:
       return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
                                      GTK_STYLE_CLASS_VIEW);
+    case MOZ_GTK_FRAME_BORDER:
+      return GetWidgetStyleInternal(MOZ_GTK_FRAME);
     default:
       GtkWidget* widget = GetWidget(aNodeType);
       MOZ_ASSERT(widget);
       return gtk_widget_get_style_context(widget);
   }
 }
 
 void
--- a/widget/gtk/gtkdrawing.h
+++ b/widget/gtk/gtkdrawing.h
@@ -154,16 +154,18 @@ typedef enum {
   /* Paints the background of a GtkHandleBox. */
   MOZ_GTK_TOOLBAR,
   /* Paints a toolbar separator */
   MOZ_GTK_TOOLBAR_SEPARATOR,
   /* Paints a GtkToolTip */
   MOZ_GTK_TOOLTIP,
   /* Paints a GtkFrame (e.g. a status bar panel). */
   MOZ_GTK_FRAME,
+  /* Paints the border of a GtkFrame */
+  MOZ_GTK_FRAME_BORDER,
   /* Paints a resize grip for a GtkWindow */
   MOZ_GTK_RESIZER,
   /* Paints a GtkProgressBar. */
   MOZ_GTK_PROGRESSBAR,
   /* Paints a trough (track) of a GtkProgressBar */
   MOZ_GTK_PROGRESS_TROUGH,
   /* Paints a progress chunk of a GtkProgressBar. */
   MOZ_GTK_PROGRESS_CHUNK,
--- a/widget/gtk/nsLookAndFeel.cpp
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -1344,22 +1344,21 @@ nsLookAndFeel::Init()
 
     // Get odd row background color
     gtk_style_context_save(style);
     gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
     gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
     sOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
     gtk_style_context_restore(style);
 
-    GtkWidget *frame = gtk_frame_new(nullptr);
-    gtk_container_add(GTK_CONTAINER(parent), frame);
-    style = gtk_widget_get_style_context(frame);
+    gtk_widget_path_free(path);
+
+    style = ClaimStyleContext(MOZ_GTK_FRAME_BORDER);
     GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder);
-
-    gtk_widget_path_free(path);
+    ReleaseStyleContext(style);
 
     // GtkInfoBar
     // TODO - Use WidgetCache for it?
     GtkWidget* infoBar = gtk_info_bar_new();
     GtkWidget* infoBarContent = gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
     GtkWidget* infoBarLabel = gtk_label_new(nullptr);
     gtk_container_add(GTK_CONTAINER(parent), infoBar);
     gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -1190,31 +1190,30 @@ public:
     , mAPZC(aAPZC)
     , mWidget(aWidget)
     , mInputBlockId(0)
   {
   }
 
   NS_IMETHOD Run() override
   {
-    mAPZResult = mAPZC->ReceiveInputEvent(mWheelInput, &mGuid, &mInputBlockId);
-    if (mAPZResult == nsEventStatus_eConsumeNoDefault) {
+    nsEventStatus result = mAPZC->ReceiveInputEvent(mWheelInput, &mGuid, &mInputBlockId);
+    if (result == nsEventStatus_eConsumeNoDefault) {
       return NS_OK;
     }
-    RefPtr<Runnable> r = new DispatchWheelEventOnMainThread(mWheelInput, mWidget, mAPZResult, mInputBlockId, mGuid);
+    RefPtr<Runnable> r = new DispatchWheelEventOnMainThread(mWheelInput, mWidget, result, mInputBlockId, mGuid);
     mMainMessageLoop->PostTask(r.forget());
     return NS_OK;
   }
 
 private:
   MessageLoop* mMainMessageLoop;
   ScrollWheelInput mWheelInput;
   RefPtr<IAPZCTreeManager> mAPZC;
   nsBaseWidget* mWidget;
-  nsEventStatus mAPZResult;
   uint64_t mInputBlockId;
   ScrollableLayerGuid mGuid;
 };
 
 nsEventStatus
 nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1228,17 +1228,17 @@ GfxInfo::GetFeatureStatusImpl(int32_t aF
         // FIXME - these special hex values are currently used in xpcshell tests introduced by
         // bug 625160 patch 8/8. Maybe these tests need to be adjusted now that we're only whitelisting
         // intel/ati/nvidia.
         !adapterVendorID.LowerCaseEqualsLiteral("0xabcd") &&
         !adapterVendorID.LowerCaseEqualsLiteral("0xdcba") &&
         !adapterVendorID.LowerCaseEqualsLiteral("0xabab") &&
         !adapterVendorID.LowerCaseEqualsLiteral("0xdcdc"))
     {
-      aFailureId = "FEATURE_FAILURE_TEST";
+      aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR";
       *aStatus = FEATURE_BLOCKED_DEVICE;
       return NS_OK;
     }
 
     uint64_t driverVersion;
     if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) {
       aFailureId = "FEATURE_FAILURE_PARSE_DRIVER";
       *aStatus = FEATURE_BLOCKED_DRIVER_VERSION;