merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 28 Jul 2016 17:41:19 +0200
changeset 331953 afb47dfb71ed76d1bf86fe0101cda1a5e6038863
parent 331906 db3ed1fdbbeaf5ab1e8fe454780146e7499be3db (current diff)
parent 331952 0aa0956567fc0178126434c013070332eb3a6fd7 (diff)
child 332102 9ec789c0ee5bd3a5e765513c21027fdad953b022
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
merge autoland to mozilla-central a=merge
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/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
@@ -1041,18 +1041,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 },
@@ -2296,23 +2296,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
@@ -2408,16 +2408,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;