merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 07 Feb 2017 14:08:46 +0100
changeset 479917 6d27535f4fe912068e0a0ac5854f7f39e94964a5
parent 479916 b57c0a563ecd514d6ec5acc255a9435b6128650b (current diff)
parent 479774 9503e89340d97514cf455bc1c0df6bbaf00469b6 (diff)
child 479918 93557e3051c351b2ce8e7e5ab5d78c17d6153efd
child 479939 9dbd2d9b334e79c24595f17d22fe3787ce3b28f4
child 479957 b2f233e43331e3f773fe79ef1bbab83783cb1a26
child 479982 35da270179fc16dac372cf6c741c2a9887f9a2f5
child 480053 0edc732e50ea3482d0cba0fd150ac4d0a07aaa82
child 480073 39a45dfe0be9abb8887a3cc555b8b2e3594c9d0e
child 480163 2402c8c6d28b2e06c893c283b02fa5aa2ceffe61
child 480614 f3763bdc70691e61e38cc39fd07e17253f8fa1c4
child 480627 e829fc87c73f9ab298d62b3b6427015346b0105b
child 480670 f35a21d38a231b2788435268d2b7f94fd4057222
child 481471 44c715e9d0e510f4ad685f4376ba11c65927788c
child 481473 4a81d2219e41ffe6d1f192e5347d278aeaf0d231
child 481477 e2c3c52400a365634494ee79bdef49da1cd31177
push id44393
push userVYV03354@nifty.ne.jp
push dateTue, 07 Feb 2017 13:53:48 +0000
reviewersmerge
milestone54.0a1
merge mozilla-inbound to mozilla-central a=merge
browser/components/extensions/ext-tabs.js
dom/ipc/ContentChild.cpp
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -456,21 +456,25 @@ void
 DocAccessibleParent::Destroy()
 {
   // If we are already shutdown that is because our containing tab parent is
   // shutting down in which case we don't need to do anything.
   if (mShutdown) {
     return;
   }
 
-  NS_ASSERTION(mChildDocs.IsEmpty(),
-               "why weren't the child docs destroyed already?");
   mShutdown = true;
 
   uint32_t childDocCount = mChildDocs.Length();
+  for (uint32_t i = 0; i < childDocCount; i++) {
+    for (uint32_t j = i + 1; j < childDocCount; j++) {
+      MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]);
+    }
+  }
+
   for (uint32_t i = childDocCount - 1; i < childDocCount; i--)
     mChildDocs[i]->Destroy();
 
   for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
     MOZ_ASSERT(iter.Get()->mProxy != this);
     ProxyDestroyed(iter.Get()->mProxy);
     iter.Remove();
   }
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -216,17 +216,17 @@ BrowserAction.prototype = {
         break;
 
       case "mouseup":
         if (event.button == 0) {
           this.clearPopupTimeout();
           // If we have a pending pre-loaded popup, cancel it after we've waited
           // long enough that we can be relatively certain it won't be opening.
           if (this.pendingPopup) {
-            let {node} = this.widget.forWindow(window);
+            let node = window.gBrowser && this.widget.forWindow(window).node;
             if (isAncestorOrSelf(node, event.originalTarget)) {
               this.pendingPopupTimeout = setTimeout(() => this.clearPopup(),
                                                     POPUP_PRELOAD_TIMEOUT_MS);
             } else {
               this.clearPopup();
             }
           }
         }
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -53,19 +53,19 @@ extensions.on("page-shutdown", (type, co
     if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
       // Only close extension tabs.
       // This check prevents about:addons from closing when it contains a
       // WebExtension as an embedded inline options page.
       return;
     }
     let {gBrowser} = context.xulBrowser.ownerGlobal;
     if (gBrowser) {
-      let tab = gBrowser.getTabForBrowser(context.xulBrowser);
-      if (tab) {
-        gBrowser.removeTab(tab);
+      let nativeTab = gBrowser.getTabForBrowser(context.xulBrowser);
+      if (nativeTab) {
+        gBrowser.removeTab(nativeTab);
       }
     }
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 let tabListener = {
   tabReadyInitialized: false,
@@ -78,49 +78,50 @@ let tabListener = {
 
       this.tabReadyInitialized = true;
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
       let {gBrowser} = browser.ownerGlobal;
-      let tab = gBrowser.getTabForBrowser(browser);
+      let nativeTab = gBrowser.getTabForBrowser(browser);
 
       // Now we are certain that the first page in the tab was loaded.
-      this.initializingTabs.delete(tab);
+      this.initializingTabs.delete(nativeTab);
 
       // browser.innerWindowID is now set, resolve the promises if any.
-      let deferred = this.tabReadyPromises.get(tab);
+      let deferred = this.tabReadyPromises.get(nativeTab);
       if (deferred) {
-        deferred.resolve(tab);
-        this.tabReadyPromises.delete(tab);
+        deferred.resolve(nativeTab);
+        this.tabReadyPromises.delete(nativeTab);
       }
     }
   },
 
   /**
    * Returns a promise that resolves when the tab is ready.
    * Tabs created via the `tabs.create` method are "ready" once the location
    * changes to the requested URL. Other tabs are assumed to be ready once their
    * inner window ID is known.
    *
-   * @param {XULElement} tab The <tab> element.
+   * @param {XULElement} nativeTab The <tab> element.
    * @returns {Promise} Resolves with the given tab once ready.
    */
-  awaitTabReady(tab) {
-    let deferred = this.tabReadyPromises.get(tab);
+  awaitTabReady(nativeTab) {
+    let deferred = this.tabReadyPromises.get(nativeTab);
     if (!deferred) {
       deferred = PromiseUtils.defer();
-      if (!this.initializingTabs.has(tab) && (tab.linkedBrowser.innerWindowID ||
-                                              tab.linkedBrowser.currentURI.spec === "about:blank")) {
-        deferred.resolve(tab);
+      if (!this.initializingTabs.has(nativeTab) &&
+          (nativeTab.linkedBrowser.innerWindowID ||
+           nativeTab.linkedBrowser.currentURI.spec === "about:blank")) {
+        deferred.resolve(nativeTab);
       } else {
         this.initTabReady();
-        this.tabReadyPromises.set(tab, deferred);
+        this.tabReadyPromises.set(nativeTab, deferred);
       }
     }
     return deferred.promise;
   },
 };
 
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
@@ -137,51 +138,51 @@ extensions.registerSchemaAPI("tabs", "ad
   async function promiseTabWhenReady(tabId) {
     let tab;
     if (tabId !== null) {
       tab = tabManager.get(tabId);
     } else {
       tab = tabManager.getWrapper(tabTracker.activeTab);
     }
 
-    await tabListener.awaitTabReady(tab.tab);
+    await tabListener.awaitTabReady(tab.nativeTab);
 
     return tab;
   }
 
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
-        let tab = event.originalTarget;
-        let tabId = tabTracker.getId(tab);
-        let windowId = windowTracker.getId(tab.ownerGlobal);
+        let nativeTab = event.originalTarget;
+        let tabId = tabTracker.getId(nativeTab);
+        let windowId = windowTracker.getId(nativeTab.ownerGlobal);
         fire.async({tabId, windowId});
       }).api(),
 
       onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
-          fire.async(tabManager.convert(event.tab));
+          fire.async(tabManager.convert(event.nativeTab));
         };
 
         tabTracker.on("tab-created", listener);
         return () => {
           tabTracker.off("tab-created", listener);
         };
       }).api(),
 
       /**
        * Since multiple tabs currently can't be highlighted, onHighlighted
        * essentially acts an alias for self.tabs.onActivated but returns
        * the tabId in an array to match the API.
        * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
       */
       onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
-        let tab = event.originalTarget;
-        let tabIds = [tabTracker.getId(tab)];
-        let windowId = windowTracker.getId(tab.ownerGlobal);
+        let nativeTab = event.originalTarget;
+        let tabIds = [tabTracker.getId(nativeTab)];
+        let windowId = windowTracker.getId(nativeTab.ownerGlobal);
         fire.async({tabIds, windowId});
       }).api(),
 
       onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
           fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
@@ -231,27 +232,27 @@ extensions.registerSchemaAPI("tabs", "ad
           // Remove the tab from the set on the next tick, since it will already
           // have been moved by then.
           Promise.resolve().then(() => {
             ignoreNextMove.delete(event.target);
           });
         };
 
         let moveListener = event => {
-          let tab = event.originalTarget;
+          let nativeTab = event.originalTarget;
 
-          if (ignoreNextMove.has(tab)) {
-            ignoreNextMove.delete(tab);
+          if (ignoreNextMove.has(nativeTab)) {
+            ignoreNextMove.delete(nativeTab);
             return;
           }
 
-          fire.async(tabTracker.getId(tab), {
-            windowId: windowTracker.getId(tab.ownerGlobal),
+          fire.async(tabTracker.getId(nativeTab), {
+            windowId: windowTracker.getId(nativeTab.ownerGlobal),
             fromIndex: event.detail,
-            toIndex: tab._tPos,
+            toIndex: nativeTab._tPos,
           });
         };
 
         windowTracker.addListener("TabMove", moveListener);
         windowTracker.addListener("TabOpen", openListener);
         return () => {
           windowTracker.removeListener("TabMove", moveListener);
           windowTracker.removeListener("TabOpen", openListener);
@@ -395,189 +396,155 @@ extensions.registerSchemaAPI("tabs", "ad
             }
           }
 
           // Make sure things like about:blank and data: URIs never inherit,
           // and instead always get a NullPrincipal.
           options.disallowInheritPrincipal = true;
 
           tabListener.initTabReady();
-          let tab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
+          let nativeTab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
 
           let active = true;
           if (createProperties.active !== null) {
             active = createProperties.active;
           }
           if (active) {
-            window.gBrowser.selectedTab = tab;
+            window.gBrowser.selectedTab = nativeTab;
           }
 
           if (createProperties.index !== null) {
-            window.gBrowser.moveTabTo(tab, createProperties.index);
+            window.gBrowser.moveTabTo(nativeTab, createProperties.index);
           }
 
           if (createProperties.pinned) {
-            window.gBrowser.pinTab(tab);
+            window.gBrowser.pinTab(nativeTab);
           }
 
           if (active && !url) {
             window.focusAndSelectUrlBar();
           }
 
           if (createProperties.url && createProperties.url !== window.BROWSER_NEW_TAB_URL) {
             // We can't wait for a location change event for about:newtab,
             // since it may be pre-rendered, in which case its initial
             // location change event has already fired.
 
             // Mark the tab as initializing, so that operations like
             // `executeScript` wait until the requested URL is loaded in
             // the tab before dispatching messages to the inner window
             // that contains the URL we're attempting to load.
-            tabListener.initializingTabs.add(tab);
+            tabListener.initializingTabs.add(nativeTab);
           }
 
-          return tabManager.convert(tab);
+          return tabManager.convert(nativeTab);
         });
       },
 
       async remove(tabs) {
         if (!Array.isArray(tabs)) {
           tabs = [tabs];
         }
 
         for (let tabId of tabs) {
-          let tab = tabTracker.getTab(tabId);
-          tab.ownerGlobal.gBrowser.removeTab(tab);
+          let nativeTab = tabTracker.getTab(tabId);
+          nativeTab.ownerGlobal.gBrowser.removeTab(nativeTab);
         }
       },
 
       async update(tabId, updateProperties) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let tabbrowser = tab.ownerGlobal.gBrowser;
+        let tabbrowser = nativeTab.ownerGlobal.gBrowser;
 
         if (updateProperties.url !== null) {
           let url = context.uri.resolve(updateProperties.url);
 
           if (!context.checkLoadURL(url, {dontReportErrors: true})) {
             return Promise.reject({message: `Illegal URL: ${url}`});
           }
 
-          tab.linkedBrowser.loadURI(url);
+          nativeTab.linkedBrowser.loadURI(url);
         }
 
         if (updateProperties.active !== null) {
           if (updateProperties.active) {
-            tabbrowser.selectedTab = tab;
+            tabbrowser.selectedTab = nativeTab;
           } else {
             // Not sure what to do here? Which tab should we select?
           }
         }
         if (updateProperties.muted !== null) {
-          if (tab.muted != updateProperties.muted) {
-            tab.toggleMuteAudio(extension.uuid);
+          if (nativeTab.muted != updateProperties.muted) {
+            nativeTab.toggleMuteAudio(extension.uuid);
           }
         }
         if (updateProperties.pinned !== null) {
           if (updateProperties.pinned) {
-            tabbrowser.pinTab(tab);
+            tabbrowser.pinTab(nativeTab);
           } else {
-            tabbrowser.unpinTab(tab);
+            tabbrowser.unpinTab(nativeTab);
           }
         }
         // FIXME: highlighted/selected, openerTabId
 
-        return tabManager.convert(tab);
+        return tabManager.convert(nativeTab);
       },
 
       async reload(tabId, reloadProperties) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
         let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
         if (reloadProperties && reloadProperties.bypassCache) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
-        tab.linkedBrowser.reloadWithFlags(flags);
+        nativeTab.linkedBrowser.reloadWithFlags(flags);
       },
 
       async get(tabId) {
-        let tab = tabTracker.getTab(tabId);
-
-        return tabManager.convert(tab);
+        return tabManager.get(tabId).convert();
       },
 
       getCurrent() {
-        let tab;
+        let tabData;
         if (context.tabId) {
-          tab = tabManager.get(context.tabId).convert();
+          tabData = tabManager.get(context.tabId).convert();
         }
-        return Promise.resolve(tab);
+        return Promise.resolve(tabData);
       },
 
       async query(queryInfo) {
         if (queryInfo.url !== null) {
           if (!extension.hasPermission("tabs")) {
             return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
           }
 
           queryInfo = Object.assign({}, queryInfo);
           queryInfo.url = new MatchPattern(queryInfo.url);
         }
 
         return Array.from(tabManager.query(queryInfo, context),
                           tab => tab.convert());
       },
 
-      captureVisibleTab(windowId, options) {
-        if (!extension.hasPermission("<all_urls>")) {
-          return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
-        }
-
+      async captureVisibleTab(windowId, options) {
         let window = windowId == null ?
           windowTracker.topWindow :
           windowTracker.getWindow(windowId, context);
 
-        let tab = window.gBrowser.selectedTab;
-        return tabListener.awaitTabReady(tab).then(() => {
-          let browser = tab.linkedBrowser;
-          let recipient = {
-            innerWindowID: browser.innerWindowID,
-          };
+        let tab = tabManager.wrapTab(window.gBrowser.selectedTab);
+        await tabListener.awaitTabReady(tab.nativeTab);
 
-          if (!options) {
-            options = {};
-          }
-          if (options.format == null) {
-            options.format = "png";
-          }
-          if (options.quality == null) {
-            options.quality = 92;
-          }
-
-          let message = {
-            options,
-            width: browser.clientWidth,
-            height: browser.clientHeight,
-          };
-
-          return context.sendMessage(browser.messageManager, "Extension:Capture",
-                                     message, {recipient});
-        });
+        return tab.capture(context, options);
       },
 
       async detectLanguage(tabId) {
-        let tab = getTabOrActive(tabId);
+        let tab = await promiseTabWhenReady(tabId);
 
-        return tabListener.awaitTabReady(tab).then(() => {
-          let browser = tab.linkedBrowser;
-          let recipient = {innerWindowID: browser.innerWindowID};
-
-          return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
-                                     {}, {recipient});
-        });
+        return tab.sendMessage(context, "Extension:DetectLanguage");
       },
 
       async executeScript(tabId, details) {
         let tab = await promiseTabWhenReady(tabId);
 
         return tab.executeScript(context, details);
       },
 
@@ -614,131 +581,131 @@ extensions.registerSchemaAPI("tabs", "ad
             move([tabA, tabB], {index: 0})
               -> tabA to 0, tabB to 1 if tabA and tabB are in the same window
             move([tabA, tabB], {index: 0})
               -> tabA to 0, tabB to 0 if tabA and tabB are in different windows
         */
         let indexMap = new Map();
 
         let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
-        for (let tab of tabs) {
+        for (let nativeTab of tabs) {
           // If the window is not specified, use the window from the tab.
-          let window = destinationWindow || tab.ownerGlobal;
+          let window = destinationWindow || nativeTab.ownerGlobal;
           let gBrowser = window.gBrowser;
 
           let insertionPoint = indexMap.get(window) || index;
           // If the index is -1 it should go to the end of the tabs.
           if (insertionPoint == -1) {
             insertionPoint = gBrowser.tabs.length;
           }
 
           // We can only move pinned tabs to a point within, or just after,
           // the current set of pinned tabs. Unpinned tabs, likewise, can only
           // be moved to a position after the current set of pinned tabs.
           // Attempts to move a tab to an illegal position are ignored.
           let numPinned = gBrowser._numPinnedTabs;
-          let ok = tab.pinned ? insertionPoint <= numPinned : insertionPoint >= numPinned;
+          let ok = nativeTab.pinned ? insertionPoint <= numPinned : insertionPoint >= numPinned;
           if (!ok) {
             continue;
           }
 
           indexMap.set(window, insertionPoint + 1);
 
-          if (tab.ownerGlobal != window) {
+          if (nativeTab.ownerGlobal != window) {
             // If the window we are moving the tab in is different, then move the tab
             // to the new window.
-            tab = gBrowser.adoptTab(tab, insertionPoint, false);
+            nativeTab = gBrowser.adoptTab(nativeTab, insertionPoint, false);
           } else {
             // If the window we are moving is the same, just move the tab.
-            gBrowser.moveTabTo(tab, insertionPoint);
+            gBrowser.moveTabTo(nativeTab, insertionPoint);
           }
-          tabsMoved.push(tab);
+          tabsMoved.push(nativeTab);
         }
 
-        return tabsMoved.map(tab => tabManager.convert(tab));
+        return tabsMoved.map(nativeTab => tabManager.convert(nativeTab));
       },
 
       duplicate(tabId) {
-        let tab = tabTracker.getTab(tabId);
+        let nativeTab = tabTracker.getTab(tabId);
 
-        let gBrowser = tab.ownerGlobal.gBrowser;
-        let newTab = gBrowser.duplicateTab(tab);
+        let gBrowser = nativeTab.ownerGlobal.gBrowser;
+        let newTab = gBrowser.duplicateTab(nativeTab);
 
         return new Promise(resolve => {
           // We need to use SSTabRestoring because any attributes set before
           // are ignored. SSTabRestored is too late and results in a jump in
           // the UI. See http://bit.ly/session-store-api for more information.
           newTab.addEventListener("SSTabRestoring", function() {
             // As the tab is restoring, move it to the correct position.
 
             // Pinned tabs that are duplicated are inserted
             // after the existing pinned tab and pinned.
-            if (tab.pinned) {
+            if (nativeTab.pinned) {
               gBrowser.pinTab(newTab);
             }
-            gBrowser.moveTabTo(newTab, tab._tPos + 1);
+            gBrowser.moveTabTo(newTab, nativeTab._tPos + 1);
           }, {once: true});
 
           newTab.addEventListener("SSTabRestored", function() {
             // Once it has been restored, select it and return the promise.
             gBrowser.selectedTab = newTab;
 
             resolve(tabManager.convert(newTab));
           }, {once: true});
         });
       },
 
       getZoom(tabId) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let {ZoomManager} = tab.ownerGlobal;
-        let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+        let {ZoomManager} = nativeTab.ownerGlobal;
+        let zoom = ZoomManager.getZoomForBrowser(nativeTab.linkedBrowser);
 
         return Promise.resolve(zoom);
       },
 
       setZoom(tabId, zoom) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let {FullZoom, ZoomManager} = tab.ownerGlobal;
+        let {FullZoom, ZoomManager} = nativeTab.ownerGlobal;
 
         if (zoom === 0) {
           // A value of zero means use the default zoom factor.
-          return FullZoom.reset(tab.linkedBrowser);
+          return FullZoom.reset(nativeTab.linkedBrowser);
         } else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
-          FullZoom.setZoom(zoom, tab.linkedBrowser);
+          FullZoom.setZoom(zoom, nativeTab.linkedBrowser);
         } else {
           return Promise.reject({
             message: `Zoom value ${zoom} out of range (must be between ${ZoomManager.MIN} and ${ZoomManager.MAX})`,
           });
         }
 
         return Promise.resolve();
       },
 
       _getZoomSettings(tabId) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let {FullZoom} = tab.ownerGlobal;
+        let {FullZoom} = nativeTab.ownerGlobal;
 
         return {
           mode: "automatic",
           scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
           defaultZoomFactor: 1,
         };
       },
 
       getZoomSettings(tabId) {
         return Promise.resolve(this._getZoomSettings(tabId));
       },
 
       setZoomSettings(tabId, settings) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let currentSettings = this._getZoomSettings(tab.id);
+        let currentSettings = this._getZoomSettings(tabTracker.getId(nativeTab));
 
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
 
       onZoomChange: new SingletonEventManager(context, "tabs.onZoomChange", fire => {
@@ -749,53 +716,53 @@ extensions.registerSchemaAPI("tabs", "ad
         };
 
         // Stores the last known zoom level for each tab's browser.
         // WeakMap[<browser> -> number]
         let zoomLevels = new WeakMap();
 
         // Store the zoom level for all existing tabs.
         for (let window of windowTracker.browserWindows()) {
-          for (let tab of window.gBrowser.tabs) {
-            let browser = tab.linkedBrowser;
+          for (let nativeTab of window.gBrowser.tabs) {
+            let browser = nativeTab.linkedBrowser;
             zoomLevels.set(browser, getZoomLevel(browser));
           }
         }
 
         let tabCreated = (eventName, event) => {
-          let browser = event.tab.linkedBrowser;
+          let browser = event.nativeTab.linkedBrowser;
           zoomLevels.set(browser, getZoomLevel(browser));
         };
 
 
         let zoomListener = event => {
           let browser = event.originalTarget;
 
           // For non-remote browsers, this event is dispatched on the document
           // rather than on the <browser>.
           if (browser instanceof Ci.nsIDOMDocument) {
             browser = browser.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
           }
 
           let {gBrowser} = browser.ownerGlobal;
-          let tab = gBrowser.getTabForBrowser(browser);
-          if (!tab) {
+          let nativeTab = gBrowser.getTabForBrowser(browser);
+          if (!nativeTab) {
             // We only care about zoom events in the top-level browser of a tab.
             return;
           }
 
           let oldZoomFactor = zoomLevels.get(browser);
           let newZoomFactor = getZoomLevel(browser);
 
           if (oldZoomFactor != newZoomFactor) {
             zoomLevels.set(browser, newZoomFactor);
 
-            let tabId = tabTracker.getId(tab);
+            let tabId = tabTracker.getId(nativeTab);
             fire.async({
               tabId,
               oldZoomFactor,
               newZoomFactor,
               zoomSettings: self.tabs._getZoomSettings(tabId),
             });
           }
         };
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -44,33 +44,33 @@ global.TabContext = function TabContext(
 
   windowTracker.addListener("progress", this);
   windowTracker.addListener("TabSelect", this);
 
   EventEmitter.decorate(this);
 };
 
 TabContext.prototype = {
-  get(tab) {
-    if (!this.tabData.has(tab)) {
-      this.tabData.set(tab, this.getDefaults(tab));
+  get(nativeTab) {
+    if (!this.tabData.has(nativeTab)) {
+      this.tabData.set(nativeTab, this.getDefaults(nativeTab));
     }
 
-    return this.tabData.get(tab);
+    return this.tabData.get(nativeTab);
   },
 
-  clear(tab) {
-    this.tabData.delete(tab);
+  clear(nativeTab) {
+    this.tabData.delete(nativeTab);
   },
 
   handleEvent(event) {
     if (event.type == "TabSelect") {
-      let tab = event.target;
-      this.emit("tab-select", tab);
-      this.emit("location-change", tab);
+      let nativeTab = event.target;
+      this.emit("tab-select", nativeTab);
+      this.emit("location-change", nativeTab);
     }
   },
 
   onStateChange(browser, webProgress, request, stateFlags, statusCode) {
     let flags = Ci.nsIWebProgressListener;
 
     if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
           this.lastLocation.has(browser))) {
@@ -78,18 +78,18 @@ TabContext.prototype = {
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     let gBrowser = browser.ownerGlobal.gBrowser;
     let lastLocation = this.lastLocation.get(browser);
     if (browser === gBrowser.selectedBrowser &&
         !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
-      let tab = gBrowser.getTabForBrowser(browser);
-      this.emit("location-change", tab, true);
+      let nativeTab = gBrowser.getTabForBrowser(browser);
+      this.emit("location-change", nativeTab, true);
     }
     this.lastLocation.set(browser, browser.currentURI);
   },
 
   shutdown() {
     windowTracker.removeListener("progress", this);
     windowTracker.removeListener("TabSelect", this);
   },
@@ -101,16 +101,31 @@ class WindowTracker extends WindowTracke
     window.gBrowser.addTabsProgressListener(listener);
   }
 
   removeProgressListener(window, listener) {
     window.gBrowser.removeTabsProgressListener(listener);
   }
 }
 
+/**
+ * An event manager API provider which listens for a DOM event in any browser
+ * window, and calls the given listener function whenever an event is received.
+ * That listener function receives a `fire` object, which it can use to dispatch
+ * events to the extension, and a DOM event object.
+ *
+ * @param {BaseContext} context
+ *        The extension context which the event manager belongs to.
+ * @param {string} name
+ *        The API name of the event manager, e.g.,"runtime.onMessage".
+ * @param {string} event
+ *        The name of the DOM event to listen for.
+ * @param {function} listener
+ *        The listener function to call when a DOM event is received.
+ */
 global.WindowEventManager = class extends SingletonEventManager {
   constructor(context, name, event, listener) {
     super(context, name, fire => {
       let listener2 = listener.bind(null, fire);
 
       windowTracker.addListener(event, listener2);
       return () => {
         windowTracker.removeListener(event, listener2);
@@ -147,38 +162,38 @@ class TabTracker extends TabTrackerBase 
     windowTracker.addCloseListener(this._handleWindowClose);
 
     /* eslint-disable mozilla/balanced-listeners */
     this.on("tab-detached", this._handleTabDestroyed);
     this.on("tab-removed", this._handleTabDestroyed);
     /* eslint-enable mozilla/balanced-listeners */
   }
 
-  getId(tab) {
-    if (this._tabs.has(tab)) {
-      return this._tabs.get(tab);
+  getId(nativeTab) {
+    if (this._tabs.has(nativeTab)) {
+      return this._tabs.get(nativeTab);
     }
 
     this.init();
 
     let id = this._nextId++;
-    this.setId(tab, id);
+    this.setId(nativeTab, id);
     return id;
   }
 
-  setId(tab, id) {
-    this._tabs.set(tab, id);
-    this._tabIds.set(id, tab);
+  setId(nativeTab, id) {
+    this._tabs.set(nativeTab, id);
+    this._tabIds.set(id, nativeTab);
   }
 
-  _handleTabDestroyed(event, {tab}) {
-    let id = this._tabs.get(tab);
+  _handleTabDestroyed(event, {nativeTab}) {
+    let id = this._tabs.get(nativeTab);
     if (id) {
-      this._tabs.delete(tab);
-      if (this._tabIds.get(id) === tab) {
+      this._tabs.delete(nativeTab);
+      if (this._tabIds.get(id) === nativeTab) {
         this._tabIds.delete(id);
       }
     }
   }
 
   /**
    * Returns the XUL <tab> element associated with the given tab ID. If no tab
    * with the given ID exists, and no default value is provided, an error is
@@ -187,41 +202,46 @@ class TabTracker extends TabTrackerBase 
    * @param {integer} tabId
    *        The ID of the tab to retrieve.
    * @param {*} default_
    *        The value to return if no tab exists with the given ID.
    * @returns {Element<tab>}
    *        A XUL <tab> element.
    */
   getTab(tabId, default_ = undefined) {
-    let tab = this._tabIds.get(tabId);
-    if (tab) {
-      return tab;
+    let nativeTab = this._tabIds.get(tabId);
+    if (nativeTab) {
+      return nativeTab;
     }
     if (default_ !== undefined) {
       return default_;
     }
     throw new ExtensionError(`Invalid tab ID: ${tabId}`);
   }
 
+  /**
+   * @param {Event} event
+   *        The DOM Event to handle.
+   * @private
+   */
   handleEvent(event) {
-    let tab = event.target;
+    let nativeTab = event.target;
 
     switch (event.type) {
       case "TabOpen":
         let {adoptedTab} = event.detail;
         if (adoptedTab) {
           this.adoptedTabs.set(adoptedTab, event.target);
 
           // This tab is being created to adopt a tab from a different window.
           // Copy the ID from the old tab to the new.
-          this.setId(tab, this.getId(adoptedTab));
+          this.setId(nativeTab, this.getId(adoptedTab));
 
           adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
-            windowId: windowTracker.getId(tab.ownerGlobal),
+            windowId: windowTracker.getId(nativeTab.ownerGlobal),
           });
         }
 
         // We need to delay sending this event until the next tick, since the
         // tab does not have its final index when the TabOpen event is dispatched.
         Promise.resolve().then(() => {
           if (event.detail.adoptedTab) {
             this.emitAttached(event.originalTarget);
@@ -233,106 +253,156 @@ class TabTracker extends TabTrackerBase 
 
       case "TabClose":
         let {adoptedBy} = event.detail;
         if (adoptedBy) {
           // This tab is being closed because it was adopted by a new window.
           // Copy its ID to the new tab, in case it was created as the first tab
           // of a new window, and did not have an `adoptedTab` detail when it was
           // opened.
-          this.setId(adoptedBy, this.getId(tab));
+          this.setId(adoptedBy, this.getId(nativeTab));
 
-          this.emitDetached(tab, adoptedBy);
+          this.emitDetached(nativeTab, adoptedBy);
         } else {
-          this.emitRemoved(tab, false);
+          this.emitRemoved(nativeTab, false);
         }
         break;
     }
   }
 
+  /**
+   * A private method which is called whenever a new browser window is opened,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being opened.
+   * @private
+   */
   _handleWindowOpen(window) {
     if (window.arguments && window.arguments[0] instanceof window.XULElement) {
       // If the first window argument is a XUL element, it means the
       // window is about to adopt a tab from another window to replace its
       // initial tab.
       //
       // Note that this event handler depends on running before the
       // delayed startup code in browser.js, which is currently triggered
       // by the first MozAfterPaint event. That code handles finally
       // adopting the tab, and clears it from the arguments list in the
       // process, so if we run later than it, we're too late.
-      let tab = window.arguments[0];
+      let nativeTab = window.arguments[0];
       let adoptedBy = window.gBrowser.tabs[0];
 
-      this.adoptedTabs.set(tab, adoptedBy);
-      this.setId(adoptedBy, this.getId(tab));
+      this.adoptedTabs.set(nativeTab, adoptedBy);
+      this.setId(adoptedBy, this.getId(nativeTab));
 
       // We need to be sure to fire this event after the onDetached event
       // for the original tab.
       let listener = (event, details) => {
-        if (details.tab === tab) {
+        if (details.nativeTab === nativeTab) {
           this.off("tab-detached", listener);
 
           Promise.resolve().then(() => {
             this.emitAttached(details.adoptedBy);
           });
         }
       };
 
       this.on("tab-detached", listener);
     } else {
-      for (let tab of window.gBrowser.tabs) {
-        this.emitCreated(tab);
+      for (let nativeTab of window.gBrowser.tabs) {
+        this.emitCreated(nativeTab);
       }
     }
   }
 
+  /**
+   * A private method which is called whenever a browser window is closed,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being closed.
+   * @private
+   */
   _handleWindowClose(window) {
-    for (let tab of window.gBrowser.tabs) {
-      if (this.adoptedTabs.has(tab)) {
-        this.emitDetached(tab, this.adoptedTabs.get(tab));
+    for (let nativeTab of window.gBrowser.tabs) {
+      if (this.adoptedTabs.has(nativeTab)) {
+        this.emitDetached(nativeTab, this.adoptedTabs.get(nativeTab));
       } else {
-        this.emitRemoved(tab, true);
+        this.emitRemoved(nativeTab, true);
       }
     }
   }
 
-  emitAttached(tab) {
-    let newWindowId = windowTracker.getId(tab.ownerGlobal);
-    let tabId = this.getId(tab);
+  /**
+   * Emits a "tab-attached" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element in the window to which the tab is being attached.
+   * @private
+   */
+  emitAttached(nativeTab) {
+    let newWindowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
 
-    this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
+    this.emit("tab-attached", {nativeTab, tabId, newWindowId, newPosition: nativeTab._tPos});
   }
 
-  emitDetached(tab, adoptedBy) {
-    let oldWindowId = windowTracker.getId(tab.ownerGlobal);
-    let tabId = this.getId(tab);
+  /**
+   * Emits a "tab-detached" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element in the window from which the tab is being detached.
+   * @param {NativeTab} adoptedBy
+   *        The tab element in the window to which detached tab is being moved,
+   *        and will adopt this tab's contents.
+   * @private
+   */
+  emitDetached(nativeTab, adoptedBy) {
+    let oldWindowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
 
-    this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
+    this.emit("tab-detached", {nativeTab, adoptedBy, tabId, oldWindowId, oldPosition: nativeTab._tPos});
   }
 
-  emitCreated(tab) {
-    this.emit("tab-created", {tab});
+  /**
+   * Emits a "tab-created" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being created.
+   * @private
+   */
+  emitCreated(nativeTab) {
+    this.emit("tab-created", {nativeTab});
   }
 
-  emitRemoved(tab, isWindowClosing) {
-    let windowId = windowTracker.getId(tab.ownerGlobal);
-    let tabId = this.getId(tab);
+  /**
+   * Emits a "tab-removed" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being removed.
+   * @param {boolean} isWindowClosing
+   *        True if the tab is being removed because the browser window is
+   *        closing.
+   * @private
+   */
+  emitRemoved(nativeTab, isWindowClosing) {
+    let windowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
 
     // When addons run in-process, `window.close()` is synchronous. Most other
     // addon-invoked calls are asynchronous since they go through a proxy
     // context via the message manager. This includes event registrations such
     // as `tabs.onRemoved.addListener`.
     //
     // So, even if `window.close()` were to be called (in-process) after calling
     // `tabs.onRemoved.addListener`, then the tab would be closed before the
     // event listener is registered. To make sure that the event listener is
     // notified, we dispatch `tabs.onRemoved` asynchronously.
     Services.tm.mainThread.dispatch(() => {
-      this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+      this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing});
     }, Ci.nsIThread.DISPATCH_NORMAL);
   }
 
   getBrowserData(browser) {
     if (browser.ownerGlobal.location.href === "about:addons") {
       // When we're loaded into a <browser> inside about:addons, we need to go up
       // one more level.
       browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -346,19 +416,19 @@ class TabTracker extends TabTrackerBase 
     };
 
     let {gBrowser} = browser.ownerGlobal;
     // Some non-browser windows have gBrowser but not
     // getTabForBrowser!
     if (gBrowser && gBrowser.getTabForBrowser) {
       result.windowId = windowTracker.getId(browser.ownerGlobal);
 
-      let tab = gBrowser.getTabForBrowser(browser);
-      if (tab) {
-        result.tabId = this.getId(tab);
+      let nativeTab = gBrowser.getTabForBrowser(browser);
+      if (nativeTab) {
+        result.tabId = this.getId(nativeTab);
       }
     }
 
     return result;
   }
 
   get activeTab() {
     let window = windowTracker.topWindow;
@@ -371,114 +441,142 @@ class TabTracker extends TabTrackerBase 
 
 windowTracker = new WindowTracker();
 tabTracker = new TabTracker();
 
 Object.assign(global, {tabTracker, windowTracker});
 
 class Tab extends TabBase {
   get _favIconUrl() {
-    return this.window.gBrowser.getIcon(this.tab);
+    return this.window.gBrowser.getIcon(this.nativeTab);
   }
 
   get audible() {
-    return this.tab.soundPlaying;
+    return this.nativeTab.soundPlaying;
   }
 
   get browser() {
-    return this.tab.linkedBrowser;
+    return this.nativeTab.linkedBrowser;
   }
 
   get cookieStoreId() {
-    return getCookieStoreIdForTab(this, this.tab);
+    return getCookieStoreIdForTab(this, this.nativeTab);
   }
 
   get height() {
     return this.browser.clientHeight;
   }
 
   get index() {
-    return this.tab._tPos;
-  }
-
-  get innerWindowID() {
-    return this.browser.innerWindowID;
+    return this.nativeTab._tPos;
   }
 
   get mutedInfo() {
-    let tab = this.tab;
+    let {nativeTab} = this;
 
-    let mutedInfo = {muted: tab.muted};
-    if (tab.muteReason === null) {
+    let mutedInfo = {muted: nativeTab.muted};
+    if (nativeTab.muteReason === null) {
       mutedInfo.reason = "user";
-    } else if (tab.muteReason) {
+    } else if (nativeTab.muteReason) {
       mutedInfo.reason = "extension";
-      mutedInfo.extensionId = tab.muteReason;
+      mutedInfo.extensionId = nativeTab.muteReason;
     }
 
     return mutedInfo;
   }
 
   get pinned() {
-    return this.tab.pinned;
+    return this.nativeTab.pinned;
   }
 
   get active() {
-    return this.tab.selected;
+    return this.nativeTab.selected;
   }
 
   get selected() {
-    return this.tab.selected;
+    return this.nativeTab.selected;
   }
 
   get status() {
-    if (this.tab.getAttribute("busy") === "true") {
+    if (this.nativeTab.getAttribute("busy") === "true") {
       return "loading";
     }
     return "complete";
   }
 
   get width() {
     return this.browser.clientWidth;
   }
 
   get window() {
-    return this.tab.ownerGlobal;
+    return this.nativeTab.ownerGlobal;
   }
 
   get windowId() {
     return windowTracker.getId(this.window);
   }
 
-  static convertFromSessionStoreClosedData(extension, tab, window = null) {
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} tabData
+   *        Session store data for a closed tab, as returned by
+   *        `SessionStore.getClosedTabData()`.
+   * @param {DOMWindow} [window = null]
+   *        The browser window which the tab belonged to before it was closed.
+   *        May be null if the window the tab belonged to no longer exists.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, tabData, window = null) {
     let result = {
-      sessionId: String(tab.closedId),
-      index: tab.pos ? tab.pos : 0,
+      sessionId: String(tabData.closedId),
+      index: tabData.pos ? tabData.pos : 0,
       windowId: window && windowTracker.getId(window),
       selected: false,
       highlighted: false,
       active: false,
       pinned: false,
-      incognito: Boolean(tab.state && tab.state.isPrivate),
+      incognito: Boolean(tabData.state && tabData.state.isPrivate),
     };
 
-    if (extension.tabManager.hasTabPermission(tab)) {
-      let entries = tab.state ? tab.state.entries : tab.entries;
+    if (extension.tabManager.hasTabPermission(tabData)) {
+      let entries = tabData.state ? tabData.state.entries : tabData.entries;
       result.url = entries[0].url;
       result.title = entries[0].title;
-      if (tab.image) {
-        result.favIconUrl = tab.image;
+      if (tabData.image) {
+        result.favIconUrl = tabData.image;
       }
     }
 
     return result;
   }
 }
 
 class Window extends WindowBase {
+  /**
+   * Update the geometry of the browser window.
+   *
+   * @param {Object} options
+   *        An object containing new values for the window's geometry.
+   * @param {integer} [options.left]
+   *        The new pixel distance of the left side of the browser window from
+   *        the left of the screen.
+   * @param {integer} [options.top]
+   *        The new pixel distance of the top side of the browser window from
+   *        the top of the screen.
+   * @param {integer} [options.width]
+   *        The new pixel width of the window.
+   * @param {integer} [options.height]
+   *        The new pixel height of the window.
+   */
   updateGeometry(options) {
     let {window} = this;
 
     if (options.left !== null || options.top !== null) {
       let left = options.left !== null ? options.left : window.screenX;
       let top = options.top !== null ? options.top : window.screenY;
       window.moveTo(left, top);
     }
@@ -577,64 +675,77 @@ class Window extends WindowBase {
       default:
         throw new Error(`Unexpected window state: ${state}`);
     }
   }
 
   * getTabs() {
     let {tabManager} = this.extension;
 
-    for (let tab of this.window.gBrowser.tabs) {
-      yield tabManager.getWrapper(tab);
+    for (let nativeTab of this.window.gBrowser.tabs) {
+      yield tabManager.getWrapper(nativeTab);
     }
   }
 
-  static convertFromSessionStoreClosedData(extension, window) {
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} windowData
+   *        Session store data for a closed window, as returned by
+   *        `SessionStore.getClosedWindowData()`.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, windowData) {
     let result = {
-      sessionId: String(window.closedId),
+      sessionId: String(windowData.closedId),
       focused: false,
       incognito: false,
       type: "normal", // this is always "normal" for a closed window
       // Surely this does not actually work?
-      state: this.getState(window),
+      state: this.getState(windowData),
       alwaysOnTop: false,
     };
 
-    if (window.tabs.length) {
-      result.tabs = window.tabs.map(tab => {
-        return Tab.convertFromSessionStoreClosedData(extension, tab);
+    if (windowData.tabs.length) {
+      result.tabs = windowData.tabs.map(tabData => {
+        return Tab.convertFromSessionStoreClosedData(extension, tabData);
       });
     }
 
     return result;
   }
 }
 
 Object.assign(global, {Tab, Window});
 
 class TabManager extends TabManagerBase {
   get(tabId, default_ = undefined) {
-    let tab = tabTracker.getTab(tabId, default_);
+    let nativeTab = tabTracker.getTab(tabId, default_);
 
-    if (tab) {
-      return this.getWrapper(tab);
+    if (nativeTab) {
+      return this.getWrapper(nativeTab);
     }
     return default_;
   }
 
-  addActiveTabPermission(tab = tabTracker.activeTab) {
-    return super.addActiveTabPermission(tab);
+  addActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.addActiveTabPermission(nativeTab);
   }
 
-  revokeActiveTabPermission(tab = tabTracker.activeTab) {
-    return super.revokeActiveTabPermission(tab);
+  revokeActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.revokeActiveTabPermission(nativeTab);
   }
 
-  wrapTab(tab) {
-    return new Tab(this.extension, tab, tabTracker.getId(tab));
+  wrapTab(nativeTab) {
+    return new Tab(this.extension, nativeTab, tabTracker.getId(nativeTab));
   }
 }
 
 class WindowManager extends WindowManagerBase {
   get(windowId, context) {
     let window = windowTracker.getWindow(windowId, context);
 
     return this.getWrapper(window);
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
@@ -1,15 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>${url}</body></html>`;
 
 add_task(function* testBrowserActionClickCanceled() {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
       "permissions": ["activeTab"],
     },
@@ -22,17 +24,16 @@ add_task(function* testBrowserActionClic
   yield extension.startup();
 
   const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   let ext = GlobalManager.extensionMap.get(extension.id);
   let browserAction = browserActionFor(ext);
 
   let widget = getBrowserActionWidget(extension).forWindow(window);
-  let tab = window.gBrowser.selectedTab;
 
   // Test canceled click.
   EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
 
   isnot(browserAction.pendingPopup, null, "Have pending popup");
   is(browserAction.pendingPopup.window, window, "Have pending popup for the correct window");
 
   is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
@@ -71,16 +72,18 @@ add_task(function* testBrowserActionClic
 
   is(browserAction.pendingPopup, null, "Pending popup was cleared");
   is(browserAction.pendingPopupTimeout, null, "Pending popup timeout was cleared");
 
   yield promisePopupShown(getBrowserActionPopup(extension));
   yield closeBrowserAction(extension);
 
   yield extension.unload();
+
+  yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* testBrowserActionDisabled() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -104,16 +104,17 @@ def rust_triple_alias(host_or_target):
             # DragonFly
             ('x86_64', 'DragonFly'): 'x86_64-unknown-dragonfly',
             # FreeBSD
             ('x86', 'FreeBSD'): 'i686-unknown-freebsd',
             ('x86_64', 'FreeBSD'): 'x86_64-unknown-freebsd',
             # NetBSD
             ('x86_64', 'NetBSD'): 'x86_64-unknown-netbsd',
             # OpenBSD
+            ('x86', 'OpenBSD'): 'i686-unknown-openbsd',
             ('x86_64', 'OpenBSD'): 'x86_64-unknown-openbsd',
             # Linux
             ('x86', 'Linux'): 'i686-unknown-linux-gnu',
             # Linux
             ('x86_64', 'Linux'): 'x86_64-unknown-linux-gnu',
             ('aarch64', 'Linux'): 'aarch64-unknown-linux-gnu',
             # OS X and iOS
             ('x86', 'OSX'): 'i686-apple-darwin',
--- a/caps/DomainPolicy.cpp
+++ b/caps/DomainPolicy.cpp
@@ -134,17 +134,17 @@ CopyURIs(const InfallibleTArray<URIParam
 {
     for (uint32_t i = 0; i < aDomains.Length(); i++) {
         nsCOMPtr<nsIURI> uri = DeserializeURI(aDomains[i]);
         aSet->Add(uri);
     }
 }
 
 void
-DomainPolicy::ApplyClone(DomainPolicyClone* aClone)
+DomainPolicy::ApplyClone(const DomainPolicyClone* aClone)
 {
     CopyURIs(aClone->blacklist(), mBlacklist);
     CopyURIs(aClone->whitelist(), mWhitelist);
     CopyURIs(aClone->superBlacklist(), mSuperBlacklist);
     CopyURIs(aClone->superWhitelist(), mSuperWhitelist);
 }
 
 static already_AddRefed<nsIURI>
--- a/caps/nsIDomainPolicy.idl
+++ b/caps/nsIDomainPolicy.idl
@@ -12,16 +12,17 @@ interface nsIDomainSet;
 namespace mozilla {
 namespace dom {
 class DomainPolicyClone;
 }
 }
 %}
 
 [ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
+[ptr] native DomainPolicyCloneConstPtr(const mozilla::dom::DomainPolicyClone);
 
 /*
  * When a domain policy is instantiated by invoking activateDomainPolicy() on
  * nsIScriptSecurityManager, these domain sets are consulted when each new
  * global is created (they have no effect on already-created globals).
  * If javascript is globally enabled with |javascript.enabled|, the blacklists
  * are consulted. If globally disabled, the whitelists are consulted. Lookups
  * on blacklist and whitelist happen with contains(), and lookups on
@@ -36,17 +37,17 @@ interface nsIDomainPolicy : nsISupports
     readonly attribute nsIDomainSet blacklist;
     readonly attribute nsIDomainSet superBlacklist;
     readonly attribute nsIDomainSet whitelist;
     readonly attribute nsIDomainSet superWhitelist;
 
     void deactivate();
 
     [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone);
-    [noscript, notxpcom] void applyClone(in DomainPolicyClonePtr aClone);
+    [noscript, notxpcom] void applyClone(in DomainPolicyCloneConstPtr aClone);
 };
 
 [scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)]
 interface nsIDomainSet : nsISupports
 {
     /*
      * The type of the set. See: DomainSetType
      */
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -54,19 +54,19 @@ exports.viewSourceInDebugger = Task.asyn
   // If the Debugger was already open, switch to it and try to show the
   // source immediately. Otherwise, initialize it and wait for the sources
   // to be added first.
   let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
   let dbg = yield toolbox.loadTool("jsdebugger");
 
   // New debugger frontend
   if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
-    yield toolbox.selectTool("jsdebugger");
     const source = dbg._selectors().getSourceByURL(dbg._getState(), sourceURL);
     if (source) {
+      yield toolbox.selectTool("jsdebugger");
       dbg._actions().selectSourceURL(sourceURL, { line: sourceLine });
       return true;
     }
 
     exports.viewSource(toolbox, sourceURL, sourceLine);
     return false;
   }
 
--- a/dom/battery/moz.build
+++ b/dom/battery/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
 EXPORTS.mozilla.dom.battery += [
     'Constants.h',
     'Types.h',
 ]
 
 SOURCES += [
     'BatteryManager.cpp',
 ]
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -15441,16 +15441,17 @@ class CGCallback(CGClass):
         # Note that we need to insert at rvIndex + 1, since we inserted a
         # thisVal arg at the start.
         argnamesWithoutRv.insert(rvIndex + 1, "rv")
 
         errorReturn = method.getDefaultRetval()
 
         setupCall = fill(
             """
+            MOZ_ASSERT(!aRv.Failed(), "Don't pass an already-failed ErrorResult to a callback!");
             if (!aExecutionReason) {
               aExecutionReason = "${executionReason}";
             }
             CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aCompartment);
             if (!s.GetContext()) {
               MOZ_ASSERT(aRv.Failed());
               return${errorReturn};
             }
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'nsIScriptError.idl'
 ]
 
 XPIDL_MODULE = 'dom_bindings'
 
--- a/dom/broadcastchannel/moz.build
+++ b/dom/broadcastchannel/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 EXPORTS.mozilla.dom += [
     'BroadcastChannel.h',
 ]
 
 UNIFIED_SOURCES += [
     'BroadcastChannel.cpp',
     'BroadcastChannelChild.cpp',
     'BroadcastChannelParent.cpp',
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 EXPORTS.mozilla += [
     'BrowserElementParent.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'BrowserElementAudioChannel.h',
 ]
 
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -1,14 +1,18 @@
 # -*- 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/.
 
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 EXPORTS.mozilla.dom.cache += [
     'Action.h',
     'ActorChild.h',
     'ActorUtils.h',
     'AutoUtils.h',
     'Cache.h',
     'CacheChild.h',
     'CacheOpChild.h',
--- a/dom/commandhandler/moz.build
+++ b/dom/commandhandler/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "Embedding: APIs")
+
 XPIDL_SOURCES += [
     'nsICommandManager.idl',
     'nsICommandParams.idl',
     'nsIControllerCommand.idl',
     'nsIControllerCommandTable.idl',
     'nsIControllerContext.idl',
     'nsPICommandUpdater.idl',
 ]
--- a/dom/console/moz.build
+++ b/dom/console/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 XPIDL_SOURCES += [
     'nsIConsoleAPIStorage.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS += [
     'nsIConsoleReportCollector.h',
--- a/dom/crypto/moz.build
+++ b/dom/crypto/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
 EXPORTS.mozilla.dom += [
     'CryptoBuffer.h',
     'CryptoKey.h',
     'KeyAlgorithmProxy.h',
     'WebCryptoCommon.h',
     'WebCryptoTask.h',
     'WebCryptoThreadPool.h'
 ]
--- a/dom/devicestorage/moz.build
+++ b/dom/devicestorage/moz.build
@@ -1,14 +1,18 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+
 EXPORTS += [
     'DeviceStorage.h',
     'DeviceStorageFileDescriptor.h',
     'nsDeviceStorage.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'DeviceStorageAreaListener.h',
--- a/dom/flyweb/moz.build
+++ b/dom/flyweb/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Flyweb")
+
 EXPORTS.mozilla.dom += [
     'FlyWebDiscoveryManager.h',
     'FlyWebPublishedServer.h',
     'FlyWebPublishedServerIPC.h',
     'FlyWebPublishOptionsIPCSerializer.h',
     'FlyWebServerEvents.h',
     'FlyWebService.h',
     'HttpServer.h',
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
 IPDL_SOURCES += [
     'ipc/GamepadEventTypes.ipdlh',
     'ipc/PGamepadEventChannel.ipdl',
     'ipc/PGamepadTestChannel.ipdl'
 ]
 
 EXPORTS.mozilla.dom += [
     'Gamepad.h',
--- a/dom/geolocation/moz.build
+++ b/dom/geolocation/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "Geolocation")
+
 EXPORTS += [
     'nsGeoPosition.h',
     'nsGeoPositionIPCSerialiser.h',
 ]
 
 SOURCES += [
     'nsGeolocation.cpp',
 ]
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -497,20 +497,34 @@ ContentChild::~ContentChild()
 #endif
 
 NS_INTERFACE_MAP_BEGIN(ContentChild)
   NS_INTERFACE_MAP_ENTRY(nsIContentChild)
   NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentChild)
 NS_INTERFACE_MAP_END
 
+
+mozilla::ipc::IPCResult
+ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
+                                            const StructuredCloneData& aInitialData,
+                                            nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache)
+{
+  mLookAndFeelCache = aLookAndFeelIntCache;
+  InitXPCOM(aXPCOMInit, aInitialData);
+  InitGraphicsDeviceData();
+  return IPC_OK();
+}
+
 bool
 ContentChild::Init(MessageLoop* aIOLoop,
                    base::ProcessId aParentPid,
-                   IPC::Channel* aChannel)
+                   IPC::Channel* aChannel,
+                   uint64_t aChildID,
+                   bool aIsForBrowser)
 {
 #ifdef MOZ_WIDGET_GTK
   // We need to pass a display down to gtk_init because it's not going to
   // use the one from the environment on its own when deciding which backend
   // to use, and when starting under XWayland, it may choose to start with
   // the wayland backend instead of the x11 backend.
   // The DISPLAY environment variable is normally set by the parent process.
   char* display_name = PR_GetEnv("DISPLAY");
@@ -566,17 +580,18 @@ ContentChild::Init(MessageLoop* aIOLoop,
   SendBackUpXResources(FileDescriptor(xSocketFd));
 #endif
 
 #ifdef MOZ_CRASHREPORTER
   SendPCrashReporterConstructor(CrashReporter::CurrentThreadId(),
                                 XRE_GetProcessType());
 #endif
 
-  SendGetProcessAttributes(&mID, &mIsForBrowser);
+  mID = aChildID;
+  mIsForBrowser = aIsForBrowser;
 
 #ifdef NS_PRINTING
   // Force the creation of the nsPrintingProxy so that it's IPC counterpart,
   // PrintingParent, is always available for printing initiated from the parent.
   RefPtr<nsPrintingProxy> printingProxy = nsPrintingProxy::GetInstance();
 #endif
 
   SetProcessName(NS_LITERAL_STRING("Web Content"), true);
@@ -928,18 +943,24 @@ void
 ContentChild::InitGraphicsDeviceData()
 {
   // Initialize the graphics platform. This may contact the parent process
   // to read device preferences.
   gfxPlatform::GetPlatform();
 }
 
 void
-ContentChild::InitXPCOM()
+ContentChild::InitXPCOM(const XPCOMInitData& aXPCOMInit,
+                        const mozilla::dom::ipc::StructuredCloneData& aInitialData)
 {
+  SET_PREF_PHASE(pref_initPhase::BEGIN_ALL_PREFS);
+  for (unsigned int i = 0; i < aXPCOMInit.prefs().Length(); i++) {
+    Preferences::SetPreference(aXPCOMInit.prefs().ElementAt(i));
+  }
+  SET_PREF_PHASE(pref_initPhase::END_ALL_PREFS);
   // Do this as early as possible to get the parent process to initialize the
   // background thread since we'll likely need database information very soon.
   BackgroundChild::Startup();
 
   nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback =
     new BackgroundChildPrimer();
   if (!BackgroundChild::GetOrCreateForCurrentThread(callback)) {
     MOZ_CRASH("Failed to create PBackgroundChild!");
@@ -952,71 +973,60 @@ ContentChild::InitXPCOM()
     NS_WARNING("Couldn't acquire console service");
     return;
   }
 
   mConsoleListener = new ConsoleListener(this);
   if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
     NS_WARNING("Couldn't register console listener for child process");
 
-  bool isOffline, isLangRTL, haveBidiKeyboards;
-  bool isConnected;
-  int32_t captivePortalState;
-  ClipboardCapabilities clipboardCaps;
-  DomainPolicyClone domainPolicy;
-  StructuredCloneData initialData;
-  OptionalURIParams userContentSheetURL;
-
-  SendGetXPCOMProcessAttributes(&isOffline, &isConnected, &captivePortalState,
-                                &isLangRTL, &haveBidiKeyboards,
-                                &mAvailableDictionaries,
-                                &clipboardCaps, &domainPolicy, &initialData,
-                                &mFontFamilies, &userContentSheetURL,
-                                &mLookAndFeelCache);
-
-  RecvSetOffline(isOffline);
-  RecvSetConnectivity(isConnected);
-  RecvSetCaptivePortalState(captivePortalState);
-  RecvBidiKeyboardNotify(isLangRTL, haveBidiKeyboards);
+  mAvailableDictionaries = aXPCOMInit.dictionaries();
+
+  RecvSetOffline(aXPCOMInit.isOffline());
+  RecvSetConnectivity(aXPCOMInit.isConnected());
+  RecvSetCaptivePortalState(aXPCOMInit.captivePortalState());
+  RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(), aXPCOMInit.haveBidiKeyboards());
 
   // Create the CPOW manager as soon as possible.
   SendPJavaScriptConstructor();
 
-  if (domainPolicy.active()) {
+  if (aXPCOMInit.domainPolicy().active()) {
     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
     MOZ_ASSERT(ssm);
     ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy));
     if (!mPolicy) {
       MOZ_CRASH("Failed to activate domain policy.");
     }
-    mPolicy->ApplyClone(&domainPolicy);
+    mPolicy->ApplyClone(&aXPCOMInit.domainPolicy());
   }
 
   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
   if (nsCOMPtr<nsIClipboardProxy> clipboardProxy = do_QueryInterface(clipboard)) {
-    clipboardProxy->SetCapabilities(clipboardCaps);
+    clipboardProxy->SetCapabilities(aXPCOMInit.clipboardCaps());
   }
 
   {
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) {
       MOZ_CRASH();
     }
     ErrorResult rv;
     JS::RootedValue data(jsapi.cx());
-    initialData.Read(jsapi.cx(), &data, rv);
+    mozilla::dom::ipc::StructuredCloneData id;
+    id.Copy(aInitialData);
+    id.Read(jsapi.cx(), &data, rv);
     if (NS_WARN_IF(rv.Failed())) {
       MOZ_CRASH();
     }
     ProcessGlobal* global = ProcessGlobal::Get();
     global->SetInitialProcessData(data);
   }
 
   // The stylesheet cache is not ready yet. Store this URL for future use.
-  nsCOMPtr<nsIURI> ucsURL = DeserializeURI(userContentSheetURL);
+  nsCOMPtr<nsIURI> ucsURL = DeserializeURI(aXPCOMInit.userContentSheetURL());
   nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
 
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvRequestMemoryReport(const uint32_t& aGeneration,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -91,19 +91,22 @@ public:
                       const nsAString& aName,
                       const nsACString& aFeatures,
                       bool aForceNoOpener,
                       bool* aWindowIsNew,
                       mozIDOMWindowProxy** aReturn);
 
   bool Init(MessageLoop* aIOLoop,
             base::ProcessId aParentPid,
-            IPC::Channel* aChannel);
+            IPC::Channel* aChannel,
+            uint64_t aChildID,
+            bool aIsForBrowser);
 
-  void InitXPCOM();
+  void InitXPCOM(const XPCOMInitData& aXPCOMInit,
+                 const mozilla::dom::ipc::StructuredCloneData& aInitialData);
 
   void InitGraphicsDeviceData();
 
   static ContentChild* GetSingleton()
   {
     return sSingleton;
   }
 
@@ -568,16 +571,21 @@ public:
 
   mozilla::ipc::IPCResult
   RecvRequestMemoryReport(
           const uint32_t& generation,
           const bool& anonymize,
           const bool& minimizeMemoryUsage,
           const MaybeFileDesc& DMDFile) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
+                                const StructuredCloneData& aInitialData,
+                                nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache) override;
+
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   bool
   SendGetA11yContentId();
 #endif // defined(XP_WIN) && defined(ACCESSIBILITY)
 
   // Get a reference to the font family list passed from the chrome process,
   // for use during gfx initialization.
   InfallibleTArray<mozilla::dom::FontFamilyListEntry>&
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -181,16 +181,18 @@
 #include "mozilla/StyleSheetInlines.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsICaptivePortalService.h"
 
 #include "nsIBidiKeyboard.h"
 
 #include "nsLayoutStylesheetCache.h"
 
+#include "ContentPrefs.h"
+
 #ifdef MOZ_WEBRTC
 #include "signaling/src/peerconnection/WebrtcGlobalParent.h"
 #endif
 
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
 
@@ -1819,16 +1821,58 @@ ContentParent::InitializeMembers()
 }
 
 bool
 ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
   std::vector<std::string> extraArgs;
+  extraArgs.push_back("-childID");
+  char idStr[21];
+  SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
+  extraArgs.push_back(idStr);
+  extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
+
+  std::stringstream boolPrefs;
+  std::stringstream intPrefs;
+  std::stringstream stringPrefs;
+
+  size_t prefsLen;
+  ContentPrefs::GetContentPrefs(&prefsLen);
+
+  for (unsigned int i = 0; i < prefsLen; i++) {
+    MOZ_ASSERT(i == 0 || strcmp(ContentPrefs::GetContentPref(i), ContentPrefs::GetContentPref(i - 1)) > 0);
+    switch (Preferences::GetType(ContentPrefs::GetContentPref(i))) {
+    case nsIPrefBranch::PREF_INT:
+      intPrefs << i << ':' << Preferences::GetInt(ContentPrefs::GetContentPref(i)) << '|';
+      break;
+    case nsIPrefBranch::PREF_BOOL:
+      boolPrefs << i << ':' << Preferences::GetBool(ContentPrefs::GetContentPref(i)) << '|';
+      break;
+    case nsIPrefBranch::PREF_STRING: {
+      std::string value(Preferences::GetCString(ContentPrefs::GetContentPref(i)).get());
+      stringPrefs << i << ':' << value.length() << ':' << value << '|';
+    }
+      break;
+    case nsIPrefBranch::PREF_INVALID:
+      break;
+    default:
+      printf("preference type: %x\n", Preferences::GetType(ContentPrefs::GetContentPref(i)));
+      MOZ_CRASH();
+    }
+  }
+
+  extraArgs.push_back("-intPrefs");
+  extraArgs.push_back(intPrefs.str());
+  extraArgs.push_back("-boolPrefs");
+  extraArgs.push_back(boolPrefs.str());
+  extraArgs.push_back("-stringPrefs");
+  extraArgs.push_back(stringPrefs.str());
+
   if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
     MarkAsDead();
     return false;
   }
 
   Open(mSubprocess->GetChannel(),
      base::GetProcId(mSubprocess->GetChildProcessHandle()));
 
@@ -1838,16 +1882,27 @@ ContentParent::LaunchSubprocess(ProcessP
 
   ContentProcessManager::GetSingleton()->AddContentProcess(this);
 
   mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
 
   // Set a reply timeout for CPOWs.
   SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
 
+  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
+                        static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
+                                              .ToMilliseconds()));
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    nsAutoString cpId;
+    cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
+    obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
+  }
+
   return true;
 }
 
 ContentParent::ContentParent(ContentParent* aOpener,
                              const nsAString& aRemoteType)
   : nsIContentParent()
   , mLaunchTS(TimeStamp::Now())
   , mOpener(aOpener)
@@ -1895,16 +1950,100 @@ ContentParent::~ContentParent()
              !sBrowserContentParents->Get(mRemoteType)->Contains(this));
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority,
                             bool aSetupOffMainThreadCompositing,
                             bool aSendRegisteredChrome)
 {
+  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
+                        static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
+                                              .ToMilliseconds()));
+
+  XPCOMInitData xpcomInit;
+
+  Preferences::GetPreferences(&xpcomInit.prefs());
+  nsCOMPtr<nsIIOService> io(do_GetIOService());
+  MOZ_ASSERT(io, "No IO service?");
+  DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline());
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
+
+  rv = io->GetConnectivity(&xpcomInit.isConnected());
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?");
+
+  xpcomInit.captivePortalState() = nsICaptivePortalService::UNKNOWN;
+  nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CONTRACTID);
+  if (cps) {
+    cps->GetState(&xpcomInit.captivePortalState());
+  }
+
+  nsIBidiKeyboard* bidi = nsContentUtils::GetBidiKeyboard();
+
+  xpcomInit.isLangRTL() = false;
+  xpcomInit.haveBidiKeyboards() = false;
+  if (bidi) {
+    bidi->IsLangRTL(&xpcomInit.isLangRTL());
+    bidi->GetHaveBidiKeyboards(&xpcomInit.haveBidiKeyboards());
+  }
+
+  nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
+  MOZ_ASSERT(spellChecker, "No spell checker?");
+
+  spellChecker->GetDictionaryList(&xpcomInit.dictionaries());
+
+  nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
+  MOZ_ASSERT(clipboard, "No clipboard?");
+
+  rv = clipboard->SupportsSelectionClipboard(&xpcomInit.clipboardCaps().supportsSelectionClipboard());
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  rv = clipboard->SupportsFindClipboard(&xpcomInit.clipboardCaps().supportsFindClipboard());
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  // Let's copy the domain policy from the parent to the child (if it's active).
+  StructuredCloneData initialData;
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  if (ssm) {
+    ssm->CloneDomainPolicy(&xpcomInit.domainPolicy());
+
+    if (nsFrameMessageManager* mm = nsFrameMessageManager::sParentProcessManager) {
+      AutoJSAPI jsapi;
+      if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) {
+        MOZ_CRASH();
+      }
+      JS::RootedValue init(jsapi.cx());
+      nsresult result = mm->GetInitialProcessData(jsapi.cx(), &init);
+      if (NS_FAILED(result)) {
+        MOZ_CRASH();
+      }
+
+      ErrorResult rv;
+      initialData.Write(jsapi.cx(), init, rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
+        MOZ_CRASH();
+      }
+    }
+  }
+  // This is only implemented (returns a non-empty list) by MacOSX at present.
+  gfxPlatform::GetPlatform()->GetSystemFontFamilyList(&xpcomInit.fontFamilies());
+  nsTArray<LookAndFeelInt> lnfCache = LookAndFeel::GetIntCache();
+
+  // Content processes have no permission to access profile directory, so we
+  // send the file URL instead.
+  StyleSheet* ucs = nsLayoutStylesheetCache::For(StyleBackendType::Gecko)->UserContentSheet();
+  if (ucs) {
+    SerializeURI(ucs->GetSheetURI(), xpcomInit.userContentSheetURL());
+  } else {
+    SerializeURI(nullptr, xpcomInit.userContentSheetURL());
+  }
+
+  Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache);
+
   if (aSendRegisteredChrome) {
     nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
     nsChromeRegistryChrome* chromeRegistry =
       static_cast<nsChromeRegistryChrome*>(registrySvc.get());
     chromeRegistry->SendRegisteredChrome(this);
   }
 
   if (gAppData) {
@@ -2067,23 +2206,16 @@ ContentParent::Pid() const
 {
   if (!mSubprocess || !mSubprocess->GetChildProcessHandle()) {
     return -1;
   }
   return base::GetProcId(mSubprocess->GetChildProcessHandle());
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs)
-{
-  Preferences::GetPreferences(aPrefs);
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 ContentParent::RecvGetGfxVars(InfallibleTArray<GfxVarUpdate>* aVars)
 {
   // Ensure gfxVars is initialized (for xpcshell tests).
   gfxVars::Initialize();
 
   *aVars = gfxVars::FetchNonDefaultVars();
 
   // Now that content has initialized gfxVars, we can start listening for
@@ -2543,128 +2675,16 @@ ContentParent::RecvInitBackground(Endpoi
 {
   if (!BackgroundParent::Alloc(this, Move(aEndpoint))) {
     return IPC_FAIL(this, "BackgroundParent::Alloc failed");
   }
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvGetProcessAttributes(ContentParentId* aCpId,
-                                        bool* aIsForBrowser)
-{
-  *aCpId = mChildID;
-  *aIsForBrowser = mIsForBrowser;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline,
-                                             bool* aIsConnected,
-                                             int32_t* aCaptivePortalState,
-                                             bool* aIsLangRTL,
-                                             bool* aHaveBidiKeyboards,
-                                             InfallibleTArray<nsString>* dictionaries,
-                                             ClipboardCapabilities* clipboardCaps,
-                                             DomainPolicyClone* domainPolicy,
-                                             StructuredCloneData* aInitialData,
-                                             InfallibleTArray<FontFamilyListEntry>* fontFamilies,
-                                             OptionalURIParams* aUserContentCSSURL,
-                                             nsTArray<LookAndFeelInt>* aLookAndFeelIntCache)
-{
-  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
-                        static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
-                                              .ToMilliseconds()));
-
-  nsCOMPtr<nsIIOService> io(do_GetIOService());
-  MOZ_ASSERT(io, "No IO service?");
-  DebugOnly<nsresult> rv = io->GetOffline(aIsOffline);
-  MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
-
-  rv = io->GetConnectivity(aIsConnected);
-  MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?");
-
-  *aCaptivePortalState = nsICaptivePortalService::UNKNOWN;
-  nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CONTRACTID);
-  if (cps) {
-    cps->GetState(aCaptivePortalState);
-  }
-
-  nsIBidiKeyboard* bidi = nsContentUtils::GetBidiKeyboard();
-
-  *aIsLangRTL = false;
-  *aHaveBidiKeyboards = false;
-  if (bidi) {
-    bidi->IsLangRTL(aIsLangRTL);
-    bidi->GetHaveBidiKeyboards(aHaveBidiKeyboards);
-  }
-
-  nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
-  MOZ_ASSERT(spellChecker, "No spell checker?");
-
-  spellChecker->GetDictionaryList(dictionaries);
-
-  nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
-  MOZ_ASSERT(clipboard, "No clipboard?");
-
-  rv = clipboard->SupportsSelectionClipboard(&clipboardCaps->supportsSelectionClipboard());
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  rv = clipboard->SupportsFindClipboard(&clipboardCaps->supportsFindClipboard());
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  // Let's copy the domain policy from the parent to the child (if it's active).
-  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-  NS_ENSURE_TRUE(ssm, IPC_OK());
-  ssm->CloneDomainPolicy(domainPolicy);
-
-  if (nsFrameMessageManager* mm = nsFrameMessageManager::sParentProcessManager) {
-    AutoJSAPI jsapi;
-    if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) {
-      return IPC_FAIL_NO_REASON(this);
-    }
-    JS::RootedValue init(jsapi.cx());
-    nsresult result = mm->GetInitialProcessData(jsapi.cx(), &init);
-    if (NS_FAILED(result)) {
-      return IPC_FAIL_NO_REASON(this);
-    }
-
-    ErrorResult rv;
-    aInitialData->Write(jsapi.cx(), init, rv);
-    if (NS_WARN_IF(rv.Failed())) {
-      rv.SuppressException();
-      return IPC_FAIL_NO_REASON(this);
-    }
-  }
-
-  // This is only implemented (returns a non-empty list) by MacOSX at present.
-  gfxPlatform::GetPlatform()->GetSystemFontFamilyList(fontFamilies);
-  *aLookAndFeelIntCache = LookAndFeel::GetIntCache();
-
-  // Content processes have no permission to access profile directory, so we
-  // send the file URL instead.
-  StyleSheet* ucs = nsLayoutStylesheetCache::For(StyleBackendType::Gecko)->UserContentSheet();
-  if (ucs) {
-    SerializeURI(ucs->GetSheetURI(), *aUserContentCSSURL);
-  } else {
-    SerializeURI(nullptr, *aUserContentCSSURL);
-  }
-
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    nsAutoString cpId;
-    cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
-    obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
-  }
-
-  return IPC_OK();
-}
-
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
   MOZ_ASSERT(ManagedPJavaScriptParent().IsEmpty());
   return nsIContentParent::AllocPJavaScriptParent();
 }
 
 bool
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -741,34 +741,16 @@ private:
 
   /**
    * Get or create the corresponding content parent array to |aContentProcessType|.
    */
   static nsTArray<ContentParent*>& GetOrCreatePool(const nsAString& aContentProcessType);
 
   virtual mozilla::ipc::IPCResult RecvInitBackground(Endpoint<mozilla::ipc::PBackgroundParent>&& aEndpoint) override;
 
-  virtual mozilla::ipc::IPCResult RecvGetProcessAttributes(ContentParentId* aCpId,
-                                                           bool* aIsForBrowser) override;
-
-  virtual mozilla::ipc::IPCResult
-  RecvGetXPCOMProcessAttributes(bool* aIsOffline,
-                                bool* aIsConnected,
-                                int32_t* aCaptivePortalState,
-                                bool* aIsLangRTL,
-                                bool* aHaveBidiKeyboards,
-                                InfallibleTArray<nsString>* dictionaries,
-                                ClipboardCapabilities* clipboardCaps,
-                                DomainPolicyClone* domainPolicy,
-                                StructuredCloneData* initialData,
-                                InfallibleTArray<FontFamilyListEntry>* fontFamilies,
-                                OptionalURIParams* aUserContentSheetURL,
-                                nsTArray<LookAndFeelInt>* aLookAndFeelIntCache) override;
-
-
   mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
   mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
 
   virtual bool
   DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override;
 
   virtual bool
   DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override;
@@ -893,17 +875,16 @@ private:
 
   virtual PWebBrowserPersistDocumentParent*
   AllocPWebBrowserPersistDocumentParent(PBrowserParent* aBrowser,
                                         const uint64_t& aOuterWindowID) override;
 
   virtual bool
   DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
 
-  virtual mozilla::ipc::IPCResult RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs) override;
   virtual mozilla::ipc::IPCResult RecvGetGfxVars(InfallibleTArray<GfxVarUpdate>* aVars) override;
 
   virtual mozilla::ipc::IPCResult RecvReadFontList(InfallibleTArray<FontListEntry>* retValue) override;
 
   virtual mozilla::ipc::IPCResult RecvReadDataStorageArray(const nsString& aFilename,
                                                            InfallibleTArray<DataStorageItem>* aValues) override;
 
   virtual mozilla::ipc::IPCResult RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions) override;
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentPrefs.cpp
@@ -0,0 +1,276 @@
+/* -*- 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 "ContentPrefs.h"
+
+const char* mozilla::dom::ContentPrefs::gInitPrefs[] = {
+  "accessibility.monoaudio.enable",
+  "accessibility.mouse_focuses_formcontrol",
+  "accessibility.tabfocus_applies_to_xul",
+  "app.update.channel",
+  "browser.dom.window.dump.enabled",
+  "browser.sessionhistory.max_entries",
+  "browser.sessionhistory.max_total_viewers",
+  "content.cors.disable",
+  "content.cors.no_private_data",
+  "content.notify.backoffcount",
+  "content.notify.interval",
+  "content.notify.ontimer",
+  "content.sink.enable_perf_mode",
+  "content.sink.event_probe_rate",
+  "content.sink.initial_perf_time",
+  "content.sink.interactive_deflect_count",
+  "content.sink.interactive_parse_time",
+  "content.sink.interactive_time",
+  "content.sink.pending_event_mode",
+  "content.sink.perf_deflect_count",
+  "content.sink.perf_parse_time",
+  "device.storage.prompt.testing",
+  "device.storage.writable.name",
+  "dom.allow_XUL_XBL_for_file",
+  "dom.allow_cut_copy",
+  "dom.enable_frame_timing",
+  "dom.enable_performance",
+  "dom.enable_resource_timing",
+  "dom.event.handling-user-input-time-limit",
+  "dom.event.touch.coalescing.enabled",
+  "dom.forms.autocomplete.experimental",
+  "dom.ipc.processPriorityManager.backgroundGracePeriodMS",
+  "dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS",
+  "dom.max_chrome_script_run_time",
+  "dom.max_script_run_time",
+  "dom.performance.enable_notify_performance_timing",
+  "dom.performance.enable_user_timing_logging",
+  "dom.storage.testing",
+  "dom.url.encode_decode_hash",
+  "dom.url.getters_decode_hash",
+  "dom.use_watchdog",
+  "dom.vibrator.enabled",
+  "dom.vibrator.max_vibrate_list_len",
+  "dom.vibrator.max_vibrate_ms",
+  "focusmanager.testmode",
+  "font.size.inflation.disabledInMasterProcess",
+  "font.size.inflation.emPerLine",
+  "font.size.inflation.forceEnabled",
+  "font.size.inflation.lineThreshold",
+  "font.size.inflation.mappingIntercept",
+  "font.size.inflation.maxRatio",
+  "font.size.inflation.minTwips",
+  "full-screen-api.allow-trusted-requests-only",
+  "full-screen-api.enabled",
+  "full-screen-api.unprefix.enabled",
+  "gfx.font_rendering.opentype_svg.enabled",
+  "hangmonitor.timeout",
+  "html5.flushtimer.initialdelay",
+  "html5.flushtimer.subsequentdelay",
+  "html5.offmainthread",
+  "intl.charset.fallback.tld",
+  "intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
+  "javascript.enabled",
+  "javascript.options.asmjs",
+  "javascript.options.asyncstack",
+  "javascript.options.baselinejit",
+  "javascript.options.baselinejit.threshold",
+  "javascript.options.baselinejit.unsafe_eager_compilation",
+  "javascript.options.discardSystemSource",
+  "javascript.options.dump_stack_on_debuggee_would_run",
+  "javascript.options.gczeal",
+  "javascript.options.gczeal.frequency",
+  "javascript.options.ion",
+  "javascript.options.ion.offthread_compilation",
+  "javascript.options.ion.threshold",
+  "javascript.options.ion.unsafe_eager_compilation",
+  "javascript.options.jit.full_debug_checks",
+  "javascript.options.native_regexp",
+  "javascript.options.parallel_parsing",
+  "javascript.options.shared_memory",
+  "javascript.options.strict",
+  "javascript.options.strict.debug",
+  "javascript.options.throw_on_asmjs_validation_failure",
+  "javascript.options.throw_on_debuggee_would_run",
+  "javascript.options.wasm",
+  "javascript.options.wasm_baselinejit",
+  "javascript.options.werror",
+  "javascript.use_us_english_locale",
+  "jsloader.reuseGlobal",
+  "layout.css.all-shorthand.enabled",
+  "layout.css.background-blend-mode.enabled",
+  "layout.css.background-clip-text.enabled",
+  "layout.css.box-decoration-break.enabled",
+  "layout.css.color-adjust.enabled",
+  "layout.css.contain.enabled",
+  "layout.css.control-characters.visible",
+  "layout.css.display-flow-root.enabled",
+  "layout.css.expensive-style-struct-assertions.enabled",
+  "layout.css.float-logical-values.enabled",
+  "layout.css.font-variations.enabled",
+  "layout.css.grid.enabled",
+  "layout.css.image-orientation.enabled",
+  "layout.css.initial-letter.enabled",
+  "layout.css.isolation.enabled",
+  "layout.css.mix-blend-mode.enabled",
+  "layout.css.object-fit-and-position.enabled",
+  "layout.css.osx-font-smoothing.enabled",
+  "layout.css.overflow-clip-box.enabled",
+  "layout.css.prefixes.animations",
+  "layout.css.prefixes.border-image",
+  "layout.css.prefixes.box-sizing",
+  "layout.css.prefixes.device-pixel-ratio-webkit",
+  "layout.css.prefixes.font-features",
+  "layout.css.prefixes.gradients",
+  "layout.css.prefixes.transforms",
+  "layout.css.prefixes.transitions",
+  "layout.css.prefixes.webkit",
+  "layout.css.scope-pseudo.enabled",
+  "layout.css.scroll-behavior.property-enabled",
+  "layout.css.scroll-snap.enabled",
+  "layout.css.shape-outside.enabled",
+  "layout.css.text-align-unsafe-value.enabled",
+  "layout.css.text-combine-upright-digits.enabled",
+  "layout.css.text-combine-upright.enabled",
+  "layout.css.touch_action.enabled",
+  "layout.css.unprefixing-service.enabled",
+  "layout.css.unprefixing-service.globally-whitelisted",
+  "layout.css.unprefixing-service.include-test-domains",
+  "layout.css.variables.enabled",
+  "layout.css.visited_links_enabled",
+  "layout.idle_period.required_quiescent_frames",
+  "layout.idle_period.time_limit",
+  "layout.interruptible-reflow.enabled",
+  "mathml.disabled",
+  "media.apple.forcevda",
+  "media.clearkey.persistent-license.enabled",
+  "media.cubeb_latency_msg_frames",
+  "media.cubeb_latency_playback_ms",
+  "media.decoder-doctor.wmf-disabled-is-failure",
+  "media.decoder.fuzzing.dont-delay-inputexhausted",
+  "media.decoder.fuzzing.enabled",
+  "media.decoder.fuzzing.video-output-minimum-interval-ms",
+  "media.decoder.limit",
+  "media.decoder.recycle.enabled",
+  "media.dormant-on-pause-timeout-ms",
+  "media.eme.audio.blank",
+  "media.eme.enabled",
+  "media.eme.video.blank",
+  "media.ffmpeg.enabled",
+  "media.ffvpx.enabled",
+  "media.flac.enabled",
+  "media.forcestereo.enabled",
+  "media.gmp.async-shutdown-timeout",
+  "media.gmp.decoder.aac",
+  "media.gmp.decoder.enabled",
+  "media.gmp.decoder.h264",
+  "media.gmp.insecure.allow",
+  "media.gpu-process-decoder",
+  "media.libavcodec.allow-obsolete",
+  "media.num-decode-threads",
+  "media.ogg.enabled",
+  "media.ogg.flac.enabled",
+  "media.resampling.enabled",
+  "media.resampling.rate",
+  "media.ruin-av-sync.enabled",
+  "media.rust.test_mode",
+  "media.suspend-bkgnd-video.delay-ms",
+  "media.suspend-bkgnd-video.enabled",
+  "media.use-blank-decoder",
+  "media.video_stats.enabled",
+  "media.volume_scale",
+  "media.webspeech.recognition.enable",
+  "media.webspeech.recognition.force_enable",
+  "media.webspeech.synth.force_global_queue",
+  "media.webspeech.test.enable",
+  "media.webspeech.test.fake_fsm_events",
+  "media.webspeech.test.fake_recognition_service",
+  "media.wmf.allow-unsupported-resolutions",
+  "media.wmf.decoder.thread-count",
+  "media.wmf.enabled",
+  "media.wmf.skip-blacklist",
+  "media.wmf.vp9.enabled",
+  "memory.free_dirty_pages",
+  "memory.low_commit_space_threshold_mb",
+  "memory.low_memory_notification_interval_ms",
+  "memory.low_physical_memory_threshold_mb",
+  "memory.low_virtual_mem_threshold_mb",
+  "network.IDN.blacklist_chars",
+  "network.IDN.restriction_profile",
+  "network.IDN.use_whitelist",
+  "network.IDN_show_punycode",
+  "network.buffer.cache.count",
+  "network.buffer.cache.size",
+  "network.captive-portal-service.enabled",
+  "network.cookie.cookieBehavior",
+  "network.cookie.lifetimePolicy",
+  "network.dns.disablePrefetch",
+  "network.dns.disablePrefetchFromHTTPS",
+  "network.jar.block-remote-files",
+  "network.loadinfo.skip_type_assertion",
+  "network.notify.changed",
+  "network.offline-mirrors-connectivity",
+  "network.protocol-handler.external.jar",
+  "network.proxy.type",
+  "network.security.ports.banned",
+  "network.security.ports.banned.override",
+  "network.standard-url.enable-rust",
+  "network.standard-url.max-length",
+  "network.sts.max_time_for_events_between_two_polls",
+  "network.sts.max_time_for_pr_close_during_shutdown",
+  "network.tcp.keepalive.enabled",
+  "network.tcp.keepalive.idle_time",
+  "network.tcp.keepalive.probe_count",
+  "network.tcp.keepalive.retry_interval",
+  "network.tcp.sendbuffer",
+  "nglayout.debug.invalidation",
+  "privacy.donottrackheader.enabled",
+  "privacy.firstparty.isolate",
+  "privacy.firstparty.isolate.restrict_opener_access",
+  "privacy.resistFingerprinting",
+  "security.data_uri.inherit_security_context",
+  "security.fileuri.strict_origin_policy",
+  "security.sandbox.content.level",
+  "security.sandbox.content.tempDirSuffix",
+  "security.sandbox.logging.enabled",
+  "security.sandbox.mac.track.violations",
+  "security.sandbox.windows.log",
+  "security.sandbox.windows.log.stackTraceDepth",
+  "shutdown.watchdog.timeoutSecs",
+  "signed.applets.codebase_principal_support",
+  "svg.disabled",
+  "svg.display-lists.hit-testing.enabled",
+  "svg.display-lists.painting.enabled",
+  "svg.new-getBBox.enabled",
+  "svg.paint-order.enabled",
+  "svg.path-caching.enabled",
+  "svg.transform-box.enabled",
+  "toolkit.asyncshutdown.crash_timeout",
+  "toolkit.asyncshutdown.log",
+  "toolkit.osfile.log",
+  "toolkit.osfile.log.redirect",
+  "toolkit.telemetry.enabled",
+  "toolkit.telemetry.idleTimeout",
+  "toolkit.telemetry.initDelay",
+  "toolkit.telemetry.log.dump",
+  "toolkit.telemetry.log.level",
+  "toolkit.telemetry.minSubsessionLength",
+  "toolkit.telemetry.scheduler.idleTickInterval",
+  "toolkit.telemetry.scheduler.tickInterval",
+  "toolkit.telemetry.unified",
+  "ui.key.menuAccessKeyFocuses",
+  "ui.popup.disable_autohide",
+  "ui.use_activity_cursor",
+  "view_source.editor.external"};
+
+const char** mozilla::dom::ContentPrefs::GetContentPrefs(size_t* aCount)
+{
+  *aCount = ArrayLength(ContentPrefs::gInitPrefs);
+  return gInitPrefs;
+}
+
+const char*  mozilla::dom::ContentPrefs::GetContentPref(size_t aIndex)
+{
+  MOZ_ASSERT(aIndex < ArrayLength(ContentPrefs::gInitPrefs));
+  return gInitPrefs[aIndex];
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentPrefs.h
@@ -0,0 +1,25 @@
+/* -*- 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_dom_ContentPrefs_h
+#define mozilla_dom_ContentPrefs_h
+
+namespace mozilla {
+namespace dom {
+
+class ContentPrefs {
+public:
+  static const char** GetContentPrefs(size_t* aCount);
+  static const char* GetContentPref(size_t aIndex);
+
+private:
+    static const char* gInitPrefs[];
+};
+
+}
+}
+
+#endif
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -2,16 +2,17 @@
 /* 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/ipc/IOThreadChild.h"
 
 #include "ContentProcess.h"
+#include "ContentPrefs.h"
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 #include <stdlib.h>
 #endif
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/Preferences.h"
 #include "nsAppDirectoryServiceDefs.h"
@@ -98,60 +99,157 @@ SetUpSandboxEnvironment()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   SetTmpEnvironmentVariable(sandboxedContentTemp);
 }
 #endif
 
-void
-ContentProcess::SetAppDir(const nsACString& aPath)
+bool
+ContentProcess::Init(int aArgc, char* aArgv[])
 {
-  mXREEmbed.SetAppDir(aPath);
-}
+  // If passed in grab the application path for xpcom init
+  bool foundAppdir = false;
+  bool foundChildID = false;
+  bool foundIsForBrowser = false;
+  bool foundIntPrefs = false;
+  bool foundBoolPrefs = false;
+  bool foundStringPrefs = false;
+
+  uint64_t childID;
+  bool isForBrowser;
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-void
-ContentProcess::SetProfile(const nsACString& aProfile)
-{
-  bool flag;
-  nsresult rv =
-    XRE_GetFileFromPath(aProfile.BeginReading(), getter_AddRefs(mProfileDir));
-  if (NS_FAILED(rv) ||
-      NS_FAILED(mProfileDir->Exists(&flag)) || !flag) {
-    NS_WARNING("Invalid profile directory passed to content process.");
-    mProfileDir = nullptr;
-  }
-}
+  // If passed in grab the profile path for sandboxing
+  bool foundProfile = false;
+  nsCOMPtr<nsIFile> profileDir;
 #endif
 
-bool
-ContentProcess::Init()
-{
-    mContent.Init(IOThreadChild::message_loop(),
-                  ParentPid(),
-                  IOThreadChild::channel());
-    mXREEmbed.Start();
-    mContent.InitXPCOM();
-    mContent.InitGraphicsDeviceData();
+  InfallibleTArray<PrefSetting> prefsArray;
+  for (int idx = aArgc; idx > 0; idx--) {
+    if (!aArgv[idx]) {
+      continue;
+    }
 
+    if (!strcmp(aArgv[idx], "-appdir")) {
+      MOZ_ASSERT(!foundAppdir);
+      if (foundAppdir) {
+        continue;
+      }
+      nsCString appDir;
+      appDir.Assign(nsDependentCString(aArgv[idx+1]));
+      mXREEmbed.SetAppDir(appDir);
+      foundAppdir = true;
+    } else if (!strcmp(aArgv[idx], "-childID")) {
+      MOZ_ASSERT(!foundChildID);
+      if (foundChildID) {
+        continue;
+      }
+      if (idx + 1 < aArgc) {
+        childID = strtoull(aArgv[idx + 1], nullptr, 10);
+        foundChildID = true;
+      }
+    } else if (!strcmp(aArgv[idx], "-isForBrowser") || !strcmp(aArgv[idx], "-notForBrowser")) {
+      MOZ_ASSERT(!foundIsForBrowser);
+      if (foundIsForBrowser) {
+        continue;
+      }
+      isForBrowser = strcmp(aArgv[idx], "-notForBrowser");
+      foundIsForBrowser = true;
+    } else if (!strcmp(aArgv[idx], "-intPrefs")) {
+      SET_PREF_PHASE(BEGIN_INIT_PREFS);
+      char* str = aArgv[idx + 1];
+      while (*str) {
+        int32_t index = strtol(str, &str, 10);
+        str++;
+        MaybePrefValue value(PrefValue(static_cast<int32_t>(strtol(str, &str, 10))));
+        str++;
+        PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
+        prefsArray.AppendElement(pref);
+      }
+      SET_PREF_PHASE(END_INIT_PREFS);
+      foundIntPrefs = true;
+    } else if (!strcmp(aArgv[idx], "-boolPrefs")) {
+      SET_PREF_PHASE(BEGIN_INIT_PREFS);
+      char* str = aArgv[idx + 1];
+      while (*str) {
+        int32_t index = strtol(str, &str, 10);
+        str++;
+        MaybePrefValue value(PrefValue(!!strtol(str, &str, 10)));
+        str++;
+        PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
+        prefsArray.AppendElement(pref);
+      }
+      SET_PREF_PHASE(END_INIT_PREFS);
+      foundBoolPrefs = true;
+    } else if (!strcmp(aArgv[idx], "-stringPrefs")) {
+      SET_PREF_PHASE(BEGIN_INIT_PREFS);
+      char* str = aArgv[idx + 1];
+      while (*str) {
+        int32_t index = strtol(str, &str, 10);
+        str++;
+        int32_t length = strtol(str, &str, 10);
+        str++;
+        MaybePrefValue value(PrefValue(nsCString(str, length)));
+        PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
+        prefsArray.AppendElement(pref);
+        str += length + 1;
+      }
+      SET_PREF_PHASE(END_INIT_PREFS);
+      foundStringPrefs = true;
+    }
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+    else if (!strcmp(aArgv[idx], "-profile")) {
+      MOZ_ASSERT(!foundProfile);
+      if (foundProfile) {
+        continue;
+      }
+      bool flag;
+      nsresult rv = XRE_GetFileFromPath(aArgv[idx+1], getter_AddRefs(profileDir));
+      if (NS_FAILED(rv) ||
+          NS_FAILED(profileDir->Exists(&flag)) || !flag) {
+        NS_WARNING("Invalid profile directory passed to content process.");
+        profileDir = nullptr;
+      }
+      foundProfile = true;
+    }
+#endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */
+
+    bool allFound = foundAppdir && foundChildID && foundIsForBrowser && foundIntPrefs && foundBoolPrefs && foundStringPrefs;
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+    allFound &= foundProfile;
+#endif
+
+    if (allFound) {
+      break;
+    }
+  }
+  Preferences::SetInitPreferences(&prefsArray);
+  mContent.Init(IOThreadChild::message_loop(),
+                ParentPid(),
+                IOThreadChild::channel(),
+                childID,
+                isForBrowser);
+  mXREEmbed.Start();
 #if (defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
-    mContent.SetProfileDir(mProfileDir);
+  mContent.SetProfileDir(profileDir);
 #endif
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
-    SetUpSandboxEnvironment();
+  SetUpSandboxEnvironment();
 #endif
 
-    return true;
+  return true;
 }
 
 // Note: CleanUp() never gets called in non-debug builds because we exit early
 // in ContentChild::ActorDestroy().
 void
 ContentProcess::CleanUp()
 {
-    mXREEmbed.Stop();
+  mXREEmbed.Stop();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentProcess.h
+++ b/dom/ipc/ContentProcess.h
@@ -29,33 +29,23 @@ class ContentProcess : public mozilla::i
 public:
   explicit ContentProcess(ProcessId aParentPid)
     : ProcessChild(aParentPid)
   { }
 
   ~ContentProcess()
   { }
 
-  virtual bool Init() override;
+  virtual bool Init(int aArgc, char* aArgv[]) override;
   virtual void CleanUp() override;
 
-  void SetAppDir(const nsACString& aPath);
-
-#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-  void SetProfile(const nsACString& aProfile);
-#endif
-
 private:
   ContentChild mContent;
   mozilla::ipc::ScopedXREEmbed mXREEmbed;
 
-#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-  nsCOMPtr<nsIFile> mProfileDir;
-#endif
-
 #if defined(XP_WIN)
   // This object initializes and configures COM.
   mozilla::mscom::MainThreadRuntime mCOMRuntime;
 #endif
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentProcess);
 };
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -329,16 +329,32 @@ struct GMPAPITags
 
 struct GMPCapabilityData
 {
     nsCString name;
     nsCString version;
     GMPAPITags[] capabilities;
 };
 
+struct XPCOMInitData
+{
+    bool isOffline;
+    bool isConnected;
+    int32_t captivePortalState;
+    bool isLangRTL;
+    bool haveBidiKeyboards;
+    nsString[] dictionaries;
+    ClipboardCapabilities clipboardCaps;
+    DomainPolicyClone domainPolicy;
+    /* used on MacOSX only */
+    FontFamilyListEntry[] fontFamilies;
+    OptionalURIParams userContentSheetURL;
+    PrefSetting[] prefs;
+};
+
 struct GfxInfoFeatureStatus
 {
     int32_t feature;
     int32_t status;
     nsCString failureId;
 };
 
 /**
@@ -522,16 +538,18 @@ child:
      */
     async InitServiceWorkers(ServiceWorkerConfiguration aConfig);
 
     /**
      * Send BlobURLRegistrationData to child process.
      */
     async InitBlobURLs(BlobURLRegistrationData[] registrations);
 
+    async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit, StructuredCloneData initialData, LookAndFeelInt[] lookAndFeelIntCache);
+
     // Notify child that last-pb-context-exited notification was observed
     async LastPrivateDocShellDestroyed();
 
     async FilePathUpdate(nsString storageType, nsString storageName, nsString filepath,
                          nsCString reasons);
 
     async NotifyProcessPriorityChanged(ProcessPriority priority);
     async MinimizeMemoryUsage();
@@ -645,40 +663,16 @@ child:
     async BlobURLUnregistration(nsCString aURI);
 
 
     async GMPsChanged(GMPCapabilityData[] capabilities);
 
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
-    /**
-     * Tell the content process some attributes of itself.  This is
-     * among the first information queried by content processes after
-     * startup.  (The message is sync to allow the content process to
-     * control when it receives the information.)
-     *
-     * |id| is a unique ID among all subprocesses. When
-     * |isForBrowser|, we're loading <browser> or <xul:browser remote>.
-     *
-     * Keep the return values in sync with PBrowser()!
-     */
-    sync GetProcessAttributes()
-        returns (ContentParentId cpId, bool isForBrowser);
-    sync GetXPCOMProcessAttributes()
-        returns (bool isOffline, bool isConnected, int32_t captivePortalState,
-                 bool isLangRTL,
-                 bool haveBidiKeyboards, nsString[] dictionaries,
-                 ClipboardCapabilities clipboardCaps,
-                 DomainPolicyClone domainPolicy,
-                 StructuredCloneData initialData,
-                 FontFamilyListEntry[] fontFamilies /* used on MacOSX only */,
-                 OptionalURIParams userContentSheetURL,
-                 LookAndFeelInt[] lookAndFeelIntCache);
-
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId)
         returns (Endpoint<PContentBridgeParent> endpoint);
 
     async CreateGMPService();
@@ -812,17 +806,16 @@ parent:
     async StartVisitedQuery(URIParams uri);
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri, PBrowser windowContext);
     async ExtProtocolChannelConnectParent(uint32_t registrarId);
 
     // PrefService message
-    sync ReadPrefsArray() returns (PrefSetting[] prefs) verify;
     sync GetGfxVars() returns (GfxVarUpdate[] vars);
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
     sync ReadDataStorageArray(nsString aFilename)
       returns (DataStorageItem[] retValue);
 
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -114,16 +114,18 @@
 #include "FrameLayerBuilder.h"
 #include "VRManagerChild.h"
 #include "nsICommandParams.h"
 #include "nsISHistory.h"
 #include "nsQueryObject.h"
 #include "GroupedSHistory.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/DocGroup.h"
+#include "nsISupportsPrimitives.h"
+#include "mozilla/Telemetry.h"
 
 #ifdef NS_PRINTING
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
 #include "nsIPrintSettingsService.h"
 #include "nsIWebBrowserPrint.h"
 #endif
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -15,16 +15,17 @@ EXPORTS.mozilla.dom.ipc += [
     'StructuredCloneData.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'ContentBridgeChild.h',
     'ContentBridgeParent.h',
     'ContentChild.h',
     'ContentParent.h',
+    'ContentPrefs.h',
     'ContentProcess.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'CrashReporterChild.h',
     'CrashReporterParent.h',
     'FilePickerParent.h',
     'MemoryReportRequest.h',
     'nsIContentChild.h',
@@ -45,16 +46,17 @@ EXPORTS.mozilla += [
     'ProcessPriorityManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'ColorPickerParent.cpp',
     'ContentBridgeChild.cpp',
     'ContentBridgeParent.cpp',
     'ContentParent.cpp',
+    'ContentPrefs.cpp',
     'ContentProcess.cpp',
     'ContentProcessManager.cpp',
     'CrashReporterParent.cpp',
     'DatePickerParent.cpp',
     'FilePickerParent.cpp',
     'MemoryReportRequest.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
--- a/dom/media/gmp/GMPProcessChild.cpp
+++ b/dom/media/gmp/GMPProcessChild.cpp
@@ -20,17 +20,17 @@ GMPProcessChild::GMPProcessChild(Process
 {
 }
 
 GMPProcessChild::~GMPProcessChild()
 {
 }
 
 bool
-GMPProcessChild::Init()
+GMPProcessChild::Init(int aArgc, char* aArgv[])
 {
   nsAutoString pluginFilename;
 
 #if defined(OS_POSIX)
   // NB: need to be very careful in ensuring that the first arg
   // (after the binary name) here is indeed the plugin module path.
   // Keep in sync with dom/plugins/PluginModuleParent.
   std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv();
--- a/dom/media/gmp/GMPProcessChild.h
+++ b/dom/media/gmp/GMPProcessChild.h
@@ -17,17 +17,17 @@ class GMPLoader;
 class GMPProcessChild final : public mozilla::ipc::ProcessChild {
 protected:
   typedef mozilla::ipc::ProcessChild ProcessChild;
 
 public:
   explicit GMPProcessChild(ProcessId aParentPid);
   ~GMPProcessChild();
 
-  bool Init() override;
+  bool Init(int aArgc, char* aArgv[]) override;
   void CleanUp() override;
 
 private:
   GMPChild mPlugin;
   DISALLOW_COPY_AND_ASSIGN(GMPProcessChild);
 };
 
 } // namespace gmp
--- a/dom/notification/moz.build
+++ b/dom/notification/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
+
 EXTRA_COMPONENTS += [
     'NotificationStorage.js',
     'NotificationStorage.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'NotificationDB.jsm'
 ]
--- a/dom/plugins/ipc/PluginProcessChild.cpp
+++ b/dom/plugins/ipc/PluginProcessChild.cpp
@@ -35,17 +35,17 @@ using mozilla::ipc::IOThreadChild;
 #include <algorithm>
 #endif
 
 namespace mozilla {
 namespace plugins {
 
 
 bool
-PluginProcessChild::Init()
+PluginProcessChild::Init(int aArgc, char* aArgv[])
 {
     nsDebugImpl::SetMultiprocessMode("NPAPI");
 
 #if defined(XP_MACOSX)
     // Remove the trigger for "dyld interposing" that we added in
     // GeckoChildProcessHost::PerformAsyncLaunchInternal(), in the host
     // process just before we were launched.  Dyld interposing will still
     // happen in our process (the plugin child process).  But we don't want
--- a/dom/plugins/ipc/PluginProcessChild.h
+++ b/dom/plugins/ipc/PluginProcessChild.h
@@ -25,17 +25,17 @@ protected:
 public:
     explicit PluginProcessChild(ProcessId aParentPid)
       : ProcessChild(aParentPid), mPlugin(true)
     { }
 
     virtual ~PluginProcessChild()
     { }
 
-    virtual bool Init() override;
+    virtual bool Init(int aArgc, char* aArgv[]) override;
     virtual void CleanUp() override;
 
 protected:
     static PluginProcessChild* current() {
         return static_cast<PluginProcessChild*>(ProcessChild::current());
     }
 
 private:
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 EXPORTS.mozilla.dom += [
     'Storage.h',
     'StorageIPC.h',
 ]
 
 UNIFIED_SOURCES += [
     'Storage.cpp',
     'StorageCache.cpp',
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
 EXPORTS.mozilla.dom += [
     'U2F.h',
     'U2FAuthenticator.h',
 ]
 
 UNIFIED_SOURCES += [
     'U2F.cpp',
 ]
--- a/dom/vr/moz.build
+++ b/dom/vr/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 EXPORTS.mozilla.dom += [
     'VRDisplay.h',
     'VRDisplayEvent.h',
     'VREventObserver.h',
     ]
 
 UNIFIED_SOURCES = [
     'VRDisplay.cpp',
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
 EXPORTS.mozilla.dom += [
     'NSSU2FTokenRemote.h',
     'ScopedCredential.h',
     'ScopedCredentialInfo.h',
     'WebAuthentication.h',
     'WebAuthnAssertion.h',
     'WebAuthnAttestation.h',
     'WebAuthnRequest.h',
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -1,14 +1,377 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
+with Files("APZTestData.webidl"):
+    BUG_COMPONENT = ("Core", "Panning and Zooming")
+
+with Files("AccessibleNode.webidl"):
+    BUG_COMPONENT = ("Core", "Disability Access APIs")
+
+with Files("Addon*"):
+    BUG_COMPONENT = ("Toolkit", "Add-ons Manager")
+
+with Files("AnalyserNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Animat*"):
+    BUG_COMPONENT = ("Core", "DOM: Animation")
+
+with Files("*Audio*"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Autocomplete*"):
+    BUG_COMPONENT = ("Toolkit", "Autocomplete")
+
+with Files("BaseKeyframeTypes.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Animation")
+
+with Files("BatteryManager.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("BiquadFilterNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("BrowserElement*"):
+    BUG_COMPONENT = ("Core", "DOM")
+
+with Files("CSP*"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
+with Files("CSS*"):
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("Canvas*"):
+    BUG_COMPONENT = ("Core", "Canvas: 2D")
+
+with Files("Caret*"):
+    BUG_COMPONENT = ("Core", "Editor")
+
+with Files("Channel*"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Client*"):
+    BUG_COMPONENT = ("Core", "DOM: Service Workers")
+
+with Files("ClipboardEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("ConstantSourceNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("ContainerBoxObject.webidl"):
+    BUG_COMPONENT = ("Core", "DOM")
+
+with Files("ConvolverNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Coordinates.webidl"):
+    BUG_COMPONENT = ("Core", "Geolocation")
+
+with Files("Crypto.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
+with Files("Device*"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("Directory.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("DataTransfer*"):
+    BUG_COMPONENT = ("Core", "Drag and Drop")
+
+with Files("DragEvent.webidl"):
+    BUG_COMPONENT = ("Core", "Drag and Drop")
+
+with Files("DecoderDoctorNotification.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video: Playback")
+
+with Files("DelayNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("DynamicsCompressorNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("DesktopNotification.webidl"):
+    BUG_COMPONENT = ("Toolkit", "Notification and Alerts")
+
+with Files("FakePluginTagInit.webidl"):
+    BUG_COMPONENT = ("Core", "Plug-ins")
+
+with Files("FlyWeb*"):
+    BUG_COMPONENT = ("Core", "DOM: Flyweb")
+
+with Files("FocusEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("Font*"):
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("FormData.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("Geolocation.webidl"):
+    BUG_COMPONENT = ("Core", "Geolocation")
+
+with Files("GainNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Gamepad*"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("GeometryUtils.webidl"):
+    BUG_COMPONENT = ("Core", "Layout")
+
+with Files("GetUserMediaRequest.webidl"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("Grid.webidl"):
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("GroupedHistoryEvent.webidl"):
+    BUG_COMPONENT = ("Core", "Document Navigation")
+
+with Files("HTML*"):
+    BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("HashChangeEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("HeapSnapshot.webidl"):
+    BUG_COMPONENT = ("Firefox", "Developer Tools: Memory")
+
+with Files("HiddenPluginEvent.webidl"):
+    BUG_COMPONENT = ("Core", "Plug-ins")
+
+with Files("IDB*"):
+    BUG_COMPONENT = ("Core", "DOM: IndexedDB")
+
+with Files("IIRFilterNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Image*"):
+    BUG_COMPONENT = ("Core", "DOM")
+
+with Files("ImageCapture*"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("InputEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("InstallTrigger.webidl"):
+    BUG_COMPONENT = ("Toolkit", "Add-ons Manager")
+
+with Files("InspectorUtils.webidl"):
+    BUG_COMPONENT = ("Firefox", "Developer Tools: Inspector")
+
+with Files("KeyAlgorithm.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
+with Files("Key*Event*"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("KeyIdsInitData.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video: Playback")
+
+with Files("Keyframe*"):
+    BUG_COMPONENT = ("Core", "DOM: Animation")
+
+with Files("LocalMediaStream.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("MediaDevice*"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("Media*Source*"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("MediaStream*"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("MediaStreamAudio*"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("MediaEncryptedEvent.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("MediaKey*"):
+    BUG_COMPONENT = ("Core", "Audio/Video: Playback")
+
+with Files("Media*List*"):
+    BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("MediaStreamList.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("*Record*"):
+    BUG_COMPONENT = ("Core", "Audio/Video: Recording")
+
+with Files("Media*Track*"):
+    BUG_COMPONENT = ("Core", "WebRTC: Audio/Video")
+
+with Files("Mouse*"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("MozSelfSupport.webidl"):
+    BUG_COMPONENT = ("Firefox Health Report", "Client: Desktop")
+
+with Files("MozTimeManager.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("MutationEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("NativeOSFileInternals.webidl"):
+    BUG_COMPONENT = ("Toolkit", "OS.File")
+
+with Files("Net*"):
+    BUG_COMPONENT = ("Core", "Networking")
+
+with Files("OfflineAudio*"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("OffscreenCanvas.webidl"):
+    BUG_COMPONENT = ("Core", "Canvas 2D")
+
+with Files("OscillatorNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("PannerNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Peer*"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("PeriodicWave.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("PointerEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("PopStateEvent.webidl*"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("Position*"):
+    BUG_COMPONENT = ("Core", "Geolocation")
+
+with Files("ProfileTimelineMarker.webidl"):
+    BUG_COMPONENT = ("Firefox", "Developer Tools: Performance Tools (profiler/timeline)")
+
+with Files("ProgressEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("Push*"):
+    BUG_COMPONENT = ("Core", "DOM: Push Notifications")
+
+with Files("RTC*"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("SVG*"):
+    BUG_COMPONENT = ("Core", "SVG")
+
+with Files("ScriptProcessorNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+# TODO: SecureElement*, SettingChangeNotification
+# are FirefoxOS::*, leaving as Core::DOM
+
+with Files("Selection.webidl"):
+    BUG_COMPONENT = ("Core", "Selection")
+
+with Files("ServiceWorker*"):
+    BUG_COMPONENT = ("Core", "DOM: Service Workers")
+
+with Files("SimpleGestureEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("SocketCommon.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("SourceBuffer*"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("StereoPannerNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("Style*"):
+    BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("SubtleCrypto.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Security")
+
+with Files("TCP*"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("TextTrack*"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("ThreadSafeChromeUtils.webidl"):
+    BUG_COMPONENT = ("Firefox", "Developer Tools: Memory")
+
+with Files("TrackEvent.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("U2F.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("UDP*"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("UIEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("URL.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("UserProximityEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("VTT*"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("VRDisplay.webidl"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("Video*"):
+    BUG_COMPONENT = ("Core", "Audio/Video")
+
+with Files("WaveShaperNode.webidl"):
+    BUG_COMPONENT = ("Core", "Web Audio")
+
+with Files("WebAuthentication.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+with Files("WebGL*"):
+    BUG_COMPONENT = ("Core", "Canvas: WebGL")
+
+with Files("WebKitCSSMatrix.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: CSS Object Model")
+
+with Files("Webrtc*"):
+    BUG_COMPONENT = ("Core", "WebRTC")
+
+with Files("WheelEvent.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("WidevineCDMManifest.webidl"):
+    BUG_COMPONENT = ("Core", "Audio/Video: Playback")
+
+with Files("WindowOrWorkerGlobalScope.webidl"):
+    BUG_COMPONENT = ("Core", "DOM: Workers")
+
+with Files("Worker*"):
+    BUG_COMPONENT = ("Core", "DOM: Workers")
+
 GENERATED_WEBIDL_FILES = [
     'CSS2Properties.webidl',
 ]
 
 PREPROCESSED_WEBIDL_FILES = [
     'Navigator.webidl',
     'Node.webidl',
     'Window.webidl',
--- a/dom/xbl/moz.build
+++ b/dom/xbl/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "XBL")
+
 DIRS += ['builtin']
 
 EXPORTS += [
     'nsBindingManager.h',
     'nsXBLBinding.h',
     'nsXBLService.h',
 ]
 
--- a/dom/xhr/moz.build
+++ b/dom/xhr/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM")
+
 XPIDL_SOURCES += [
     'nsIXMLHttpRequest.idl',
 ]
 
 XPIDL_MODULE = 'dom_xhr'
 
 EXPORTS.mozilla.dom += [
     'XMLHttpRequest.h',
--- a/dom/xml/moz.build
+++ b/dom/xml/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "XML")
+
 DIRS += ['resources']
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 EXPORTS += [
     'nsIXMLContentSink.h',
 ]
 
--- a/dom/xslt/moz.build
+++ b/dom/xslt/moz.build
@@ -1,14 +1,17 @@
 # -*- 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/.
 
+with Files("**"):
+    BUG_COMPONENT = ("Core", "XSLT")
+
 XPIDL_SOURCES += [
     'nsIXSLTProcessor.idl',
     'nsIXSLTProcessorPrivate.idl',
     'txIEXSLTRegExFunctions.idl',
     'txIFunctionEvaluationContext.idl',
     'txINodeSet.idl',
     'txIXPathObject.idl',
 ]
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -813,28 +813,16 @@ nsPermissionManager::Init()
   // ignore failure here, since it's non-fatal (we can run fine without
   // persistent storage - e.g. if there's no profile).
   // XXX should we tell the user about this?
   InitDB(false);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsPermissionManager::RefreshPermission() {
-  NS_ENSURE_TRUE(IsChildProcess(), NS_ERROR_FAILURE);
-
-  nsresult rv = RemoveAllFromMemory();
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = FetchPermissions();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
 nsresult
 nsPermissionManager::OpenDatabase(nsIFile* aPermissionsFile)
 {
   nsresult rv;
   nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   if (!storage) {
     return NS_ERROR_UNEXPECTED;
   }
--- a/gfx/angle/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
+++ b/gfx/angle/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
@@ -687,16 +687,17 @@ gl::ErrorOrResult<Buffer11::BufferStorag
     }
 
     // resize buffer
     if (newStorage->getSize() < mSize)
     {
         ANGLE_TRY(newStorage->resize(mSize, true));
     }
 
+    mIdleness[usage] = 0;
     ANGLE_TRY(updateBufferStorage(newStorage, 0, mSize));
     ANGLE_TRY(markBufferUsage(usage));
 
     return newStorage;
 }
 
 Buffer11::BufferStorage *Buffer11::allocateStorage(BufferUsage usage)
 {
--- a/gfx/ipc/GPUProcessImpl.cpp
+++ b/gfx/ipc/GPUProcessImpl.cpp
@@ -17,17 +17,17 @@ GPUProcessImpl::GPUProcessImpl(ProcessId
 {
 }
 
 GPUProcessImpl::~GPUProcessImpl()
 {
 }
 
 bool
-GPUProcessImpl::Init()
+GPUProcessImpl::Init(int aArgc, char* aArgv[])
 {
   return mGPU.Init(ParentPid(),
                    IOThreadChild::message_loop(),
                    IOThreadChild::channel());
 }
 
 void
 GPUProcessImpl::CleanUp()
--- a/gfx/ipc/GPUProcessImpl.h
+++ b/gfx/ipc/GPUProcessImpl.h
@@ -19,17 +19,17 @@ namespace gfx {
 // This class owns the subprocess instance of a PGPU - which in this case,
 // is a GPUParent. It is instantiated as a singleton in XRE_InitChildProcess.
 class GPUProcessImpl final : public ipc::ProcessChild
 {
 public:
   explicit GPUProcessImpl(ProcessId aParentPid);
   ~GPUProcessImpl();
 
-  bool Init() override;
+  bool Init(int aArgc, char* aArgv[]) override;
   void CleanUp() override;
 
 private:
   DISALLOW_COPY_AND_ASSIGN(GPUProcessImpl);
 
   GPUParent mGPU;
 
 #if defined(XP_WIN)
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -54,48 +54,16 @@ using base::Thread;
 using base::ProcessId;
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 typedef std::vector<CompositableOperation> OpVector;
 typedef nsTArray<OpDestroy> OpDestroyVector;
 
-namespace {
-class ImageBridgeThread : public Thread {
-public:
-
-  ImageBridgeThread() : Thread("ImageBridgeChild") {
-  }
-
-protected:
-
-  MOZ_IS_CLASS_INIT
-  void Init() {
-#ifdef MOZ_GECKO_PROFILER
-    mPseudoStackHack = profiler_get_pseudo_stack();
-#endif
-  }
-
-  void CleanUp() {
-#ifdef MOZ_GECKO_PROFILER
-    mPseudoStackHack = nullptr;
-#endif
-  }
-
-private:
-
-#ifdef MOZ_GECKO_PROFILER
-  // This is needed to avoid a spurious leak report.  There's no other
-  // use for it.  See bug 1239504 and bug 1215265.
-  MOZ_INIT_OUTSIDE_CTOR PseudoStack* mPseudoStackHack;
-#endif
-};
-}
-
 struct CompositableTransaction
 {
   CompositableTransaction()
   : mSwapRequired(false)
   , mFinished(true)
   {}
   ~CompositableTransaction()
   {
@@ -615,17 +583,17 @@ ImageBridgeChild::SendImageBridgeThreadI
 bool
 ImageBridgeChild::InitForContent(Endpoint<PImageBridgeChild>&& aEndpoint)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   gfxPlatform::GetPlatform();
 
   if (!sImageBridgeChildThread) {
-    sImageBridgeChildThread = new ImageBridgeThread();
+    sImageBridgeChildThread = new Thread("ImageBridgeChild");
     if (!sImageBridgeChildThread->Start()) {
       return false;
     }
   }
 
   RefPtr<ImageBridgeChild> child = new ImageBridgeChild();
 
   RefPtr<Runnable> runnable = NewRunnableMethod<Endpoint<PImageBridgeChild>&&>(
@@ -742,17 +710,17 @@ ImageBridgeChild::WillShutdown()
 void
 ImageBridgeChild::InitSameProcess()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
 
   MOZ_ASSERT(!sImageBridgeChildSingleton);
   MOZ_ASSERT(!sImageBridgeChildThread);
 
-  sImageBridgeChildThread = new ImageBridgeThread();
+  sImageBridgeChildThread = new Thread("ImageBridgeChild");
   if (!sImageBridgeChildThread->IsRunning()) {
     sImageBridgeChildThread->Start();
   }
 
   RefPtr<ImageBridgeChild> child = new ImageBridgeChild();
   RefPtr<ImageBridgeParent> parent = ImageBridgeParent::CreateSameProcess();
 
   RefPtr<Runnable> runnable = WrapRunnable(
@@ -770,17 +738,17 @@ ImageBridgeChild::InitSameProcess()
 
 /* static */ void
 ImageBridgeChild::InitWithGPUProcess(Endpoint<PImageBridgeChild>&& aEndpoint)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sImageBridgeChildSingleton);
   MOZ_ASSERT(!sImageBridgeChildThread);
 
-  sImageBridgeChildThread = new ImageBridgeThread();
+  sImageBridgeChildThread = new Thread("ImageBridgeChild");
   if (!sImageBridgeChildThread->IsRunning()) {
     sImageBridgeChildThread->Start();
   }
 
   RefPtr<ImageBridgeChild> child = new ImageBridgeChild();
 
   MessageLoop* loop = child->GetMessageLoop();
   loop->PostTask(NewRunnableMethod<Endpoint<PImageBridgeChild>&&>(
--- a/intl/locale/nsWin32Locale.h
+++ b/intl/locale/nsWin32Locale.h
@@ -5,28 +5,19 @@
 #ifndef nsWin32Locale_h__
 #define nsWin32Locale_h__
 
 #include "nscore.h"
 #include "nsString.h"
 #include <windows.h>
 
 
-class nsWin32Locale {
+class nsWin32Locale final {
 public: 
   static nsresult    GetPlatformLocale(const nsAString& locale, LCID* winLCID); 
   static void        GetXPLocale(LCID winLCID, nsAString& locale);
 
 private:
   // Static class - Don't allow instantiation.
   nsWin32Locale(void) {}
-
-  typedef LCID (WINAPI*LocaleNameToLCIDPtr)(LPCWSTR lpName, DWORD dwFlags);
-  typedef int (WINAPI*LCIDToLocaleNamePtr)(LCID Locale, LPWSTR lpName,
-                                           int cchName, DWORD dwFlags);
-
-  static LocaleNameToLCIDPtr localeNameToLCID;
-  static LCIDToLocaleNamePtr lcidToLocaleName;
-
-  static void initFunctionPointers ();
 };
 
 #endif
--- a/intl/locale/windows/nsWin32Locale.cpp
+++ b/intl/locale/windows/nsWin32Locale.cpp
@@ -5,132 +5,33 @@
 
 #include "mozilla/ArrayUtils.h"
 
 #include "nscore.h"
 #include "nsString.h"
 #include "nsXPCOMStrings.h"
 #include "nsReadableUtils.h"
 #include "nsWin32Locale.h"
-#include "prprf.h"
 #include <windows.h>
-#include "nsCRT.h"
 
 using namespace mozilla;
 
 struct iso_pair 
 {
 	const char*	iso_code;
 	DWORD       win_code;
 };
 
 struct iso_map
 {
 	const char* iso_code;
 	DWORD       win_code;
 	iso_pair    sublang_list[20];
 };
 
-nsWin32Locale::LocaleNameToLCIDPtr nsWin32Locale::localeNameToLCID = nullptr;
-nsWin32Locale::LCIDToLocaleNamePtr nsWin32Locale::lcidToLocaleName = nullptr;
-
-// Older versions of VC++ and Win32 SDK  and mingw don't have 
-// macros for languages and sublanguages recently added to Win32. 
-// see http://www.tug.org/ftp/tex/texinfo/intl/localename.c
-
-#ifndef LANG_URDU
-#define LANG_URDU                           0x20
-#endif
-#ifndef LANG_ARMENIAN
-#define LANG_ARMENIAN                       0x2b
-#endif
-#ifndef LANG_AZERI
-#define LANG_AZERI                          0x2c
-#endif
-#ifndef LANG_MACEDONIAN
-#define LANG_MACEDONIAN                     0x2f
-#endif
-#ifndef LANG_GEORGIAN
-#define LANG_GEORGIAN                       0x37
-#endif
-#ifndef LANG_HINDI
-#define LANG_HINDI                          0x39
-#endif
-#ifndef LANG_MALAY
-#define LANG_MALAY                          0x3e
-#endif
-#ifndef LANG_KAZAK
-#define LANG_KAZAK                          0x3f
-#endif
-#ifndef LANG_KYRGYZ
-#define LANG_KYRGYZ                         0x40
-#endif
-#ifndef LANG_SWAHILI
-#define LANG_SWAHILI                        0x41
-#endif
-#ifndef LANG_UZBEK
-#define LANG_UZBEK                          0x43
-#endif
-#ifndef LANG_TATAR
-#define LANG_TATAR                          0x44
-#endif
-#ifndef LANG_PUNJABI
-#define LANG_PUNJABI                        0x46
-#endif
-#ifndef LANG_GUJARAT
-#define LANG_GUJARAT                        0x47
-#endif
-#ifndef LANG_TAMIL
-#define LANG_TAMIL                          0x49
-#endif
-#ifndef LANG_TELUGU
-#define LANG_TELUGU                         0x4a
-#endif
-#ifndef LANG_KANNADA
-#define LANG_KANNADA                        0x4b
-#endif
-#ifndef LANG_MARATHI
-#define LANG_MARATHI                        0x4e
-#endif
-#ifndef LANG_SANSKRIT
-#define LANG_SANSKRIT                       0x4f
-#endif
-#ifndef LANG_MONGOLIAN
-#define LANG_MONGOLIAN                      0x50
-#endif
-#ifndef LANG_GALICIAN
-#define LANG_GALICIAN                       0x56
-#endif
-#ifndef LANG_KONKANI
-#define LANG_KONKANI                        0x57
-#endif
-#ifndef LANG_DIVEHI
-#define LANG_DIVEHI                         0x65
-#endif
-
-#ifndef SUBLANG_MALAY_MALAYSIA
-#define SUBLANG_MALAY_MALAYSIA              0x01
-#endif
-#ifndef SUBLANG_MALAY_BRUNEI_DARUSSALAM
-#define SUBLANG_MALAY_BRUNEI_DARUSSALAM     0x02
-#endif
-#ifndef SUBLANG_CHINESE_MACAU
-#define SUBLANG_CHINESE_MACAU               0x05
-#endif
-#ifndef SUBLANG_FRENCH_MONACO
-#define SUBLANG_FRENCH_MONACO               0x06
-#endif
-#ifndef SUBLANG_ENGLISH_ZIMBABWE
-#define SUBLANG_ENGLISH_ZIMBABWE            0x0c
-#endif
-#ifndef SUBLANG_ENGLISH_PHILIPPINES
-#define SUBLANG_ENGLISH_PHILIPPINES         0x0d
-#endif
-
-
 //
 // This list is used to map between ISO language
 // References : 
 // http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html
 // http://www.loc.gov/standards/iso639-2/
 // http://www.ethnologue.com/
 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/nls_19ir.asp
 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/nls_61df.asp
@@ -569,51 +470,30 @@ iso_pair dbg_list[] =
 	{"zh",	LANG_CHINESE},
 	{"",0}
 };
 #endif
 
 #define CROATIAN_ISO_CODE "hr"
 #define SERBIAN_ISO_CODE "sr"
 
-void
-nsWin32Locale::initFunctionPointers(void)
-{
-  static bool sInitialized = false;
-  // We use the Vista and above functions if we have them
-  if (!sInitialized) {
-    HMODULE kernelDLL = GetModuleHandleW(L"kernel32.dll");
-    if (kernelDLL) {
-      localeNameToLCID = (LocaleNameToLCIDPtr) GetProcAddress(kernelDLL, "LocaleNameToLCID");
-      lcidToLocaleName = (LCIDToLocaleNamePtr) GetProcAddress(kernelDLL, "LCIDToLocaleName");
-    }
-    sInitialized = true;
-  }
-}
-
 //
 // the mapping routines are a first approximation to get us going on
 // the tier-1 languages.  we are making an assumption that we can map
 // language and country codes separately on Windows, which isn't true
 //
 nsresult
 nsWin32Locale::GetPlatformLocale(const nsAString& locale, LCID* winLCID)
 {
-  initFunctionPointers ();
-
-  if (localeNameToLCID) {
-    nsAutoString locale_autostr(locale);
-    LCID lcid = localeNameToLCID(locale_autostr.get(), 0);
-    // The function returning 0 means that the locale name couldn't be matched,
-    // so we fallback to the old function
-    if (lcid != 0)
-    {
-      *winLCID = lcid;
-      return NS_OK;
-    }
+  LCID lcid = LocaleNameToLCID(PromiseFlatString(locale).get(), 0);
+  // The function returning 0 means that the locale name couldn't be matched,
+  // so we fallback to the old function
+  if (lcid != 0) {
+    *winLCID = lcid;
+    return NS_OK;
   }
 
   char    locale_string[9] = {'\0','\0','\0','\0','\0','\0','\0','\0','\0'};
   char*   language_code;
   char*   country_code;
   size_t  i, j;
 
   // parse the locale
@@ -639,36 +519,29 @@ nsWin32Locale::GetPlatformLocale(const n
       *winLCID = MAKELCID(MAKELANGID(iso_list[i].win_code,SUBLANG_DEFAULT),SORT_DEFAULT);
       return NS_OK;
     }
   }
     
   return NS_ERROR_FAILURE;
 }
 
-#ifndef LOCALE_NAME_MAX_LENGTH
-#define LOCALE_NAME_MAX_LENGTH 85
-#endif
-
 void
 nsWin32Locale::GetXPLocale(LCID winLCID, nsAString& locale)
 {
-  initFunctionPointers ();
-
-  if (lcidToLocaleName)
-  {
-    WCHAR ret_locale[LOCALE_NAME_MAX_LENGTH];
-    int rv = lcidToLocaleName(winLCID, ret_locale, LOCALE_NAME_MAX_LENGTH, 0);
-    // rv 0 means that the function failed to match up the LCID, so we fallback
-    // to the old function
-    if (rv != 0)
-    {
-      locale.Assign(ret_locale);
-      return;
-    }
+  locale.SetCapacity(LOCALE_NAME_MAX_LENGTH);
+  int length = LCIDToLocaleName(winLCID,
+                                reinterpret_cast<LPWSTR>(locale.BeginWriting()),
+                                LOCALE_NAME_MAX_LENGTH, 0);
+  // 0 length means that the function failed to match up the LCID,
+  // so we fallback to the old function
+  if (length) {
+    // length contains null terminate.
+    locale.SetLength(length - 1);
+    return;
   }
 
   DWORD    lang_id, sublang_id;
   size_t   i, j;
 
   lang_id = PRIMARYLANGID(LANGIDFROMLCID(winLCID));
   sublang_id = SUBLANGID(LANGIDFROMLCID(winLCID));
 
--- a/ipc/glue/CrashReporterHost.cpp
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -104,28 +104,31 @@ public:
     if (mProcessType == nsICrashService::PROCESS_TYPE_CONTENT) {
       CrashReporter::RunMinidumpAnalyzer(mChildDumpID);
     }
 
     // Make a copy of these so we can copy them into the runnable lambda
     int32_t processType = mProcessType;
     int32_t crashType = mCrashType;
     nsString childDumpID(mChildDumpID);
+    nsCOMPtr<nsIAsyncShutdownBlocker> self(this);
 
-    NS_DispatchToMainThread(NS_NewRunnableFunction([=] () -> void {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([
+      self, processType, crashType, childDumpID
+    ] {
       nsCOMPtr<nsICrashService> crashService =
         do_GetService("@mozilla.org/crashservice;1");
       if (crashService) {
         crashService->AddCrash(processType, crashType, childDumpID);
       }
 
       nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
 
       if (barrier) {
-        barrier->RemoveBlocker(this);
+        barrier->RemoveBlocker(self);
       }
     }));
 
     return NS_OK;
   }
 
   NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aBarrierClient) override
   {
--- a/ipc/glue/ProcessChild.h
+++ b/ipc/glue/ProcessChild.h
@@ -22,17 +22,17 @@ namespace ipc {
 class ProcessChild : public ChildProcess {
 protected:
   typedef base::ProcessId ProcessId;
 
 public:
   explicit ProcessChild(ProcessId aParentPid);
   virtual ~ProcessChild();
 
-  virtual bool Init() = 0;
+  virtual bool Init(int aArgc, char* aArgv[]) = 0;
   virtual void CleanUp()
   { }
 
   static MessageLoop* message_loop() {
     return gProcessChild->mUILoop;
   }
 
     /**
--- a/js/src/jit/ProcessExecutableMemory.cpp
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -254,24 +254,33 @@ DecommitPages(void* addr, size_t bytes)
 {
     if (!VirtualFree(addr, bytes, MEM_DECOMMIT))
         MOZ_CRASH("DecommitPages failed");
 }
 #else // !XP_WIN
 static void*
 ComputeRandomAllocationAddress()
 {
-    // Return a random, page-aligned address. x64 CPUs have a 48-bit address
-    // space and on some platforms the OS will give us access to 47 bits, so
-    // to be safe we right shift by 18 to leave 46 bits.
+    uint64_t rand = js::GenerateRandomSeed();
 
-    uint64_t rand = js::GenerateRandomSeed();
 # ifdef HAVE_64BIT_BUILD
+    // x64 CPUs have a 48-bit address space and on some platforms the OS will
+    // give us access to 47 bits, so to be safe we right shift by 18 to leave
+    // 46 bits.
     rand >>= 18;
+# else
+    // On 32-bit, right shift by 34 to leave 30 bits, range [0, 1GiB). Then add
+    // 512MiB to get range [512MiB, 1.5GiB), or [0x20000000, 0x60000000). This
+    // is based on V8 comments in platform-posix.cc saying this range is
+    // relatively unpopulated across a variety of kernels.
+    rand >>= 34;
+    rand += 512 * 1024 * 1024;
 # endif
+
+    // Ensure page alignment.
     uintptr_t mask = ~uintptr_t(gc::SystemPageSize() - 1);
     return (void*) uintptr_t(rand & mask);
 }
 
 static void*
 ReserveProcessExecutableMemory(size_t bytes)
 {
     // Note that randomAddr is just a hint: if the address is not available
--- a/layout/reftests/w3c-css/submitted/ui3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/ui3/reftest.list
@@ -1,10 +1,10 @@
 == box-sizing-border-box-001.xht box-sizing-border-box-001-ref.xht
 == box-sizing-border-box-002.xht box-sizing-border-box-002-ref.xht
 == box-sizing-border-box-003.xht box-sizing-border-box-003-ref.xht
 == box-sizing-border-box-004.xht box-sizing-border-box-004-ref.xht
 == box-sizing-content-box-001.xht box-sizing-content-box-001-ref.xht
 == box-sizing-content-box-002.xht box-sizing-content-box-002-ref.xht
 == box-sizing-content-box-003.xht box-sizing-content-box-003-ref.xht
-random-if(Android) fuzzy-if(skiaContent,15,50) fuzzy-if(OSX,255,1575) == box-sizing-replaced-001.xht box-sizing-replaced-001-ref.xht # bug 982547, Bug 1295466
+skip-if(Android||gtkWidget) fuzzy-if(skiaContent,15,50) fuzzy-if(OSX,255,1575) == box-sizing-replaced-001.xht box-sizing-replaced-001-ref.xht # bug 982547, Bug 1295466, Bug 1321707
 fuzzy-if(Android,27,874) fuzzy-if(gtkWidget,14,29) == box-sizing-replaced-002.xht box-sizing-replaced-002-ref.xht # Bug 1128229, Bug 1313772
 fuzzy-if(Android,27,925) fuzzy-if(gtkWidget,14,43) == box-sizing-replaced-003.xht box-sizing-replaced-003-ref.xht # Bug 1128229
--- a/layout/xul/nsXULTooltipListener.cpp
+++ b/layout/xul/nsXULTooltipListener.cpp
@@ -723,17 +723,18 @@ nsXULTooltipListener::sTooltipCallback(n
 #ifdef MOZ_XUL
 nsresult
 nsXULTooltipListener::GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject)
 {
   *aBoxObject = nullptr;
 
   nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode);
   if (mIsSourceTree && sourceNode) {
-    RefPtr<nsXULElement> xulEl = nsXULElement::FromContent(sourceNode);
+    RefPtr<nsXULElement> xulEl =
+      nsXULElement::FromContentOrNull(sourceNode->GetParent());
     if (xulEl) {
       IgnoredErrorResult ignored;
       nsCOMPtr<nsIBoxObject> bx = xulEl->GetBoxObject(ignored);
       nsCOMPtr<nsITreeBoxObject> obx(do_QueryInterface(bx));
       if (obx) {
         *aBoxObject = obx;
         NS_ADDREF(*aBoxObject);
         return NS_OK;
--- a/mobile/android/components/extensions/ext-tabs.js
+++ b/mobile/android/components/extensions/ext-tabs.js
@@ -53,19 +53,19 @@ extensions.on("page-shutdown", (type, co
     if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
       // Only close extension tabs.
       // This check prevents about:addons from closing when it contains a
       // WebExtension as an embedded inline options page.
       return;
     }
     let {BrowserApp} = context.xulBrowser.ownerGlobal;
     if (BrowserApp) {
-      let tab = BrowserApp.getTabForBrowser(context.xulBrowser);
-      if (tab) {
-        BrowserApp.closeTab(tab);
+      let nativeTab = BrowserApp.getTabForBrowser(context.xulBrowser);
+      if (nativeTab) {
+        BrowserApp.closeTab(nativeTab);
       }
     }
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 function getBrowserWindow(window) {
   return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
@@ -84,49 +84,50 @@ let tabListener = {
 
       this.tabReadyInitialized = true;
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
       let {BrowserApp} = browser.ownerGlobal;
-      let tab = BrowserApp.getTabForBrowser(browser);
+      let nativeTab = BrowserApp.getTabForBrowser(browser);
 
       // Now we are certain that the first page in the tab was loaded.
-      this.initializingTabs.delete(tab);
+      this.initializingTabs.delete(nativeTab);
 
       // browser.innerWindowID is now set, resolve the promises if any.
-      let deferred = this.tabReadyPromises.get(tab);
+      let deferred = this.tabReadyPromises.get(nativeTab);
       if (deferred) {
-        deferred.resolve(tab);
-        this.tabReadyPromises.delete(tab);
+        deferred.resolve(nativeTab);
+        this.tabReadyPromises.delete(nativeTab);
       }
     }
   },
 
   /**
    * Returns a promise that resolves when the tab is ready.
    * Tabs created via the `tabs.create` method are "ready" once the location
    * changes to the requested URL. Other tabs are assumed to be ready once their
    * inner window ID is known.
    *
-   * @param {XULElement} tab The <tab> element.
+   * @param {NativeTab} nativeTab The native tab object.
    * @returns {Promise} Resolves with the given tab once ready.
    */
-  awaitTabReady(tab) {
-    let deferred = this.tabReadyPromises.get(tab);
+  awaitTabReady(nativeTab) {
+    let deferred = this.tabReadyPromises.get(nativeTab);
     if (!deferred) {
       deferred = PromiseUtils.defer();
-      if (!this.initializingTabs.has(tab) && (tab.browser.innerWindowID ||
-                                              tab.browser.currentURI.spec === "about:blank")) {
-        deferred.resolve(tab);
+      if (!this.initializingTabs.has(nativeTab) &&
+          (nativeTab.browser.innerWindowID ||
+           nativeTab.browser.currentURI.spec === "about:blank")) {
+        deferred.resolve(nativeTab);
       } else {
         this.initTabReady();
-        this.tabReadyPromises.set(tab, deferred);
+        this.tabReadyPromises.set(nativeTab, deferred);
       }
     }
     return deferred.promise;
   },
 };
 
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
@@ -143,32 +144,32 @@ extensions.registerSchemaAPI("tabs", "ad
   async function promiseTabWhenReady(tabId) {
     let tab;
     if (tabId !== null) {
       tab = tabManager.get(tabId);
     } else {
       tab = tabManager.getWrapper(tabTracker.activeTab);
     }
 
-    await tabListener.awaitTabReady(tab.tab);
+    await tabListener.awaitTabReady(tab.nativeTab);
 
     return tab;
   }
 
   let self = {
     tabs: {
       onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
         let tab = tabManager.get(data.id);
 
         fire.async({tabId: tab.id, windowId: tab.windowId});
       }).api(),
 
       onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
-          fire.async(tabManager.convert(event.tab));
+          fire.async(tabManager.convert(event.nativeTab));
         };
 
         tabTracker.on("tab-created", listener);
         return () => {
           tabTracker.off("tab-created", listener);
         };
       }).api(),
 
@@ -228,58 +229,58 @@ extensions.registerSchemaAPI("tabs", "ad
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
             fire.async(tab.id, changeInfo, tab.convert());
           }
         };
 
         let listener = event => {
           let needed = [];
-          let tab;
+          let nativeTab;
           switch (event.type) {
             case "DOMTitleChanged": {
               let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
 
-              tab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
+              nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
               needed.push("title");
               break;
             }
 
             case "DOMAudioPlaybackStarted":
             case "DOMAudioPlaybackStopped": {
               let {BrowserApp} = event.target.ownerGlobal;
-              tab = BrowserApp.getTabForBrowser(event.originalTarget);
+              nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
               needed.push("audible");
               break;
             }
           }
 
-          if (!tab) {
+          if (!nativeTab) {
             return;
           }
 
-          tab = tabManager.getWrapper(tab);
+          let tab = tabManager.getWrapper(nativeTab);
           let changeInfo = {};
           for (let prop of needed) {
             changeInfo[prop] = tab[prop];
           }
 
           fireForTab(tab, changeInfo);
         };
 
         let statusListener = ({browser, status, url}) => {
           let {BrowserApp} = browser.ownerGlobal;
-          let tab = BrowserApp.getTabForBrowser(browser);
-          if (tab) {
+          let nativeTab = BrowserApp.getTabForBrowser(browser);
+          if (nativeTab) {
             let changed = {status};
             if (url) {
               changed.url = url;
             }
 
-            fireForTab(tabManager.wrapTab(tab), changed);
+            fireForTab(tabManager.wrapTab(nativeTab), changed);
           }
         };
 
         windowTracker.addListener("status", statusListener);
         windowTracker.addListener("DOMTitleChanged", listener);
         return () => {
           windowTracker.removeListener("status", statusListener);
           windowTracker.removeListener("DOMTitleChanged", listener);
@@ -314,71 +315,71 @@ extensions.registerSchemaAPI("tabs", "ad
           options.tabIndex = createProperties.index;
         }
 
         // Make sure things like about:blank and data: URIs never inherit,
         // and instead always get a NullPrincipal.
         options.disallowInheritPrincipal = true;
 
         tabListener.initTabReady();
-        let tab = BrowserApp.addTab(url, options);
+        let nativeTab = BrowserApp.addTab(url, options);
 
         if (createProperties.url) {
-          tabListener.initializingTabs.add(tab);
+          tabListener.initializingTabs.add(nativeTab);
         }
 
-        return tabManager.convert(tab);
+        return tabManager.convert(nativeTab);
       },
 
       async remove(tabs) {
         if (!Array.isArray(tabs)) {
           tabs = [tabs];
         }
 
         for (let tabId of tabs) {
-          let tab = tabTracker.getTab(tabId);
-          tab.browser.ownerGlobal.BrowserApp.closeTab(tab);
+          let nativeTab = tabTracker.getTab(tabId);
+          nativeTab.browser.ownerGlobal.BrowserApp.closeTab(nativeTab);
         }
       },
 
       async update(tabId, updateProperties) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
-        let {BrowserApp} = tab.browser.ownerGlobal;
+        let {BrowserApp} = nativeTab.browser.ownerGlobal;
 
         if (updateProperties.url !== null) {
           let url = context.uri.resolve(updateProperties.url);
 
           if (!context.checkLoadURL(url, {dontReportErrors: true})) {
             return Promise.reject({message: `Illegal URL: ${url}`});
           }
 
-          tab.browser.loadURI(url);
+          nativeTab.browser.loadURI(url);
         }
 
         if (updateProperties.active !== null) {
           if (updateProperties.active) {
-            BrowserApp.selectTab(tab);
+            BrowserApp.selectTab(nativeTab);
           } else {
             // Not sure what to do here? Which tab should we select?
           }
         }
         // FIXME: highlighted/selected, muted, pinned, openerTabId
 
-        return tabManager.convert(tab);
+        return tabManager.convert(nativeTab);
       },
 
       async reload(tabId, reloadProperties) {
-        let tab = getTabOrActive(tabId);
+        let nativeTab = getTabOrActive(tabId);
 
         let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
         if (reloadProperties && reloadProperties.bypassCache) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
-        tab.browser.reloadWithFlags(flags);
+        nativeTab.browser.reloadWithFlags(flags);
       },
 
       async get(tabId) {
         return tabManager.get(tabId).convert();
       },
 
       async getCurrent() {
         if (context.tabId) {
@@ -395,51 +396,25 @@ extensions.registerSchemaAPI("tabs", "ad
           queryInfo = Object.assign({}, queryInfo);
           queryInfo.url = new MatchPattern(queryInfo.url);
         }
 
         return Array.from(tabManager.query(queryInfo, context),
                           tab => tab.convert());
       },
 
-      captureVisibleTab(windowId, options) {
-        if (!extension.hasPermission("<all_urls>")) {
-          return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
-        }
-
+      async captureVisibleTab(windowId, options) {
         let window = windowId == null ?
           windowTracker.topWindow :
           windowTracker.getWindow(windowId, context);
 
-        let tab = window.BrowserApp.selectedTab;
-        return tabListener.awaitTabReady(tab).then(() => {
-          let {browser} = tab;
-          let recipient = {
-            innerWindowID: browser.innerWindowID,
-          };
+        let tab = tabManager.wrapTab(window.BrowserApp.selectedTab);
+        await tabListener.awaitTabReady(tab.nativeTab);
 
-          if (!options) {
-            options = {};
-          }
-          if (options.format == null) {
-            options.format = "png";
-          }
-          if (options.quality == null) {
-            options.quality = 92;
-          }
-
-          let message = {
-            options,
-            width: browser.clientWidth,
-            height: browser.clientHeight,
-          };
-
-          return context.sendMessage(browser.messageManager, "Extension:Capture",
-                                     message, {recipient});
-        });
+        return tab.capture(context, options);
       },
 
       async executeScript(tabId, details) {
         let tab = await promiseTabWhenReady(tabId);
 
         return tab.executeScript(context, details);
       },
 
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -23,27 +23,56 @@ global.GlobalEventDispatcher = EventDisp
 
 const BrowserStatusFilter = Components.Constructor(
   "@mozilla.org/appshell/component/browser-status-filter;1", "nsIWebProgress",
   "addProgressListener");
 
 let tabTracker;
 let windowTracker;
 
+/**
+ * A nsIWebProgressListener for a specific XUL browser, which delegates the
+ * events that it receives to a tab progress listener, and prepends the browser
+ * to their arguments list.
+ *
+ * @param {XULElement} browser
+ *        A XUL browser element.
+ * @param {object} listener
+ *        A tab progress listener object.
+ * @param {integer} flags
+ *        The web progress notification flags with which to filter events.
+ */
 class BrowserProgressListener {
   constructor(browser, listener, flags) {
     this.listener = listener;
     this.browser = browser;
     this.filter = new BrowserStatusFilter(this, flags);
+    this.browser.addProgressListener(this.filter, flags);
   }
 
+  /**
+   * Destroy the listener, and perform any necessary cleanup.
+   */
   destroy() {
+    this.browser.removeProgressListener(this.filter);
     this.filter.removeProgressListener(this);
   }
 
+  /**
+   * Calls the appropriate listener in the wrapped tab progress listener, with
+   * the wrapped XUL browser object as its first argument, and the additional
+   * arguments in `args`.
+   *
+   * @param {string} method
+   *        The name of the nsIWebProgressListener method which is being
+   *        delegated.
+   * @param {*} args
+   *        The arguments to pass to the delegated listener.
+   * @private
+   */
   delegate(method, ...args) {
     if (this.listener[method]) {
       this.listener[method](this.browser, ...args);
     }
   }
 
   onLocationChange(webProgress, request, locationURI, flags) {
     this.delegate("onLocationChange", webProgress, request, locationURI, flags);
@@ -52,67 +81,98 @@ class BrowserProgressListener {
     this.delegate("onStateChange", webProgress, request, stateFlags, status);
   }
 
   onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {}
   onStatusChange(webProgress, request, status, message) {}
   onSecurityChange(webProgress, request, state) {}
 }
 
+/**
+ * Handles wrapping a tab progress listener in browser-specific
+ * BrowserProgressListener instances, an attaching them to each tab in a given
+ * browser window.
+ *
+ * @param {DOMWindow} window
+ *        The browser window to which to attach the listeners.
+ * @param {object} listener
+ *        The tab progress listener to wrap.
+ */
 class ProgressListenerWrapper {
   constructor(window, listener) {
     this.window = window;
     this.listener = listener;
     this.listeners = new WeakMap();
 
     this.flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
                  Ci.nsIWebProgress.NOTIFY_LOCATION;
 
-    for (let tab of this.window.BrowserApp.tabs) {
-      this.addBrowserProgressListener(tab.browser);
+    for (let nativeTab of this.window.BrowserApp.tabs) {
+      this.addBrowserProgressListener(nativeTab.browser);
     }
 
     this.window.BrowserApp.deck.addEventListener("TabOpen", this);
   }
 
+  /**
+   * Destroy the wrapper, removing any remaining listeners it has added.
+   */
   destroy() {
     this.window.BrowserApp.deck.removeEventListener("TabOpen", this);
 
-    for (let tab of this.window.BrowserApp.tabs) {
-      this.removeProgressListener(tab.browser);
+    for (let nativeTab of this.window.BrowserApp.tabs) {
+      this.removeProgressListener(nativeTab.browser);
     }
   }
 
+  /**
+   * Adds a progress listener to the given XUL browser element.
+   *
+   * @param {XULElement} browser
+   *        The XUL browser to add the listener to.
+   * @private
+   */
   addBrowserProgressListener(browser) {
     this.removeProgressListener(browser);
 
     let listener = new BrowserProgressListener(browser, this.listener, this.flags);
     this.listeners.set(browser, listener);
-
-    browser.addProgressListener(listener.filter, this.flags);
   }
 
+  /**
+   * Removes a progress listener from the given XUL browser element.
+   *
+   * @param {XULElement} browser
+   *        The XUL browser to remove the listener from.
+   * @private
+   */
   removeProgressListener(browser) {
     let listener = this.listeners.get(browser);
     if (listener) {
-      browser.removeProgressListener(listener.filter);
       listener.destroy();
       this.listeners.delete(browser);
     }
   }
 
+  /**
+   * Handles tab open events, and adds the necessary progress listeners to the
+   * new tabs.
+   *
+   * @param {Event} event
+   *        The DOM event to handle.
+   * @private
+   */
   handleEvent(event) {
     if (event.type === "TabOpen") {
       this.addBrowserProgressListener(event.originalTarget);
     }
   }
 
 }
 
-
 class WindowTracker extends WindowTrackerBase {
   constructor(...args) {
     super(...args);
 
     this.progressListeners = new DefaultWeakMap(() => new WeakMap());
   }
 
   addProgressListener(window, listener) {
@@ -127,16 +187,34 @@ class WindowTracker extends WindowTracke
     let listeners = this.progressListeners.get(window);
     let wrapper = listeners.get(listener);
     if (wrapper) {
       wrapper.destroy();
       listeners.delete(listener);
     }
   }
 }
+
+/**
+ * An event manager API provider which listens for an event in the Android
+ * global EventDispatcher, and calls the given listener function whenever an event
+ * is received. That listener function receives a `fire` object, which it can
+ * use to dispatch events to the extension, and an object detailing the
+ * EventDispatcher event that was received.
+ *
+ * @param {BaseContext} context
+ *        The extension context which the event manager belongs to.
+ * @param {string} name
+ *        The API name of the event manager, e.g.,"runtime.onMessage".
+ * @param {string} event
+ *        The name of the EventDispatcher event to listen for.
+ * @param {function} listener
+ *        The listener function to call when an EventDispatcher event is
+ *        recieved.
+ */
 global.GlobalEventManager = class extends SingletonEventManager {
   constructor(context, name, event, listener) {
     super(context, name, fire => {
       let listener2 = {
         onEvent(event, data, callback) {
           listener(fire, data);
         },
       };
@@ -144,16 +222,31 @@ global.GlobalEventManager = class extend
       GlobalEventDispatcher.registerListener(listener2, [event]);
       return () => {
         GlobalEventDispatcher.unregisterListener(listener2, [event]);
       };
     });
   }
 };
 
+/**
+ * An event manager API provider which listens for a DOM event in any browser
+ * window, and calls the given listener function whenever an event is received.
+ * That listener function receives a `fire` object, which it can use to dispatch
+ * events to the extension, and a DOM event object.
+ *
+ * @param {BaseContext} context
+ *        The extension context which the event manager belongs to.
+ * @param {string} name
+ *        The API name of the event manager, e.g.,"runtime.onMessage".
+ * @param {string} event
+ *        The name of the DOM event to listen for.
+ * @param {function} listener
+ *        The listener function to call when a DOM event is received.
+ */
 global.WindowEventManager = class extends SingletonEventManager {
   constructor(context, name, event, listener) {
     super(context, name, fire => {
       let listener2 = listener.bind(null, fire);
 
       windowTracker.addListener(event, listener2);
       return () => {
         windowTracker.removeListener(event, listener2);
@@ -168,75 +261,100 @@ class TabTracker extends TabTrackerBase 
       return;
     }
     this.initialized = true;
 
     windowTracker.addListener("TabClose", this);
     windowTracker.addListener("TabOpen", this);
   }
 
-  getId(tab) {
-    return tab.id;
+  getId(nativeTab) {
+    return nativeTab.id;
   }
 
   getTab(id, default_ = undefined) {
     let win = windowTracker.topWindow;
     if (win) {
-      let tab = win.BrowserApp.getTabForId(id);
-      if (tab) {
-        return tab;
+      let nativeTab = win.BrowserApp.getTabForId(id);
+      if (nativeTab) {
+        return nativeTab;
       }
     }
     if (default_ !== undefined) {
       return default_;
     }
     throw new ExtensionError(`Invalid tab ID: ${id}`);
   }
 
+  /**
+   * Handles tab open and close events, and emits the appropriate internal
+   * events for them.
+   *
+   * @param {Event} event
+   *        A DOM event to handle.
+   * @private
+   */
   handleEvent(event) {
     const {BrowserApp} = event.target.ownerGlobal;
-    let tab = BrowserApp.getTabForBrowser(event.target);
+    let nativeTab = BrowserApp.getTabForBrowser(event.target);
 
     switch (event.type) {
       case "TabOpen":
-        this.emitCreated(tab);
+        this.emitCreated(nativeTab);
         break;
 
       case "TabClose":
-        this.emitRemoved(tab, false);
+        this.emitRemoved(nativeTab, false);
         break;
     }
   }
 
-  emitCreated(tab) {
-    this.emit("tab-created", {tab});
+  /**
+   * Emits a "tab-created" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being created.
+   * @private
+   */
+  emitCreated(nativeTab) {
+    this.emit("tab-created", {nativeTab});
   }
 
-  emitRemoved(tab, isWindowClosing) {
-    let windowId = windowTracker.getId(tab.browser.ownerGlobal);
-    let tabId = this.getId(tab);
+  /**
+   * Emits a "tab-removed" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being removed.
+   * @param {boolean} isWindowClosing
+   *        True if the tab is being removed because the browser window is
+   *        closing.
+   * @private
+   */
+  emitRemoved(nativeTab, isWindowClosing) {
+    let windowId = windowTracker.getId(nativeTab.browser.ownerGlobal);
+    let tabId = this.getId(nativeTab);
 
     Services.tm.mainThread.dispatch(() => {
-      this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+      this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing});
     }, Ci.nsIThread.DISPATCH_NORMAL);
   }
 
   getBrowserData(browser) {
     let result = {
       tabId: -1,
       windowId: -1,
     };
 
     let {BrowserApp} = browser.ownerGlobal;
     if (BrowserApp) {
       result.windowId = windowTracker.getId(browser.ownerGlobal);
 
-      let tab = BrowserApp.getTabForBrowser(browser);
-      if (tab) {
-        result.tabId = this.getId(tab);
+      let nativeTab = BrowserApp.getTabForBrowser(browser);
+      if (nativeTab) {
+        result.tabId = this.getId(nativeTab);
       }
     }
 
     return result;
   }
 
   get activeTab() {
     let window = windowTracker.topWindow;
@@ -253,53 +371,53 @@ tabTracker = new TabTracker();
 Object.assign(global, {tabTracker, windowTracker});
 
 class Tab extends TabBase {
   get _favIconUrl() {
     return undefined;
   }
 
   get audible() {
-    return this.tab.playingAudio;
+    return this.nativeTab.playingAudio;
   }
 
   get browser() {
-    return this.tab.browser;
+    return this.nativeTab.browser;
   }
 
   get cookieStoreId() {
-    return getCookieStoreIdForTab(this, this.tab);
+    return getCookieStoreIdForTab(this, this.nativeTab);
   }
 
   get height() {
     return this.browser.clientHeight;
   }
 
   get incognito() {
     return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
   }
 
   get index() {
-    return this.window.BrowserApp.tabs.indexOf(this.tab);
+    return this.window.BrowserApp.tabs.indexOf(this.nativeTab);
   }
 
   get mutedInfo() {
     return {muted: false};
   }
 
   get pinned() {
     return false;
   }
 
   get active() {
-    return this.tab.getActive();
+    return this.nativeTab.getActive();
   }
 
   get selected() {
-    return this.tab.getActive();
+    return this.nativeTab.getActive();
   }
 
   get status() {
     if (this.browser.webProgress.isLoadingDocument) {
       return "loading";
     }
     return "complete";
   }
@@ -352,44 +470,44 @@ class Window extends WindowBase {
 
   get state() {
     return "fullscreen";
   }
 
   * getTabs() {
     let {tabManager} = this.extension;
 
-    for (let tab of this.window.BrowserApp.tabs) {
-      yield tabManager.getWrapper(tab);
+    for (let nativeTab of this.window.BrowserApp.tabs) {
+      yield tabManager.getWrapper(nativeTab);
     }
   }
 }
 
 Object.assign(global, {Tab, Window});
 
 class TabManager extends TabManagerBase {
   get(tabId, default_ = undefined) {
-    let tab = tabTracker.getTab(tabId, default_);
+    let nativeTab = tabTracker.getTab(tabId, default_);
 
-    if (tab) {
-      return this.getWrapper(tab);
+    if (nativeTab) {
+      return this.getWrapper(nativeTab);
     }
     return default_;
   }
 
-  addActiveTabPermission(tab = tabTracker.activeTab) {
-    return super.addActiveTabPermission(tab);
+  addActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.addActiveTabPermission(nativeTab);
   }
 
-  revokeActiveTabPermission(tab = tabTracker.activeTab) {
-    return super.revokeActiveTabPermission(tab);
+  revokeActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.revokeActiveTabPermission(nativeTab);
   }
 
-  wrapTab(tab) {
-    return new Tab(this.extension, tab, tab.id);
+  wrapTab(nativeTab) {
+    return new Tab(this.extension, nativeTab, nativeTab.id);
   }
 }
 
 class WindowManager extends WindowManagerBase {
   get(windowId, context) {
     let window = windowTracker.getWindow(windowId, context);
 
     return this.getWrapper(window);
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -1,16 +1,16 @@
 /* -*- 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/MemoryReporting.h"
-#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PContent.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 #include "nsXULAppAPI.h"
 
@@ -541,36 +541,42 @@ NS_INTERFACE_MAP_BEGIN(Preferences)
     NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 
 /*
  * nsIPrefService Implementation
  */
 
+InfallibleTArray<Preferences::PrefSetting>* gInitPrefs;
+
+/*static*/
+void
+Preferences::SetInitPreferences(nsTArray<PrefSetting>* aPrefs) {
+  gInitPrefs = new InfallibleTArray<PrefSetting>(mozilla::Move(*aPrefs));
+}
+
 nsresult
 Preferences::Init()
 {
   nsresult rv;
 
   PREF_SetDirtyCallback(&DirtyCallback);
   PREF_Init();
 
   rv = pref_InitInitialObjects();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  using mozilla::dom::ContentChild;
   if (XRE_IsContentProcess()) {
-    InfallibleTArray<PrefSetting> prefs;
-    ContentChild::GetSingleton()->SendReadPrefsArray(&prefs);
-
-    // Store the array
-    for (uint32_t i = 0; i < prefs.Length(); ++i) {
-      pref_SetPref(prefs[i]);
+    MOZ_ASSERT(gInitPrefs);
+    for (unsigned int i = 0; i < gInitPrefs->Length(); i++) {
+      Preferences::SetPreference(gInitPrefs->ElementAt(i));
     }
+    delete gInitPrefs;
+    gInitPrefs = nullptr;
     return NS_OK;
   }
 
   nsXPIDLCString lockFileName;
   /*
    * The following is a small hack which will allow us to only load the library
    * which supports the netscape.cfg file if the preference is defined. We
    * test for the existence of the pref, set in the all.js (mozilla) or
@@ -775,16 +781,24 @@ Preferences::GetPreferences(InfallibleTA
       continue;
     }
 
     dom::PrefSetting *pref = aPrefs->AppendElement();
     pref_GetPrefFromEntry(entry, pref);
   }
 }
 
+#ifdef DEBUG
+void
+Preferences::SetInitPhase(pref_initPhase phase)
+{
+  pref_SetInitPhase(phase);
+}
+#endif
+
 NS_IMETHODIMP
 Preferences::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval)
 {
   nsresult rv;
 
   if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
     // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think)
     RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, false);
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -23,16 +23,30 @@ class nsIFile;
 class nsAdoptingString;
 class nsAdoptingCString;
 
 #ifndef have_PrefChangedFunc_typedef
 typedef void (*PrefChangedFunc)(const char *, void *);
 #define have_PrefChangedFunc_typedef
 #endif
 
+#ifdef DEBUG
+enum pref_initPhase {
+  START,
+  BEGIN_INIT_PREFS,
+  END_INIT_PREFS,
+  BEGIN_ALL_PREFS,
+  END_ALL_PREFS
+};
+
+#define SET_PREF_PHASE(p) Preferences::SetInitPhase(p)
+#else
+#define SET_PREF_PHASE(p) do { } while (0)
+#endif
+
 namespace mozilla {
 
 namespace dom {
 class PrefSetting;
 } // namespace dom
 
 class Preferences final : public nsIPrefService,
                           public nsIObserver,
@@ -361,16 +375,22 @@ public:
    */
   static int32_t GetDefaultType(const char* aPref);
 
   // Used to synchronise preferences between chrome and content processes.
   static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs);
   static void GetPreference(PrefSetting* aPref);
   static void SetPreference(const PrefSetting& aPref);
 
+  static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs);
+
+#ifdef DEBUG
+  static void SetInitPhase(pref_initPhase phase);
+#endif
+
   static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf);
 
   static void DirtyCallback();
 
 protected:
   virtual ~Preferences();
 
   nsresult NotifyServiceObservers(const char *aSubject);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4528,23 +4528,17 @@ pref("webgl.disable-DOM-blit-uploads", f
 pref("webgl.allow-fb-invalidation", false);
 pref("webgl.webgl2-compat-mode", false);
 
 pref("webgl.max-perf-warnings", 0);
 pref("webgl.max-acceptable-fb-status-invals", 0);
 
 pref("webgl.enable-webgl2", true);
 
-#ifdef RELEASE_OR_BETA
-// Keep this disabled on Release and Beta for now. (see bug 1171228)
-pref("webgl.enable-debug-renderer-info", false);
-#else
 pref("webgl.enable-debug-renderer-info", true);
-#endif
-
 pref("webgl.renderer-string-override", "");
 pref("webgl.vendor-string-override", "");
 
 #ifdef XP_WIN
 pref("webgl.angle.try-d3d11", true);
 pref("webgl.angle.force-d3d11", false);
 pref("webgl.angle.force-warp", false);
 pref("webgl.dxgl.enabled", true);
--- a/modules/libpref/prefapi.cpp
+++ b/modules/libpref/prefapi.cpp
@@ -24,16 +24,17 @@
 
 #include "plstr.h"
 #include "PLDHashTable.h"
 #include "plbase64.h"
 #include "mozilla/Logging.h"
 #include "prprf.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/PContent.h"
+#include "mozilla/dom/ContentPrefs.h"
 #include "nsQuickSort.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "prlink.h"
 
 using namespace mozilla;
 
 static void
@@ -731,23 +732,58 @@ static PrefTypeFlags pref_SetValue(PrefV
         MOZ_ASSERT(newValue.stringVal);
         existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr;
     }
     else {
         *existingValue = newValue;
     }
     return flags;
 }
+#ifdef DEBUG
+static pref_initPhase gPhase = START;
+
+void
+pref_SetInitPhase(pref_initPhase phase)
+{
+    gPhase = phase;
+}
+
+struct StringComparator
+{
+    const char* mKey;
+    explicit StringComparator(const char* aKey) : mKey(aKey) {}
+    int operator()(const char* string) const {
+        return strcmp(mKey, string);
+    }
+};
+
+bool
+inInitArray(const char* key)
+{
+    size_t prefsLen;
+    size_t found;
+    const char** list = mozilla::dom::ContentPrefs::GetContentPrefs(&prefsLen);
+    return BinarySearchIf(list, 0, prefsLen,
+                          StringComparator(key), &found);
+}
+#endif
 
 PrefHashEntry* pref_HashTableLookup(const char *key)
 {
 #ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread());
 #endif
-
+    MOZ_ASSERT((!XRE_IsContentProcess() || gPhase != START),
+               "pref access before commandline prefs set");
+    /* If you're hitting this assertion, you've added a pref access to start up.
+     * Consider moving it later or add it to the whitelist in ContentPrefs.cpp
+     * and get review from a DOM peer
+     */
+    MOZ_ASSERT((!XRE_IsContentProcess() || gPhase > END_INIT_PREFS || inInitArray(key)),
+               "accessing non-init pref before the rest of the prefs are sent");
     return static_cast<PrefHashEntry*>(gHashTable->Search(key));
 }
 
 nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags)
 {
 #ifndef MOZ_B2G
     MOZ_ASSERT(NS_IsMainThread());
 #endif
--- a/modules/libpref/prefapi_private_data.h
+++ b/modules/libpref/prefapi_private_data.h
@@ -5,31 +5,37 @@
 
 /* Data shared between prefapi.c and nsPref.cpp */
 
 #ifndef prefapi_private_data_h
 #define prefapi_private_data_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/UniquePtr.h"
+#include "Preferences.h"
 
 extern PLDHashTable* gHashTable;
 
 namespace mozilla {
 namespace dom {
 class PrefSetting;
 } // namespace dom
 } // namespace mozilla
 
 mozilla::UniquePtr<char*[]>
 pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount);
 
 nsresult
 pref_SetPref(const mozilla::dom::PrefSetting& aPref);
 
+#ifdef DEBUG
+void
+pref_SetInitPhase(pref_initPhase phase);
+#endif
+
 int pref_CompareStrings(const void *v1, const void *v2, void* unused);
 PrefHashEntry* pref_HashTableLookup(const char *key);
 
 bool
 pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry);
 
 void pref_GetPrefFromEntry(PrefHashEntry *aHashEntry,
                            mozilla::dom::PrefSetting* aPref);
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -35,23 +35,18 @@ nsresult
 LoadInfoArgsToLoadInfo(const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
                        nsILoadInfo** outLoadInfo);
 } // namespace ipc
 
 namespace net {
 
 /**
  * Class that provides an nsILoadInfo implementation.
- *
- * Note that there is no reason why this class should be MOZ_EXPORT, but
- * Thunderbird relies on some insane hacks which require this, so we'll leave it
- * as is for now, but hopefully we'll be able to remove the MOZ_EXPORT keyword
- * from this class at some point.  See bug 1149127 for the discussion.
  */
-class MOZ_EXPORT LoadInfo final : public nsILoadInfo
+class LoadInfo final : public nsILoadInfo
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSILOADINFO
 
   // aLoadingPrincipal MUST NOT BE NULL.
   LoadInfo(nsIPrincipal* aLoadingPrincipal,
            nsIPrincipal* aTriggeringPrincipal,
--- a/netwerk/base/nsIPermissionManager.idl
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -268,21 +268,15 @@ interface nsIPermissionManager : nsISupp
    *                           Jan 1 1970 0:00:00), if it is currently
    *                           EXPIRE_TIME.
    */
   void updateExpireTime(in nsIPrincipal principal,
                         in string type,
                         in boolean exactHost,
                         in uint64_t sessionExpireTime,
                         in uint64_t persistentExpireTime);
-
-  /**
-   * Remove all current permission settings and get permission settings from
-   * chrome process.
-   */
-  void refreshPermission();
 };
 
 %{ C++
 #define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
 
 #define PERM_CHANGE_NOTIFICATION "perm-changed"
 %}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/webvtt/rendering/cues-with-video/processing-model/selectors/__dir__.ini
@@ -0,0 +1,1 @@
+disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1332564
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -22,76 +22,417 @@ Cu.import("resource://gre/modules/Extens
 
 const {
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
 } = ExtensionUtils;
 
+/**
+ * The platform-specific type of native tab objects, which are wrapped by
+ * TabBase instances.
+ *
+ * @typedef {Object|XULElement} NativeTab
+ */
+
+/**
+ * @typedef {Object} MutedInfo
+ * @property {boolean} muted
+ *        True if the tab is currently muted, false otherwise.
+ * @property {string} [reason]
+ *        The reason the tab is muted. Either "user", if the tab was muted by a
+ *        user, or "extension", if it was muted by an extension.
+ * @property {string} [extensionId]
+ *        If the tab was muted by an extension, contains the internal ID of that
+ *        extension.
+ */
+
+/**
+ * A platform-independent base class for extension-specific wrappers around
+ * native tab objects.
+ *
+ * @param {Extension} extension
+ *        The extension object for which this wrapper is being created. Used to
+ *        determine permissions for access to certain properties and
+ *        functionality.
+ * @param {NativeTab} nativeTab
+ *        The native tab object which is being wrapped. The type of this object
+ *        varies by platform.
+ * @param {integer} id
+ *        The numeric ID of this tab object. This ID should be the same for
+ *        every extension, and for the lifetime of the tab.
+ */
 class TabBase {
-  constructor(extension, tab, id) {
+  constructor(extension, nativeTab, id) {
     this.extension = extension;
     this.tabManager = extension.tabManager;
     this.id = id;
-    this.tab = tab;
-    this.activeTabWindowId = null;
+    this.nativeTab = nativeTab;
+    this.activeTabWindowID = null;
+  }
+
+  /**
+   * Sends a message, via the given context, to the ExtensionContent running in
+   * this tab. The tab's current innerWindowID is automatically added to the
+   * recipient filter for the message, and is used to ensure that the message is
+   * not processed if the content process navigates to a different content page
+   * before the message is received.
+   *
+   * @param {BaseContext} context
+   *        The context through which to send the message.
+   * @param {string} messageName
+   *        The name of the messge to send.
+   * @param {object} [data = {}]
+   *        Arbitrary, structured-clonable message data to send.
+   * @param {object} [options]
+   *        An options object, as accepted by BaseContext.sendMessage.
+   *
+   * @returns {Promise}
+   */
+  sendMessage(context, messageName, data = {}, options = null) {
+    let {browser, innerWindowID} = this;
+
+    options = Object.assign({}, options);
+    options.recipient = Object.assign({innerWindowID}, options.recipient);
+
+    return context.sendMessage(browser.messageManager, messageName,
+                               data, options);
   }
 
-  get innerWindowId() {
-    return this.browser.innerWindowId;
+  /**
+   * Capture the visible area of this tab, and return the result as a data: URL.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to perform the capture.
+   * @param {Object} [options]
+   *        The options with which to perform the capture.
+   * @param {string} [options.format = "png"]
+   *        The image format in which to encode the captured data. May be one of
+   *        "png" or "jpeg".
+   * @param {integer} [options.quality = 92]
+   *        The quality at which to encode the captured image data, ranging from
+   *        0 to 100. Has no effect for the "png" format.
+   *
+   * @returns {Promise<string>}
+   */
+  capture(context, options = null) {
+    if (!options) {
+      options = {};
+    }
+    if (options.format == null) {
+      options.format = "png";
+    }
+    if (options.quality == null) {
+      options.quality = 92;
+    }
+
+    let message = {
+      options,
+      width: this.width,
+      height: this.height,
+    };
+
+    return this.sendMessage(context, "Extension:Capture", message);
   }
 
+  /**
+   * @property {integer | null} innerWindowID
+   *        The last known innerWindowID loaded into this tab's docShell. This
+   *        property must remain in sync with the last known values of
+   *        properties such as `url` and `title`. Any operations on the content
+   *        of an out-of-process tab will automatically fail if the
+   *        innerWindowID of the tab when the message is received does not match
+   *        the value of this property when the message was sent.
+   *        @readonly
+   */
+  get innerWindowID() {
+    return this.browser.innerWindowID;
+  }
+
+  /**
+   * @property {boolean} hasTabPermission
+   *        Returns true if the extension has permission to access restricted
+   *        properties of this tab, such as `url`, `title`, and `favIconUrl`.
+   *        @readonly
+   */
   get hasTabPermission() {
     return this.extension.hasPermission("tabs") || this.hasActiveTabPermission;
   }
 
+  /**
+   * @property {boolean} hasActiveTabPermission
+   *        Returns true if the extension has the "activeTab" permission, and
+   *        has been granted access to this tab due to a user executing an
+   *        extension action.
+   *
+   *        If true, the extension may load scripts and CSS into this tab, and
+   *        access restricted properties, such as its `url`.
+   *        @readonly
+   */
   get hasActiveTabPermission() {
     return (this.extension.hasPermission("activeTab") &&
-            this.activeTabWindowId !== null &&
-            this.activeTabWindowId === this.innerWindowId);
+            this.activeTabWindowID != null &&
+            this.activeTabWindowID === this.innerWindowID);
   }
 
+  /**
+   * @property {boolean} incognito
+   *        Returns true if this is a private browsing tab, false otherwise.
+   *        @readonly
+   */
   get incognito() {
     return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
   }
 
+  /**
+   * @property {string} _url
+   *        Returns the current URL of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   */
   get _url() {
     return this.browser.currentURI.spec;
   }
 
+  /**
+   * @property {string | null} url
+   *        Returns the current URL of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get url() {
     if (this.hasTabPermission) {
       return this._url;
     }
   }
 
+  /**
+   * @property {nsIURI | null} uri
+   *        Returns the current URI of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get uri() {
     if (this.hasTabPermission) {
       return this.browser.currentURI;
     }
   }
 
+  /**
+   * @property {string} _title
+   *        Returns the current title of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   */
   get _title() {
-    return this.browser.contentTitle || this.tab.label;
+    return this.browser.contentTitle || this.nativeTab.label;
   }
 
 
+  /**
+   * @property {nsIURI | null} title
+   *        Returns the current title of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get title() {
     if (this.hasTabPermission) {
       return this._title;
     }
   }
 
+  /**
+   * @property {string} _favIconUrl
+   *        Returns the current favicon URL of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   *        @abstract
+   */
+  get _favIconUrl() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {nsIURI | null} faviconUrl
+   *        Returns the current faviron URL of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get favIconUrl() {
     if (this.hasTabPermission) {
       return this._favIconUrl;
     }
   }
 
+  /**
+   * @property {boolean} audible
+   *        Returns true if the tab is currently playing audio, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get audible() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {XULElement} browser
+   *        Returns the XUL browser for the given tab.
+   *        @readonly
+   *        @abstract
+   */
+  get browser() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} cookieStoreId
+   *        Returns the cookie store identifier for the given tab.
+   *        @readonly
+   *        @abstract
+   */
+  get cookieStoreId() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the visible area of the tab.
+   *        @readonly
+   *        @abstract
+   */
+  get height() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} index
+   *        Returns the index of the tab in its window's tab list.
+   *        @readonly
+   *        @abstract
+   */
+  get index() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {MutedInfo} mutedInfo
+   *        Returns information about the tab's current audio muting status.
+   *        @readonly
+   *        @abstract
+   */
+  get mutedInfo() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} pinned
+   *        Returns true if the tab is pinned, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get pinned() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} active
+   *        Returns true if the tab is the currently-selected tab, false
+   *        otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get active() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} selected
+   *        An alias for `active`.
+   *        @readonly
+   *        @abstract
+   */
+  get selected() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} status
+   *        Returns the current loading status of the tab. May be either
+   *        "loading" or "complete".
+   *        @readonly
+   *        @abstract
+   */
+  get status() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the visible area of the tab.
+   *        @readonly
+   *        @abstract
+   */
+  get width() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {DOMWindow} window
+   *        Returns the browser window to which the tab belongs.
+   *        @readonly
+   *        @abstract
+   */
+  get window() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} window
+   *        Returns the numeric ID of the browser window to which the tab belongs.
+   *        @readonly
+   *        @abstract
+   */
+  get windowId() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns true if this tab matches the the given query info object. Omitted
+   * or null have no effect on the match.
+   *
+   * @param {object} queryInfo
+   *        The query info against which to match.
+   * @param {boolean} [queryInfo.active]
+   *        Matches against the exact value of the tab's `active` attribute.
+   * @param {boolean} [queryInfo.audible]
+   *        Matches against the exact value of the tab's `audible` attribute.
+   * @param {string} [queryInfo.cookieStoreId]
+   *        Matches against the exact value of the tab's `cookieStoreId` attribute.
+   * @param {boolean} [queryInfo.highlighted]
+   *        Matches against the exact value of the tab's `highlighted` attribute.
+   * @param {integer} [queryInfo.index]
+   *        Matches against the exact value of the tab's `index` attribute.
+   * @param {boolean} [queryInfo.muted]
+   *        Matches against the exact value of the tab's `mutedInfo.muted` attribute.
+   * @param {boolean} [queryInfo.pinned]
+   *        Matches against the exact value of the tab's `pinned` attribute.
+   * @param {string} [queryInfo.status]
+   *        Matches against the exact value of the tab's `status` attribute.
+   * @param {string} [queryInfo.title]
+   *        Matches against the exact value of the tab's `title` attribute.
+   *
+   *        Note: Per specification, this should perform a pattern match, rather
+   *        than an exact value match, and will do so in the future.
+   * @param {MatchPattern} [queryInfo.url]
+   *        Requires the tab's URL to match the given MatchPattern object.
+   *
+   * @returns {boolean}
+   *        True if the tab matches the query.
+   */
   matches(queryInfo) {
     const PROPS = ["active", "audible", "cookieStoreId", "highlighted", "index", "pinned", "status", "title"];
 
     if (PROPS.some(prop => queryInfo[prop] !== null && queryInfo[prop] !== this[prop])) {
       return false;
     }
 
     if (queryInfo.muted !== null) {
@@ -102,16 +443,23 @@ class TabBase {
 
     if (queryInfo.url && !queryInfo.url.matches(this.uri)) {
       return false;
     }
 
     return true;
   }
 
+  /**
+   * Converts this tab object to a JSON-compatible object containing the values
+   * of its properties which the extension is permitted to access, in the format
+   * requried to be returned by WebExtension APIs.
+   *
+   * @returns {object}
+   */
   convert() {
     let result = {
       id: this.id,
       index: this.index,
       windowId: this.windowId,
       selected: this.selected,
       highlighted: this.selected,
       active: this.selected,
@@ -137,16 +485,35 @@ class TabBase {
           result[prop] = val;
         }
       }
     }
 
     return result;
   }
 
+  /**
+   * Inserts a script or stylesheet in the given tab, and returns a promise
+   * which resolves when the operation has completed.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to perform the injection.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, where, and
+   *        when.
+   * @param {string} kind
+   *        The kind of data being injected. Either "script" or "css".
+   * @param {string} method
+   *        The name of the method which was called to trigger the injection.
+   *        Used to generate appropriate error messages on failure.
+   *
+   * @returns {Promise}
+   *        Resolves to the result of the execution, once it has completed.
+   * @private
+   */
   _execute(context, details, kind, method) {
     let options = {
       js: [],
       css: [],
       remove_css: method == "removeCSS",
     };
 
     // We require a `code` or a `file` property, but we can't accept both.
@@ -191,72 +558,156 @@ class TabBase {
       options.run_at = "document_idle";
     }
     if (details.cssOrigin !== null) {
       options.css_origin = details.cssOrigin;
     } else {
       options.css_origin = "author";
     }
 
-    let {browser} = this;
-    let recipient = {
-      innerWindowID: browser.innerWindowID,
-    };
-
-    return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
+    return this.sendMessage(context, "Extension:Execute", {options});
   }
 
+  /**
+   * Executes a script in the tab's content window, and returns a Promise which
+   * resolves to the result of the evaluation, or rejects to the value of any
+   * error the injection generates.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to inject the script.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, where, and
+   *        when.
+   *
+   * @returns {Promise}
+   *        Resolves to the result of the evaluation of the given script, once
+   *        it has completed, or rejects with any error the evaluation
+   *        generates.
+   */
   executeScript(context, details) {
     return this._execute(context, details, "js", "executeScript");
   }
 
+  /**
+   * Injects CSS into the tab's content window, and returns a Promise which
+   * resolves when the injection is complete.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to inject the script.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, and where.
+   *
+   * @returns {Promise}
+   *        Resolves when the injection has completed.
+   */
   insertCSS(context, details) {
     return this._execute(context, details, "css", "insertCSS").then(() => {});
   }
 
+
+  /**
+   * Removes CSS which was previously into the tab's content window via
+   * `insertCSS`, and returns a Promise which resolves when the operation is
+   * complete.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to remove the CSS.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to remove, and from where.
+   *
+   * @returns {Promise}
+   *        Resolves when the operation has completed.
+   */
   removeCSS(context, details) {
     return this._execute(context, details, "css", "removeCSS").then(() => {});
   }
 }
 
 // Note: These must match the values in windows.json.
 const WINDOW_ID_NONE = -1;
 const WINDOW_ID_CURRENT = -2;
 
+/**
+ * A platform-independent base class for extension-specific wrappers around
+ * native browser windows
+ *
+ * @param {Extension} extension
+ *        The extension object for which this wrapper is being created.
+ * @param {DOMWindow} window
+ *        The browser DOM window which is being wrapped.
+ * @param {integer} id
+ *        The numeric ID of this DOM window object. This ID should be the same for
+ *        every extension, and for the lifetime of the window.
+ */
 class WindowBase {
   constructor(extension, window, id) {
     this.extension = extension;
     this.window = window;
     this.id = id;
   }
 
+  /**
+   * @property {nsIXULWindow} xulWindow
+   *        The nsIXULWindow object for this browser window.
+   *        @readonly
+   */
   get xulWindow() {
     return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDocShell)
                .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIXULWindow);
   }
 
+  /**
+   * Returns true if this window is the current window for the given extension
+   * context, false otherwise.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to perform the check.
+   *
+   * @returns {boolean}
+   */
   isCurrentFor(context) {
     if (context && context.currentWindow) {
       return this.window === context.currentWindow;
     }
     return this.isLastFocused;
   }
 
+  /**
+   * @property {string} type
+   *        The type of the window, as defined by the WebExtension API. May be
+   *        either "normal" or "popup".
+   *        @readonly
+   */
   get type() {
     let {chromeFlags} = this.xulWindow;
 
     if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
       return "popup";
     }
 
     return "normal";
   }
 
+  /**
+   * Converts this window object to a JSON-compatible object which may be
+   * returned to an extension, in the format requried to be returned by
+   * WebExtension APIs.
+   *
+   * @param {object} [getInfo]
+   *        An optional object, the properties of which determine what data is
+   *        available on the result object.
+   * @param {boolean} [getInfo.populate]
+   *        Of true, the result object will contain a `tabs` property,
+   *        containing an array of converted Tab objects, one for each tab in
+   *        the window.
+   *
+   * @returns {object}
+   */
   convert(getInfo) {
     let result = {
       id: this.id,
       focused: this.focused,
       top: this.top,
       left: this.left,
       width: this.width,
       height: this.height,
@@ -268,16 +719,39 @@ class WindowBase {
 
     if (getInfo && getInfo.populate) {
       result.tabs = Array.from(this.getTabs(), tab => tab.convert());
     }
 
     return result;
   }
 
+  /**
+   * Returns true if this window matches the the given query info object. Omitted
+   * or null have no effect on the match.
+   *
+   * @param {object} queryInfo
+   *        The query info against which to match.
+   * @param {boolean} [queryInfo.currentWindow]
+   *        Matches against against the return value of `isCurrentFor()` for the
+   *        given context.
+   * @param {boolean} [queryInfo.lastFocusedWindow]
+   *        Matches against the exact value of the window's `isLastFocused` attribute.
+   * @param {boolean} [queryInfo.windowId]
+   *        Matches against the exact value of the window's ID, taking into
+   *        account the special WINDOW_ID_CURRENT value.
+   * @param {string} [queryInfo.windowType]
+   *        Matches against the exact value of the window's `type` attribute.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {boolean}
+   *        True if the window matches the query.
+   */
   matches(queryInfo, context) {
     if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== this.isLastFocused) {
       return false;
     }
 
     if (queryInfo.windowType !== null && queryInfo.windowType !== this.type) {
       return false;
     }
@@ -293,30 +767,331 @@ class WindowBase {
     }
 
     if (queryInfo.currentWindow !== null && queryInfo.currentWindow !== this.isCurrentFor(context)) {
       return false;
     }
 
     return true;
   }
+
+  /**
+   * @property {boolean} focused
+   *        Returns true if the browser window is currently focused.
+   *        @readonly
+   *        @abstract
+   */
+  get focused() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} top
+   *        Returns the pixel offset of the top of the window from the top of
+   *        the screen.
+   *        @readonly
+   *        @abstract
+   */
+  get top() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} left
+   *        Returns the pixel offset of the left of the window from the left of
+   *        the screen.
+   *        @readonly
+   *        @abstract
+   */
+  get left() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} width
+   *        Returns the pixel width of the window.
+   *        @readonly
+   *        @abstract
+   */
+  get width() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the window.
+   *        @readonly
+   *        @abstract
+   */
+  get height() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} incognito
+   *        Returns true if this is a private browsing window, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get incognito() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} alwaysOnTop
+   *        Returns true if this window is constrained to always remain above
+   *        other windows.
+   *        @readonly
+   *        @abstract
+   */
+  get alwaysOnTop() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} isLastFocused
+   *        Returns true if this is the browser window which most recently had
+   *        focus.
+   *        @readonly
+   *        @abstract
+   */
+  get isLastFocused() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} state
+   *        Returns or sets the current state of this window, as determined by
+   *        `getState()`.
+   *        @abstract
+   */
+  get state() {
+    throw new Error("Not implemented");
+  }
+
+  set state(state) {
+    throw new Error("Not implemented");
+  }
+
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns the window state of the given window.
+   *
+   * @param {DOMWindow} window
+   *        The window for which to return a state.
+   *
+   * @returns {string}
+   *        The window's state. One of "normal", "minimized", "maximized",
+   *        "fullscreen", or "docked".
+   * @static
+   * @abstract
+   */
+  static getState(window) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns an iterator of TabBase objects for each tab in this window.
+   *
+   * @returns {Iterator<TabBase>}
+   */
+  * getTabs() {
+    throw new Error("Not implemented");
+  }
+  /* eslint-enable valid-jsdoc */
 }
 
 Object.assign(WindowBase, {WINDOW_ID_NONE, WINDOW_ID_CURRENT});
 
+/**
+ * The parameter type of "tab-attached" events, which are emitted when a
+ * pre-existing tab is attached to a new window.
+ *
+ * @typedef {Object} TabAttachedEvent
+ * @property {NativeTab} tab
+ *        The native tab object in the window to which the tab is being
+ *        attached. This may be a different object than was used to represent
+ *        the tab in the old window.
+ * @property {integer} tabId
+ *        The ID of the tab being attached.
+ * @property {integer} newWindowId
+ *        The ID of the window to which the tab is being attached.
+ * @property {integer} newPosition
+ *        The position of the tab in the tab list of the new window.
+ */
+
+/**
+ * The parameter type of "tab-detached" events, which are emitted when a
+ * pre-existing tab is detached from a window, in order to be attached to a new
+ * window.
+ *
+ * @typedef {Object} TabDetachedEvent
+ * @property {NativeTab} tab
+ *        The native tab object in the window from which the tab is being
+ *        detached. This may be a different object than will be used to
+ *        represent the tab in the new window.
+ * @property {NativeTab} adoptedBy
+ *        The native tab object in the window to which the tab will be attached,
+ *        and is adopting the contents of this tab. This may be a different
+ *        object than the tab in the previous window.
+ * @property {integer} tabId
+ *        The ID of the tab being detached.
+ * @property {integer} oldWindowId
+ *        The ID of the window from which the tab is being detached.
+ * @property {integer} oldPosition
+ *        The position of the tab in the tab list of the window from which it is
+ *        being detached.
+ */
+
+/**
+ * The parameter type of "tab-created" events, which are emitted when a
+ * new tab is created.
+ *
+ * @typedef {Object} TabCreatedEvent
+ * @property {NativeTab} tab
+ *        The native tab object for the tab which is being created.
+ */
+
+/**
+ * The parameter type of "tab-removed" events, which are emitted when a
+ * tab is removed and destroyed.
+ *
+ * @typedef {Object} TabRemovedEvent
+ * @property {NativeTab} tab
+ *        The native tab object for the tab which is being removed.
+ * @property {integer} tabId
+ *        The ID of the tab being removed.
+ * @property {integer} windowId
+ *        The ID of the window from which the tab is being removed.
+ * @property {boolean} isWindowClosing
+ *        True if the tab is being removed because the window is closing.
+ */
+
+/**
+ * An object containg basic, extension-independent information about the window
+ * and tab that a XUL <browser> belongs to.
+ *
+ * @typedef {Object} BrowserData
+ * @property {integer} tabId
+ *        The numeric ID of the tab that a <browser> belongs to, or -1 if it
+ *        does not belong to a tab.
+ * @property {integer} windowId
+ *        The numeric ID of the browser window that a <browser> belongs to, or -1
+ *        if it does not belong to a browser window.
+ */
+
+/**
+ * A platform-independent base class for the platform-specific TabTracker
+ * classes, which track the opening and closing of tabs, and manage the mapping
+ * of them between numeric IDs and native tab objects.
+ *
+ * Instances of this class are EventEmitters which emit the following events,
+ * each with an argument of the given type:
+ *
+ * - "tab-attached" {@link TabAttacheEvent}
+ * - "tab-detached" {@link TabDetachedEvent}
+ * - "tab-created" {@link TabCreatedEvent}
+ * - "tab-removed" {@link TabRemovedEvent}
+ */
 class TabTrackerBase extends EventEmitter {
   on(...args) {
     if (!this.initialized) {
       this.init();
     }
 
     return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
   }
+
+
+  /**
+   * Called to initialize the tab tracking listeners the first time that an
+   * event listener is added.
+   *
+   * @protected
+   * @abstract
+   */
+  init() {
+    throw new Error("Not implemented");
+  }
+
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns the numeric ID for the given native tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to return an ID.
+   *
+   * @returns {integer}
+   *        The tab's numeric ID.
+   * @abstract
+   */
+  getId(nativeTab) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns the native tab with the given numeric ID.
+   *
+   * @param {integer} tabId
+   *        The numeric ID of the tab to return.
+   * @param {*} default_
+   *        The value to return if no tab exists with the given ID.
+   *
+   * @returns {NativeTab}
+   * @throws {ExtensionError}
+   *       If no tab exists with the given ID and a default return value is not
+   *       provided.
+   * @abstract
+   */
+  getTab(tabId, default_ = undefined) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns basic information about the tab and window that the given browser
+   * belongs to.
+   *
+   * @param {XULElement} browser
+   *        The XUL browser element for which to return data.
+   *
+   * @returns {BrowserData}
+   * @abstract
+   */
+  /* eslint-enable valid-jsdoc */
+  getBrowserData(browser) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {NativeTab} activeTab
+   *        Returns the native tab object for the active tab in the
+   *        most-recently focused window, or null if no live tabs currently
+   *        exist.
+   *        @abstract
+   */
+  get activeTab() {
+    throw new Error("Not implemented");
+  }
 }
 
+/**
+ * A browser progress listener instance which calls a given listener function
+ * whenever the status of the given browser changes.
+ *
+ * @param {function(Object)} listener
+ *        A function to be called whenever the status of a tab's top-level
+ *        browser. It is passed an object with a `browser` property pointing to
+ *        the XUL browser, and a `status` property with a string description of
+ *        the browser's status.
+ * @private
+ */
 class StatusListener {
   constructor(listener) {
     this.listener = listener;
   }
 
   onStateChange(browser, webProgress, request, stateFlags, statusCode) {
     if (!webProgress.isTopLevel) {
       return;
@@ -342,16 +1117,21 @@ class StatusListener {
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
       let status = webProgress.isLoadingDocument ? "loading" : "complete";
       this.listener({browser, status, url: locationURI.spec});
     }
   }
 }
 
+/**
+ * A platform-independent base class for the platform-specific WindowTracker
+ * classes, which track the opening and closing of windows, and manage the
+ * mapping of them between numeric IDs and native tab objects.
+ */
 class WindowTrackerBase extends EventEmitter {
   constructor() {
     super();
 
     this._handleWindowOpened = this._handleWindowOpened.bind(this);
 
     this._openListeners = new Set();
     this._closeListeners = new Set();
@@ -370,18 +1150,29 @@ class WindowTrackerBase extends EventEmi
   }
 
   isBrowserWindow(window) {
     let {documentElement} = window.document;
 
     return documentElement.getAttribute("windowtype") === "navigator:browser";
   }
 
-  // Returns an iterator for all browser windows. Unless |includeIncomplete| is
-  // true, only fully-loaded windows are returned.
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator for all currently active browser windows.
+   *
+   * @param {boolean} [includeInomplete = false]
+   *        If true, include browser windows which are not yet fully loaded.
+   *        Otherwise, only include windows which are.
+   *
+   * @returns {Iterator<DOMWindow>}
+   */
+  /* eslint-enable valid-jsdoc */
   * browserWindows(includeIncomplete = false) {
     // The window type parameter is only available once the window's document
     // element has been created. This means that, when looking for incomplete
     // browser windows, we need to ignore the type entirely for windows which
     // haven't finished loading, since we would otherwise skip browser windows
     // in their early loading stages.
     // This is particularly important given that the "domwindowcreated" event
     // fires for browser windows when they're in that in-between state, and just
@@ -397,87 +1188,162 @@ class WindowTrackerBase extends EventEmi
       }
 
       if (ok) {
         yield window;
       }
     }
   }
 
+  /**
+   * @property {DOMWindow|null} topWindow
+   *        The currently active, or topmost, browser window, or null if no
+   *        browser window is currently open.
+   *        @readonly
+   */
   get topWindow() {
     return Services.wm.getMostRecentWindow("navigator:browser");
   }
 
+  /**
+   * Returns the numeric ID for the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The DOM window for which to return an ID.
+   *
+   * @returns {integer}
+   *        The window's numeric ID.
+   */
   getId(window) {
     return this._windowIds.get(window);
   }
 
+  /**
+   * Returns the browser window to which the given context belongs, or the top
+   * browser window if the context does not belong to a browser window.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to return the current window.
+   *
+   * @returns {DOMWindow|null}
+   */
   getCurrentWindow(context) {
-    let {xulWindow} = context;
-    if (xulWindow && context.viewType !== "background") {
-      return xulWindow;
-    }
-    return this.topWindow;
+    return context.currentWindow || this.topWindow;
   }
 
+  /**
+   * Returns the browser window with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the window to return.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {DOMWindow}
+   * @throws {ExtensionError}
+   *        If no window exists with the given ID.
+   */
   getWindow(id, context) {
     if (id === WINDOW_ID_CURRENT) {
       return this.getCurrentWindow(context);
     }
 
     for (let window of this.browserWindows(true)) {
       if (this.getId(window) === id) {
         return window;
       }
     }
     throw new ExtensionError(`Invalid window ID: ${id}`);
   }
 
-  get haveListeners() {
+  /**
+   * @property {boolean} _haveListeners
+   *        Returns true if any window open or close listeners are currently
+   *        registered.
+   * @private
+   */
+  get _haveListeners() {
     return this._openListeners.size > 0 || this._closeListeners.size > 0;
   }
 
+  /**
+   * Register the given listener function to be called whenever a new browser
+   * window is opened.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to register.
+   */
   addOpenListener(listener) {
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.registerNotification(this);
     }
 
     this._openListeners.add(listener);
 
     for (let window of this.browserWindows(true)) {
       if (window.document.readyState !== "complete") {
         window.addEventListener("load", this);
       }
     }
   }
 
+  /**
+   * Unregister a listener function registered in a previous addOpenListener
+   * call.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to unregister.
+   */
   removeOpenListener(listener) {
     this._openListeners.delete(listener);
 
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.unregisterNotification(this);
     }
   }
 
+  /**
+   * Register the given listener function to be called whenever a browser
+   * window is closed.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to register.
+   */
   addCloseListener(listener) {
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.registerNotification(this);
     }
 
     this._closeListeners.add(listener);
   }
 
+  /**
+   * Unregister a listener function registered in a previous addCloseListener
+   * call.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to unregister.
+   */
   removeCloseListener(listener) {
     this._closeListeners.delete(listener);
 
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.unregisterNotification(this);
     }
   }
 
+  /**
+   * Handles load events for recently-opened windows, and adds additional
+   * listeners which may only be safely added when the window is fully loaded.
+   *
+   * @param {Event} event
+   *        A DOM event to handle.
+   * @private
+   */
   handleEvent(event) {
     if (event.type === "load") {
       event.currentTarget.removeEventListener(event.type, this);
 
       let window = event.target.defaultView;
       if (!this.isBrowserWindow(window)) {
         return;
       }
@@ -487,17 +1353,28 @@ class WindowTrackerBase extends EventEmi
           listener(window);
         } catch (e) {
           Cu.reportError(e);
         }
       }
     }
   }
 
-  observe(window, topic, data) {
+  /**
+   * Observes "domwindowopened" and "domwindowclosed" events, notifies the
+   * appropriate listeners, and adds necessary additional listeners to the new
+   * windows.
+   *
+   * @param {DOMWindow} window
+   *        A DOM window.
+   * @param {string} topic
+   *        The topic being observed.
+   * @private
+   */
+  observe(window, topic) {
     if (topic === "domwindowclosed") {
       if (!this.isBrowserWindow(window)) {
         return;
       }
 
       window.removeEventListener("load", this);
       for (let listener of this._closeListeners) {
         try {
@@ -506,19 +1383,34 @@ class WindowTrackerBase extends EventEmi
           Cu.reportError(e);
         }
       }
     } else if (topic === "domwindowopened") {
       window.addEventListener("load", this);
     }
   }
 
-  // If |type| is a normal event type, invoke |listener| each time
-  // that event fires in any open window. If |type| is "progress", add
-  // a web progress listener that covers all open windows.
+  /**
+   * Add an event listener to be called whenever the given DOM event is recieved
+   * at the top level of any browser window.
+   *
+   * @param {string} type
+   *        The type of event to listen for. May be any valid DOM event name, or
+   *        one of the following special cases:
+   *
+   *        - "progress": Adds a tab progress listener to every browser window.
+   *        - "status": Adds a StatusListener to every tab of every browser
+   *           window.
+   *        - "domwindowopened": Acts as an alias for addOpenListener.
+   *        - "domwindowclosed": Acts as an alias for addCloseListener.
+   * @param {function|object} listener
+   *        The listener to invoke in response to the given events.
+   *
+   * @returns {undefined}
+   */
   addListener(type, listener) {
     if (type === "domwindowopened") {
       return this.addOpenListener(listener);
     } else if (type === "domwindowclosed") {
       return this.addCloseListener(listener);
     }
 
     if (this._listeners.size === 0) {
@@ -533,140 +1425,384 @@ class WindowTrackerBase extends EventEmi
     this._listeners.get(type).add(listener);
 
     // Register listener on all existing windows.
     for (let window of this.browserWindows()) {
       this._addWindowListener(window, type, listener);
     }
   }
 
-  removeListener(eventType, listener) {
-    if (eventType === "domwindowopened") {
+  /**
+   * Removes an event listener previously registered via an addListener call.
+   *
+   * @param {string} type
+   *        The type of event to stop listening for.
+   * @param {function|object} listener
+   *        The listener to remove.
+   *
+   * @returns {undefined}
+   */
+  removeListener(type, listener) {
+    if (type === "domwindowopened") {
       return this.removeOpenListener(listener);
-    } else if (eventType === "domwindowclosed") {
+    } else if (type === "domwindowclosed") {
       return this.removeCloseListener(listener);
     }
 
-    if (eventType === "status") {
+    if (type === "status") {
       listener = this._statusListeners.get(listener);
-      eventType = "progress";
+      type = "progress";
     }
 
-    let listeners = this._listeners.get(eventType);
+    let listeners = this._listeners.get(type);
     listeners.delete(listener);
 
     if (listeners.size === 0) {
-      this._listeners.delete(eventType);
+      this._listeners.delete(type);
       if (this._listeners.size === 0) {
         this.removeOpenListener(this._handleWindowOpened);
       }
     }
 
     // Unregister listener from all existing windows.
-    let useCapture = eventType === "focus" || eventType === "blur";
+    let useCapture = type === "focus" || type === "blur";
     for (let window of this.browserWindows()) {
-      if (eventType === "progress") {
+      if (type === "progress") {
         this.removeProgressListener(window, listener);
       } else {
-        window.removeEventListener(eventType, listener, useCapture);
+        window.removeEventListener(type, listener, useCapture);
       }
     }
   }
 
+  /**
+   * Adds a listener for the given event to the given window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to which to add the listener.
+   * @param {string} eventType
+   *        The type of DOM event to listen for, or "progress" to add a tab
+   *        progress listener.
+   * @param {function|object} listener
+   *        The listener to add.
+   * @private
+   */
   _addWindowListener(window, eventType, listener) {
     let useCapture = eventType === "focus" || eventType === "blur";
 
     if (eventType === "progress") {
       this.addProgressListener(window, listener);
     } else {
       window.addEventListener(eventType, listener, useCapture);
     }
   }
 
+  /**
+   * A private method which is called whenever a new browser window is opened,
+   * and adds the necessary listeners to it.
+   *
+   * @param {DOMWindow} window
+   *        The window being opened.
+   * @private
+   */
   _handleWindowOpened(window) {
     for (let [eventType, listeners] of this._listeners) {
       for (let listener of listeners) {
         this._addWindowListener(window, eventType, listener);
       }
     }
   }
+
+  /**
+   * Adds a tab progress listener to the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to which to add the listener.
+   * @param {object} listener
+   *        The tab progress listener to add.
+   * @abstract
+   */
+  addProgressListener(window, listener) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Removes a tab progress listener from the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window from which to remove the listener.
+   * @param {object} listener
+   *        The tab progress listener to remove.
+   * @abstract
+   */
+  removeProgressListener(window, listener) {
+    throw new Error("Not implemented");
+  }
 }
 
+/**
+ * Manages native tabs, their wrappers, and their dynamic permissions for a
+ * particular extension.
+ *
+ * @param {Extension} extension
+ *        The extension for which to manage tabs.
+ */
 class TabManagerBase {
   constructor(extension) {
     this.extension = extension;
 
     this._tabs = new DefaultWeakMap(tab => this.wrapTab(tab));
   }
 
-  addActiveTabPermission(tab) {
+  /**
+   * If the extension has requested activeTab permission, grant it those
+   * permissions for the current inner window in the given native tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to grant permissions.
+   */
+  addActiveTabPermission(nativeTab) {
     if (this.extension.hasPermission("activeTab")) {
       // Note that, unlike Chrome, we don't currently clear this permission with
       // the tab navigates. If the inner window is revived from BFCache before
       // we've granted this permission to a new inner window, the extension
       // maintains its permissions for it.
-      tab = this.getWrapper(tab);
-      tab.activeTabWindowId = tab.innerWindowId;
+      let tab = this.getWrapper(nativeTab);
+      tab.activeTabWindowID = tab.innerWindowID;
     }
   }
 
-  revokeActiveTabPermission(tab) {
-    this.getWrapper(tab).activeTabWindowId = null;
+  /**
+   * Revoke the extension's activeTab permissions for the current inner window
+   * of the given native tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to revoke permissions.
+   */
+  revokeActiveTabPermission(nativeTab) {
+    this.getWrapper(nativeTab).activeTabWindowID = null;
+  }
+
+  /**
+   * Returns true if the extension has requested activeTab permission, and has
+   * been granted permissions for the current inner window if this tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to check permissions.
+   * @returns {boolean}
+   *        True if the extension has activeTab permissions for this tab.
+   */
+  hasActiveTabPermission(nativeTab) {
+    return this.getWrapper(nativeTab).hasActiveTabPermission;
+  }
+
+  /**
+   * Returns true if the extension has permissions to access restricted
+   * properties of the given native tab. In practice, this means that it has
+   * either requested the "tabs" permission or has activeTab permissions for the
+   * given tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to check permissions.
+   * @returns {boolean}
+   *        True if the extension has permissions for this tab.
+   */
+  hasTabPermission(nativeTab) {
+    return this.getWrapper(nativeTab).hasTabPermission;
   }
 
-  // Returns true if the extension has the "activeTab" permission for this tab.
-  // This is somewhat more permissive than the generic "tabs" permission, as
-  // checked by |hasTabPermission|, in that it also allows programmatic script
-  // injection without an explicit host permission.
-  hasActiveTabPermission(tab) {
-    return this.getWrapper(tab).hasActiveTabPermission;
+  /**
+   * Returns this extension's TabBase wrapper for the given native tab. This
+   * method will always return the same wrapper object for any given native tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   *        The wrapper for this tab.
+   */
+  getWrapper(nativeTab) {
+    return this._tabs.get(nativeTab);
   }
 
-  hasTabPermission(tab) {
-    return this.getWrapper(tab).hasTabPermission;
+  /**
+   * Converts the given native tab to a JSON-compatible object, in the format
+   * requried to be returned by WebExtension APIs, which may be safely passed to
+   * extension code.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab to convert.
+   *
+   * @returns {Object}
+   */
+  convert(nativeTab) {
+    return this.getWrapper(nativeTab).convert();
   }
 
-  getWrapper(tab) {
-    return this._tabs.get(tab);
-  }
-
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator of TabBase objects which match the given query info.
+   *
+   * @param {Object|null} [queryInfo = null]
+   *        An object containing properties on which to filter. May contain any
+   *        properties which are recognized by {@link TabBase#matches} or
+   *        {@link WindowBase#matches}. Unknown properties will be ignored.
+   * @param {BaseContext|null} [context = null]
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {Iterator<TabBase>}
+   */
   * query(queryInfo = null, context = null) {
     for (let window of this.extension.windowManager.query(queryInfo, context)) {
       for (let tab of window.getTabs()) {
         if (!queryInfo || tab.matches(queryInfo)) {
           yield tab;
         }
       }
     }
   }
 
-  convert(tab) {
-    return this.getWrapper(tab).convert();
+  /**
+   * Returns a TabBase wrapper for the tab with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   * @throws {ExtensionError}
+   *        If no tab exists with the given ID.
+   * @abstract
+   */
+  get(tabId) {
+    throw new Error("Not implemented");
   }
 
-  wrapTab(tab) {
+  /**
+   * Returns a new TabBase instance wrapping the given native tab.
+   *
+   * @param {NativeTab} nativeTab
+   *        The native tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   * @protected
+   * @abstract
+   */
+  /* eslint-enable valid-jsdoc */
+  wrapTab(nativeTab) {
     throw new Error("Not implemented");
   }
 }
 
+/**
+ * Manages native browser windows and their wrappers for a particular extension.
+ *
+ * @param {Extension} extension
+ *        The extension for which to manage windows.
+ */
 class WindowManagerBase {
   constructor(extension) {
     this.extension = extension;
 
     this._windows = new DefaultWeakMap(window => this.wrapWindow(window));
   }
 
+  /**
+   * Converts the given browser window to a JSON-compatible object, in the
+   * format requried to be returned by WebExtension APIs, which may be safely
+   * passed to extension code.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to convert.
+   * @param {*} args
+   *        Additional arguments to be passed to {@link WindowBase#convert}.
+   *
+   * @returns {Object}
+   */
   convert(window, ...args) {
     return this.getWrapper(window).convert(...args);
   }
 
-  getWrapper(tab) {
-    return this._windows.get(tab);
+  /**
+   * Returns this extension's WindowBase wrapper for the given browser window.
+   * This method will always return the same wrapper object for any given
+   * browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window for which to return a wrapper.
+   *
+   * @returns {WindowBase}
+   *        The wrapper for this tab.
+   */
+  getWrapper(window) {
+    return this._windows.get(window);
   }
 
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator of WindowBase objects which match the given query info.
+   *
+   * @param {Object|null} [queryInfo = null]
+   *        An object containing properties on which to filter. May contain any
+   *        properties which are recognized by {@link WindowBase#matches}.
+   *        Unknown properties will be ignored.
+   * @param {BaseContext|null} [context = null]
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {Iterator<WindowBase>}
+   */
   * query(queryInfo = null, context = null) {
     for (let window of this.getAll()) {
       if (!queryInfo || window.matches(queryInfo, context)) {
         yield window;
       }
     }
   }
+
+  /**
+   * Returns a WindowBase wrapper for the browser window with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the browser window for which to return a wrapper.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns{WindowBase}
+   * @throws {ExtensionError}
+   *        If no window exists with the given ID.
+   * @abstract
+   */
+  get(windowId, context) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns an iterator of WindowBase wrappers for each currently existing
+   * browser window.
+   *
+   * @returns {Iterator<WindowBase>}
+   * @abstract
+   */
+  * getAll() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns a new WindowBase instance wrapping the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window for which to return a wrapper.
+   *
+   * @returns {WindowBase}
+   * @protected
+   * @abstract
+   */
+  wrapWindow(window) {
+    throw new Error("Not implemented");
+  }
+  /* eslint-enable valid-jsdoc */
 }
--- a/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
@@ -22,20 +22,20 @@ ifdef COMPILE_ENVIRONMENT
 tools::
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 	# Copy for xpcshell tests
 	$(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
 	rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
 	sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
 	  iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings
 	$(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell
-	$(NSINSTALL) updater-xpcshell $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS
+	$(NSINSTALL) $(FINAL_TARGET)/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS
 	rm -Rf $(XPCSHELLTESTROOT)/data/updater.app
 	mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app
 	mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/org.mozilla.updater
 
 	# Copy for mochitest chrome tests
 	rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/
 else
-	cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX)
-	cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX)
+	cp $(FINAL_TARGET)/updater-xpcshell$(BIN_SUFFIX) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX)
+	cp $(FINAL_TARGET)/updater-xpcshell$(BIN_SUFFIX) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX)
 endif
 endif # COMPILE_ENVIRONMENT
--- a/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
@@ -1,14 +1,15 @@
 # -*- 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/.
 
+FINAL_TARGET = '_tests/xpcshell/toolkit/mozapps/update/tests'
+
 Program('updater-xpcshell')
 
 updater_rel_path = '../'
-DIST_INSTALL = False
 DEFINES['TEST_UPDATER'] = True
 include('../updater-common.build')
 
 CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS']
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -3496,20 +3496,35 @@ int NS_main(int argc, NS_tchar **argv)
         else
           *d = *s;
         ++s;
         ++d;
       } while (*s);
       *d = NS_T('\0');
       ++d;
 
+      const size_t callbackBackupPathBufSize =
+        sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]);
+      const int callbackBackupPathLen =
+        NS_tsnprintf(gCallbackBackupPath, callbackBackupPathBufSize,
+                     NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
+
+      if (callbackBackupPathLen < 0 ||
+          callbackBackupPathLen >= static_cast<int>(callbackBackupPathBufSize)) {
+        LOG(("NS_main: callback backup path truncated"));
+        LogFinish();
+        WriteStatusFile(USAGE_ERROR);
+
+        // Don't attempt to launch the callback when the callback path is
+        // longer than expected.
+        EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+        return 1;
+      }
+
       // Make a copy of the callback executable so it can be read when patching.
-      NS_tsnprintf(gCallbackBackupPath,
-                   sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
-                   NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
       NS_tremove(gCallbackBackupPath);
       if(!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true)) {
         DWORD copyFileError = GetLastError();
         LOG(("NS_main: failed to copy callback file " LOG_S
              " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath));
         LogFinish();
         if (copyFileError == ERROR_ACCESS_DENIED) {
           WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -620,59 +620,18 @@ XRE_InitChildProcess(int aArgc,
       case GeckoProcessType_Default:
         MOZ_CRASH("This makes no sense");
         break;
 
       case GeckoProcessType_Plugin:
         process = new PluginProcessChild(parentPID);
         break;
 
-      case GeckoProcessType_Content: {
-          process = new ContentProcess(parentPID);
-          // If passed in grab the application path for xpcom init
-          bool foundAppdir = false;
-
-#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-          // If passed in grab the profile path for sandboxing
-          bool foundProfile = false;
-#endif
-
-          for (int idx = aArgc; idx > 0; idx--) {
-            if (aArgv[idx] && !strcmp(aArgv[idx], "-appdir")) {
-              MOZ_ASSERT(!foundAppdir);
-              if (foundAppdir) {
-                  continue;
-              }
-              nsCString appDir;
-              appDir.Assign(nsDependentCString(aArgv[idx+1]));
-              static_cast<ContentProcess*>(process.get())->SetAppDir(appDir);
-              foundAppdir = true;
-            }
-
-#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-            if (aArgv[idx] && !strcmp(aArgv[idx], "-profile")) {
-              MOZ_ASSERT(!foundProfile);
-              if (foundProfile) {
-                continue;
-              }
-              nsCString profile;
-              profile.Assign(nsDependentCString(aArgv[idx+1]));
-              static_cast<ContentProcess*>(process.get())->SetProfile(profile);
-              foundProfile = true;
-            }
-            if (foundProfile && foundAppdir) {
-              break;
-            }
-#else
-            if (foundAppdir) {
-              break;
-            }
-#endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */
-          }
-        }
+      case GeckoProcessType_Content:
+        process = new ContentProcess(parentPID);
         break;
 
       case GeckoProcessType_IPDLUnitTest:
 #ifdef MOZ_IPDL_TESTS
         process = new IPDLUnitTestProcessChild(parentPID);
 #else
         MOZ_CRASH("rebuild with --enable-ipdl-tests");
 #endif
@@ -685,17 +644,17 @@ XRE_InitChildProcess(int aArgc,
       case GeckoProcessType_GPU:
         process = new gfx::GPUProcessImpl(parentPID);
         break;
 
       default:
         MOZ_CRASH("Unknown main thread class");
       }
 
-      if (!process->Init()) {
+      if (!process->Init(aArgc, aArgv)) {
         return NS_ERROR_FAILURE;
       }
 
 #ifdef MOZ_CRASHREPORTER
 #if defined(XP_WIN) || defined(XP_MACOSX)
       CrashReporter::InitChildProcessTmpDir();
 #endif
 #endif
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/IncrementalTokenizer.cpp
@@ -0,0 +1,195 @@
+/* -*- 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/IncrementalTokenizer.h"
+
+#include "mozilla/AutoRestore.h"
+
+#include "nsIInputStream.h"
+#include "IncrementalTokenizer.h"
+#include <algorithm>
+
+namespace mozilla {
+
+IncrementalTokenizer::IncrementalTokenizer(Consumer aConsumer,
+                                           const char * aWhitespaces,
+                                           const char * aAdditionalWordChars,
+                                           uint32_t aRawMinBuffered)
+  : TokenizerBase(aWhitespaces, aAdditionalWordChars)
+#ifdef DEBUG
+  , mConsuming(false)
+#endif
+  , mNeedMoreInput(false)
+  , mRollback(false)
+  , mInputCursor(0)
+  , mConsumer(aConsumer)
+{
+  mInputFinished = false;
+  mMinRawDelivery = aRawMinBuffered;
+}
+
+nsresult IncrementalTokenizer::FeedInput(const nsACString & aInput)
+{
+  NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(!mInputFinished);
+
+  mInput.Cut(0, mInputCursor);
+  mInputCursor = 0;
+
+  mInput.Append(aInput);
+
+  return Process();
+}
+
+nsresult IncrementalTokenizer::FeedInput(nsIInputStream * aInput, uint32_t aCount)
+{
+  NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(!mInputFinished);
+  MOZ_ASSERT(!mConsuming);
+
+  mInput.Cut(0, mInputCursor);
+  mInputCursor = 0;
+
+  nsresult rv = NS_OK;
+  while (NS_SUCCEEDED(rv) && aCount) {
+    nsCString::index_type remainder = mInput.Length();
+    nsCString::index_type load =
+      std::min<nsCString::index_type>(aCount, PR_UINT32_MAX - remainder);
+
+    if (!load) {
+      // To keep the API simple, we fail if the input data buffer if filled.
+      // It's highly unlikely there will ever be such amout of data cumulated
+      // unless a logic fault in the consumer code.
+      NS_ERROR("IncrementalTokenizer consumer not reading data?");
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!mInput.SetLength(remainder + load, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    nsCString::char_iterator buffer = mInput.BeginWriting() + remainder;
+
+    uint32_t read;
+    rv = aInput->Read(buffer, load, &read);
+    if (NS_SUCCEEDED(rv)) {
+      // remainder + load fits the uint32_t size, so must remainder + read.
+      mInput.SetLength(remainder + read);
+      aCount -= read;
+
+      rv = Process();
+    }
+  }
+
+  return rv;
+}
+
+nsresult IncrementalTokenizer::FinishInput()
+{
+  NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(!mInputFinished);
+  MOZ_ASSERT(!mConsuming);
+
+  mInput.Cut(0, mInputCursor);
+  mInputCursor = 0;
+
+  mInputFinished = true;
+  nsresult rv = Process();
+  mConsumer = nullptr;
+  return rv;
+}
+
+bool IncrementalTokenizer::Next(Token & aToken)
+{
+  // Assert we are called only from the consumer callback
+  MOZ_ASSERT(mConsuming);
+
+  if (mPastEof) {
+    return false;
+  }
+
+  nsACString::const_char_iterator next = Parse(aToken);
+  mPastEof = aToken.Type() == TOKEN_EOF;
+  if (next == mCursor && !mPastEof) {
+    // Not enough input to make a deterministic decision.
+    return false;
+  }
+
+  AssignFragment(aToken, mCursor, next);
+  mCursor = next;
+  return true;
+}
+
+void IncrementalTokenizer::NeedMoreInput()
+{
+  // Assert we are called only from the consumer callback
+  MOZ_ASSERT(mConsuming);
+
+  // When the input has been finished, we can't set the flag to prevent
+  // indefinite wait for more input (that will never come)
+  mNeedMoreInput = !mInputFinished;
+}
+
+void IncrementalTokenizer::Rollback()
+{
+  // Assert we are called only from the consumer callback
+  MOZ_ASSERT(mConsuming);
+
+  mRollback = true;
+}
+
+nsresult IncrementalTokenizer::Process()
+{
+#ifdef DEBUG
+  // Assert we are not re-entered
+  MOZ_ASSERT(!mConsuming);
+
+  AutoRestore<bool> consuming(mConsuming);
+  mConsuming = true;
+#endif
+
+  MOZ_ASSERT(!mPastEof);
+
+  nsresult rv = NS_OK;
+
+  mInput.BeginReading(mCursor);
+  mCursor += mInputCursor;
+  mInput.EndReading(mEnd);
+
+  while (NS_SUCCEEDED(rv) && !mPastEof) {
+    Token token;
+    nsACString::const_char_iterator next = Parse(token);
+    mPastEof = token.Type() == TOKEN_EOF;
+    if (next == mCursor && !mPastEof) {
+      // Not enough input to make a deterministic decision.
+      break;
+    }
+
+    AssignFragment(token, mCursor, next);
+
+    nsACString::const_char_iterator rollback = mCursor;
+    mCursor = next;
+
+    mNeedMoreInput = mRollback = false;
+
+    rv = mConsumer(token, *this);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+    if (mNeedMoreInput || mRollback) {
+      mCursor = rollback;
+      mPastEof = false;
+      if (mNeedMoreInput) {
+        break;
+      }
+    }
+  }
+
+  mInputCursor = mCursor - mInput.BeginReading();
+  return rv;
+}
+
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/IncrementalTokenizer.h
@@ -0,0 +1,122 @@
+/* -*- 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 INCREMENTAL_TOKENIZER_H__
+#define INCREMENTAL_TOKENIZER_H__
+
+#include "mozilla/Tokenizer.h"
+
+#include "nsError.h"
+#include <functional>
+
+class nsIInputStream;
+
+namespace mozilla {
+
+class IncrementalTokenizer : public TokenizerBase
+{
+public:
+  /**
+   * The consumer callback.  The function is called for every single token
+   * as found in the input.  Failure result returned by this callback stops
+   * the tokenization immediately and bubbles to result of Feed/FinishInput.
+   *
+   * Fragment()s of consumed tokens are ensured to remain valid until next call to
+   * Feed/FinishInput and are pointing to a single linear buffer.  Hence, those can
+   * be safely used to accumulate the data for processing after Feed/FinishInput
+   * returned.
+   */
+  typedef std::function<nsresult(Token const&, IncrementalTokenizer& i)> Consumer;
+
+  /**
+   * For aWhitespaces and aAdditionalWordChars arguments see TokenizerBase.
+   *
+   * @param aConsumer
+   *    A mandatory non-null argument, a function that consumes the tokens as they
+   *    come when the tokenizer is fed.
+   * @param aRawMinBuffered
+   *    When we have buffered at least aRawMinBuffered data, but there was no custom
+   *    token found so far because of too small incremental feed chunks, deliver
+   *    the raw data to preserve streaming and to save memory.  This only has effect
+   *    in OnlyCustomTokenizing mode.
+   */
+  explicit IncrementalTokenizer(Consumer aConsumer,
+                                const char* aWhitespaces = nullptr,
+                                const char* aAdditionalWordChars = nullptr,
+                                uint32_t aRawMinBuffered = 1024);
+
+  /**
+   * Pushes the input to be tokenized.  These directly call the Consumer callback
+   * on every found token.  Result of the Consumer callback is returned here.
+   *
+   * The tokenizer must be initialized with a valid consumer prior call to these
+   * methods.  It's not allowed to call Feed/FinishInput from inside the Consumer
+   * callback.
+   */
+  nsresult FeedInput(const nsACString& aInput);
+  nsresult FeedInput(nsIInputStream* aInput, uint32_t aCount);
+  nsresult FinishInput();
+
+  /**
+   * Can only be called from inside the consumer callback.
+   *
+   * When there is still anything to read from the input, tokenize it, store
+   * the token type and value to aToken result and shift the cursor past this
+   * just parsed token.  Each call to Next() reads another token from
+   * the input and shifts the cursor.
+   *
+   * Returns false if there is not enough data to deterministically recognize
+   * tokens or when the last returned token was EOF.
+   */
+  MOZ_MUST_USE
+  bool Next(Token& aToken);
+
+  /**
+   * Can only be called from inside the consumer callback.
+   *
+   * Tells the tokenizer to revert the cursor and stop the async parsing until
+   * next feed of the input.  This is useful when more than one token is needed
+   * to decide on the syntax but there is not enough input to get a next token
+   * (Next() returned false.)
+   */
+  void NeedMoreInput();
+
+  /**
+   * Can only be called from inside the consumer callback.
+   *
+   * This makes the consumer callback be called again while parsing
+   * the input at the previous cursor position again.  This is useful when
+   * the tokenizer state (custom tokens, tokenization mode) has changed and
+   * we want to re-parse the input again.
+   */
+  void Rollback();
+
+private:
+  // Loops over the input with TokenizerBase::Parse and calls the Consumer callback.
+  nsresult Process();
+
+#ifdef DEBUG
+  // True when inside the consumer callback, used only for assertions.
+  bool mConsuming;
+#endif // DEBUG
+  // Modifyable only from the Consumer callback, tells the parser to break, rollback
+  // and wait for more input.
+  bool mNeedMoreInput;
+  // Modifyable only from the Consumer callback, tells the parser to rollback and
+  // parse the input again, with (if modified) new settings of the tokenizer.
+  bool mRollback;
+  // The input buffer.  Updated with each call to Feed/FinishInput.
+  nsCString mInput;
+  // Numerical index pointing at the current cursor position.  We don't keep direct
+  // reference to the string buffer since the buffer gets often reallocated.
+  nsCString::index_type mInputCursor;
+  // Refernce to the consumer function.
+  Consumer mConsumer;
+};
+
+} // mozilla
+
+#endif
--- a/xpcom/ds/Tokenizer.cpp
+++ b/xpcom/ds/Tokenizer.cpp
@@ -2,29 +2,28 @@
 /* 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 "Tokenizer.h"
 
 #include "nsUnicharUtils.h"
+#include <algorithm>
 
 namespace mozilla {
 
 static const char sWhitespaces[] = " \t";
 
 Tokenizer::Tokenizer(const nsACString& aSource,
                      const char* aWhitespaces,
                      const char* aAdditionalWordChars)
-  : mPastEof(false)
-  , mHasFailed(false)
-  , mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces)
-  , mAdditionalWordChars(aAdditionalWordChars)
+  : TokenizerBase(aWhitespaces, aAdditionalWordChars)
 {
+  mInputFinished = true;
   aSource.BeginReading(mCursor);
   mRecord = mRollback = mCursor;
   aSource.EndReading(mEnd);
 }
 
 Tokenizer::Tokenizer(const char* aSource,
                      const char* aWhitespaces,
                      const char* aAdditionalWordChars)
@@ -38,17 +37,17 @@ Tokenizer::Next(Token& aToken)
   if (!HasInput()) {
     mHasFailed = true;
     return false;
   }
 
   mRollback = mCursor;
   mCursor = Parse(aToken);
 
-  aToken.AssignFragment(mRollback, mCursor);
+  AssignFragment(aToken, mRollback, mCursor);
 
   mPastEof = aToken.Type() == TOKEN_EOF;
   mHasFailed = false;
   return true;
 }
 
 bool
 Tokenizer::Check(const TokenType aTokenType, Token& aResult)
@@ -62,17 +61,17 @@ Tokenizer::Check(const TokenType aTokenT
   if (aTokenType != aResult.Type()) {
     mHasFailed = true;
     return false;
   }
 
   mRollback = mCursor;
   mCursor = next;
 
-  aResult.AssignFragment(mRollback, mCursor);
+  AssignFragment(aResult, mRollback, mCursor);
 
   mPastEof = aResult.Type() == TOKEN_EOF;
   mHasFailed = false;
   return true;
 }
 
 bool
 Tokenizer::Check(const Token& aToken)
@@ -91,22 +90,16 @@ Tokenizer::Check(const Token& aToken)
 
   mRollback = mCursor;
   mCursor = next;
   mPastEof = parsed.Type() == TOKEN_EOF;
   mHasFailed = false;
   return true;
 }
 
-bool
-Tokenizer::HasFailed() const
-{
-  return mHasFailed;
-}
-
 void
 Tokenizer::SkipWhites(WhiteSkipping aIncludeNewLines)
 {
   if (!CheckWhite() && (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) {
     return;
   }
 
   nsACString::const_char_iterator rollback = mRollback;
@@ -270,34 +263,166 @@ void
 Tokenizer::Claim(nsDependentCSubstring& aResult, ClaimInclusion aInclusion)
 {
   nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST
     ? mRollback
     : mCursor;
   aResult.Rebind(mRecord, close - mRecord);
 }
 
-// protected
+// TokenizerBase
+
+TokenizerBase::TokenizerBase(const char* aWhitespaces,
+                             const char* aAdditionalWordChars)
+  : mPastEof(false)
+  , mHasFailed(false)
+  , mInputFinished(true)
+  , mMode(Mode::FULL)
+  , mMinRawDelivery(1024)
+  , mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces)
+  , mAdditionalWordChars(aAdditionalWordChars)
+  , mCursor(nullptr)
+  , mEnd(nullptr)
+  , mNextCustomTokenID(TOKEN_CUSTOM0)
+{
+}
+
+TokenizerBase::Token
+TokenizerBase::AddCustomToken(const nsACString & aValue,
+                              ECaseSensitivity aCaseInsensitivity, bool aEnabled)
+{
+  MOZ_ASSERT(!aValue.IsEmpty());
+
+  UniquePtr<Token>& t = *mCustomTokens.AppendElement();
+  t = MakeUnique<Token>();
+
+  t->mType = static_cast<TokenType>(++mNextCustomTokenID);
+  t->mCustomCaseInsensitivity = aCaseInsensitivity;
+  t->mCustomEnabled = aEnabled;
+  t->mCustom.Assign(aValue);
+  return *t;
+}
+
+void
+TokenizerBase::RemoveCustomToken(Token& aToken)
+{
+  if (aToken.mType == TOKEN_UNKNOWN) {
+    // Already removed
+    return;
+  }
+
+  for (UniquePtr<Token> const& custom : mCustomTokens) {
+    if (custom->mType == aToken.mType) {
+      mCustomTokens.RemoveElement(custom);
+      aToken.mType = TOKEN_UNKNOWN;
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Token to remove not found");
+}
+
+void
+TokenizerBase::EnableCustomToken(Token const& aToken, bool aEnabled)
+{
+  if (aToken.mType == TOKEN_UNKNOWN) {
+    // Already removed
+    return;
+  }
+
+  for (UniquePtr<Token> const& custom : mCustomTokens) {
+    if (custom->Type() == aToken.Type()) {
+      // This effectively destroys the token instance.
+      custom->mCustomEnabled = aEnabled;
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Token to change not found");
+}
+
+void
+TokenizerBase::SetTokenizingMode(Mode aMode)
+{
+  mMode = aMode;
+}
 
 bool
-Tokenizer::HasInput() const
+TokenizerBase::HasFailed() const
+{
+  return mHasFailed;
+}
+
+bool
+TokenizerBase::HasInput() const
 {
   return !mPastEof;
 }
 
 nsACString::const_char_iterator
-Tokenizer::Parse(Token& aToken) const
+TokenizerBase::Parse(Token& aToken) const
 {
   if (mCursor == mEnd) {
+    if (!mInputFinished) {
+      return mCursor;
+    }
+
     aToken = Token::EndOfFile();
     return mEnd;
   }
 
+  nsACString::size_type available = mEnd - mCursor;
+
+  uint32_t longestCustom = 0;
+  for (UniquePtr<Token> const& custom : mCustomTokens) {
+    if (IsCustom(mCursor, *custom, &longestCustom)) {
+      aToken = *custom;
+      return mCursor + custom->mCustom.Length();
+    }
+  }
+
+  if (!mInputFinished && available < longestCustom) {
+    // Not enough data to deterministically decide.
+    return mCursor;
+  }
+
   nsACString::const_char_iterator next = mCursor;
 
+  if (mMode == Mode::CUSTOM_ONLY) {
+    // We have to do a brute-force search for all of the enabled custom
+    // tokens.
+    while (next < mEnd) {
+      ++next;
+      for (UniquePtr<Token> const& custom : mCustomTokens) {
+        if (IsCustom(next, *custom)) {
+          aToken = Token::Raw();
+          return next;
+        }
+      }
+    }
+
+    if (mInputFinished) {
+      // End of the data reached.
+      aToken = Token::Raw();
+      return next;
+    }
+
+    if (longestCustom < available && available > mMinRawDelivery) {
+      // We can return some data w/o waiting for either a custom token
+      // or call to FinishData() when we leave the tail where all the
+      // custom tokens potentially fit, so we can't lose only partially
+      // delivered tokens.  This preserves reasonable granularity.
+      aToken = Token::Raw();
+      return mEnd - longestCustom + 1;
+    }
+
+    // Not enough data to deterministically decide.
+    return mCursor;
+  }
+
   enum State {
     PARSE_INTEGER,
     PARSE_WORD,
     PARSE_CRLF,
     PARSE_LF,
     PARSE_WS,
     PARSE_CHAR,
   } state;
@@ -321,36 +446,45 @@ Tokenizer::Parse(Token& aToken) const
   while (next < mEnd) {
     switch (state) {
     case PARSE_INTEGER:
       // Keep it simple for now
       resultingNumber *= 10;
       resultingNumber += static_cast<uint64_t>(*next - '0');
 
       ++next;
+      if (IsPending(next)) {
+        break;
+      }
       if (IsEnd(next) || !IsNumber(*next)) {
         if (!resultingNumber.isValid()) {
           aToken = Token::Error();
         } else {
           aToken = Token::Number(resultingNumber.value());
         }
         return next;
       }
       break;
 
     case PARSE_WORD:
       ++next;
+      if (IsPending(next)) {
+        break;
+      }
       if (IsEnd(next) || !IsWord(*next)) {
         aToken = Token::Word(Substring(mCursor, next));
         return next;
       }
       break;
 
     case PARSE_CRLF:
       ++next;
+      if (IsPending(next)) {
+        break;
+      }
       if (!IsEnd(next) && *next == '\n') { // LF is optional
         ++next;
       }
       aToken = Token::NewLine();
       return next;
 
     case PARSE_LF:
       ++next;
@@ -364,146 +498,210 @@ Tokenizer::Parse(Token& aToken) const
 
     case PARSE_CHAR:
       ++next;
       aToken = Token::Char(*mCursor);
       return next;
     } // switch (state)
   } // while (next < end)
 
-  return next;
+  MOZ_ASSERT(!mInputFinished);
+  return mCursor;
 }
 
 bool
-Tokenizer::IsEnd(const nsACString::const_char_iterator& caret) const
+TokenizerBase::IsEnd(const nsACString::const_char_iterator& caret) const
 {
   return caret == mEnd;
 }
 
 bool
-Tokenizer::IsWordFirst(const char aInput) const
+TokenizerBase::IsPending(const nsACString::const_char_iterator& caret) const
+{
+  return IsEnd(caret) && !mInputFinished;
+}
+
+bool
+TokenizerBase::IsWordFirst(const char aInput) const
 {
   // TODO: make this fully work with unicode
   return (ToLowerCase(static_cast<uint32_t>(aInput)) !=
           ToUpperCase(static_cast<uint32_t>(aInput))) ||
           '_' == aInput ||
           (mAdditionalWordChars ? !!strchr(mAdditionalWordChars, aInput) : false);
 }
 
 bool
-Tokenizer::IsWord(const char aInput) const
+TokenizerBase::IsWord(const char aInput) const
 {
   return IsWordFirst(aInput) || IsNumber(aInput);
 }
 
 bool
-Tokenizer::IsNumber(const char aInput) const
+TokenizerBase::IsNumber(const char aInput) const
 {
   // TODO: are there unicode numbers?
   return aInput >= '0' && aInput <= '9';
 }
 
-// Tokenizer::Token
+bool
+TokenizerBase::IsCustom(const nsACString::const_char_iterator & caret,
+                        const Token & aCustomToken,
+                        uint32_t * aLongest) const
+{
+  MOZ_ASSERT(aCustomToken.mType > TOKEN_CUSTOM0);
+  if (!aCustomToken.mCustomEnabled) {
+    return false;
+  }
+
+  if (aLongest) {
+    *aLongest = std::max(*aLongest, aCustomToken.mCustom.Length());
+  }
+
+  uint32_t inputLength = mEnd - caret;
+  if (aCustomToken.mCustom.Length() > inputLength) {
+    return false;
+  }
 
-Tokenizer::Token::Token(const Token& aOther)
+  nsDependentCSubstring inputFragment(caret, aCustomToken.mCustom.Length());
+  if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) {
+    return inputFragment.Equals(aCustomToken.mCustom, nsCaseInsensitiveUTF8StringComparator());
+  }
+  return inputFragment.Equals(aCustomToken.mCustom);
+}
+
+void TokenizerBase::AssignFragment(Token& aToken,
+                                   nsACString::const_char_iterator begin,
+                                   nsACString::const_char_iterator end)
+{
+  aToken.AssignFragment(begin, end);
+}
+
+// TokenizerBase::Token
+
+TokenizerBase::Token::Token()
+  : mType(TOKEN_UNKNOWN)
+  , mChar(0)
+  , mInteger(0)
+  , mCustomCaseInsensitivity(CASE_SENSITIVE)
+  , mCustomEnabled(false)
+{
+}
+
+TokenizerBase::Token::Token(const Token& aOther)
   : mType(aOther.mType)
+  , mCustom(aOther.mCustom)
   , mChar(aOther.mChar)
   , mInteger(aOther.mInteger)
+  , mCustomCaseInsensitivity(aOther.mCustomCaseInsensitivity)
+  , mCustomEnabled(aOther.mCustomEnabled)
 {
-  if (mType == TOKEN_WORD) {
+  if (mType == TOKEN_WORD || mType > TOKEN_CUSTOM0) {
     mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length());
   }
 }
 
-Tokenizer::Token&
-Tokenizer::Token::operator=(const Token& aOther)
+TokenizerBase::Token&
+TokenizerBase::Token::operator=(const Token& aOther)
 {
   mType = aOther.mType;
+  mCustom = aOther.mCustom;
   mChar = aOther.mChar;
   mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length());
   mInteger = aOther.mInteger;
+  mCustomCaseInsensitivity = aOther.mCustomCaseInsensitivity;
+  mCustomEnabled = aOther.mCustomEnabled;
   return *this;
 }
 
 void
-Tokenizer::Token::AssignFragment(nsACString::const_char_iterator begin,
-                                 nsACString::const_char_iterator end)
+TokenizerBase::Token::AssignFragment(nsACString::const_char_iterator begin,
+                                     nsACString::const_char_iterator end)
 {
   mFragment.Rebind(begin, end - begin);
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::Word(const nsACString& aValue)
+TokenizerBase::Token
+TokenizerBase::Token::Raw()
+{
+  Token t;
+  t.mType = TOKEN_RAW;
+  return t;
+}
+
+// static
+TokenizerBase::Token
+TokenizerBase::Token::Word(const nsACString& aValue)
 {
   Token t;
   t.mType = TOKEN_WORD;
   t.mWord.Rebind(aValue.BeginReading(), aValue.Length());
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::Char(const char aValue)
+TokenizerBase::Token
+TokenizerBase::Token::Char(const char aValue)
 {
   Token t;
   t.mType = TOKEN_CHAR;
   t.mChar = aValue;
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::Number(const uint64_t aValue)
+TokenizerBase::Token
+TokenizerBase::Token::Number(const uint64_t aValue)
 {
   Token t;
   t.mType = TOKEN_INTEGER;
   t.mInteger = aValue;
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::Whitespace()
+TokenizerBase::Token
+TokenizerBase::Token::Whitespace()
 {
   Token t;
   t.mType = TOKEN_WS;
   t.mChar = '\0';
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::NewLine()
+TokenizerBase::Token
+TokenizerBase::Token::NewLine()
 {
   Token t;
   t.mType = TOKEN_EOL;
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::EndOfFile()
+TokenizerBase::Token
+TokenizerBase::Token::EndOfFile()
 {
   Token t;
   t.mType = TOKEN_EOF;
   return t;
 }
 
 // static
-Tokenizer::Token
-Tokenizer::Token::Error()
+TokenizerBase::Token
+TokenizerBase::Token::Error()
 {
   Token t;
   t.mType = TOKEN_ERROR;
   return t;
 }
 
 bool
-Tokenizer::Token::Equals(const Token& aOther) const
+TokenizerBase::Token::Equals(const Token& aOther) const
 {
   if (mType != aOther.mType) {
     return false;
   }
 
   switch (mType) {
   case TOKEN_INTEGER:
     return AsInteger() == aOther.AsInteger();
@@ -512,29 +710,29 @@ Tokenizer::Token::Equals(const Token& aO
   case TOKEN_CHAR:
     return AsChar() == aOther.AsChar();
   default:
     return true;
   }
 }
 
 char
-Tokenizer::Token::AsChar() const
+TokenizerBase::Token::AsChar() const
 {
   MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS);
   return mChar;
 }
 
 nsDependentCSubstring
-Tokenizer::Token::AsString() const
+TokenizerBase::Token::AsString() const
 {
   MOZ_ASSERT(mType == TOKEN_WORD);
   return mWord;
 }
 
 uint64_t
-Tokenizer::Token::AsInteger() const
+TokenizerBase::Token::AsInteger() const
 {
   MOZ_ASSERT(mType == TOKEN_INTEGER);
   return mInteger;
 }
 
 } // mozilla
--- a/xpcom/ds/Tokenizer.h
+++ b/xpcom/ds/Tokenizer.h
@@ -4,66 +4,76 @@
  * 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 Tokenizer_h__
 #define Tokenizer_h__
 
 #include "nsString.h"
 #include "mozilla/CheckedInt.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 
-/**
- * This is a simple implementation of a lexical analyzer or maybe better
- * called a tokenizer.  It doesn't allow any user dictionaries or
- * user define token types.
- *
- * It is limited only to ASCII input for now. UTF-8 or any other input
- * encoding must yet be implemented.
- */
-class Tokenizer {
+class TokenizerBase
+{
 public:
   /**
    * The analyzer works with elements in the input cut to a sequence of token
    * where each token has an elementary type
    */
-  enum TokenType {
+  enum TokenType : uint32_t
+  {
     TOKEN_UNKNOWN,
+    TOKEN_RAW,
     TOKEN_ERROR,
     TOKEN_INTEGER,
     TOKEN_WORD,
     TOKEN_CHAR,
     TOKEN_WS,
     TOKEN_EOL,
-    TOKEN_EOF
+    TOKEN_EOF,
+    TOKEN_CUSTOM0 = 1000
+  };
+
+  enum ECaseSensitivity
+  {
+    CASE_SENSITIVE,
+    CASE_INSENSITIVE
   };
 
   /**
    * Class holding the type and the value of a token.  It can be manually created
    * to allow checks against it via methods of Tokenizer or are results of some of
    * the Tokenizer's methods.
    */
-  class Token {
+  class Token
+  {
     TokenType mType;
     nsDependentCSubstring mWord;
+    nsCString mCustom;
     char mChar;
     uint64_t mInteger;
+    ECaseSensitivity mCustomCaseInsensitivity;
+    bool mCustomEnabled;
 
     // If this token is a result of the parsing process, this member is referencing
     // a sub-string in the input buffer.  If this is externally created Token this
     // member is left an empty string.
     nsDependentCSubstring mFragment;
 
-    friend class Tokenizer;
+    friend class TokenizerBase;
     void AssignFragment(nsACString::const_char_iterator begin,
                         nsACString::const_char_iterator end);
 
+    static Token Raw();
+
   public:
-    Token() : mType(TOKEN_UNKNOWN), mChar(0), mInteger(0) {}
+    Token();
     Token(const Token& aOther);
     Token& operator=(const Token& aOther);
 
     // Static constructors of tokens by type and value
     static Token Word(const nsACString& aWord);
     static Token Char(const char aChar);
     static Token Number(const uint64_t aNumber);
     static Token Whitespace();
@@ -78,16 +88,130 @@ public:
     TokenType Type() const { return mType; }
     char AsChar() const;
     nsDependentCSubstring AsString() const;
     uint64_t AsInteger() const;
 
     nsDependentCSubstring Fragment() const { return mFragment; }
   };
 
+  /**
+   * Consumers may register a custom string that, when found in the input, is considered
+   * a token and returned by Next*() and accepted by Check*() methods.
+   * AddCustomToken() returns a reference to a token that can then be comapred using
+   * Token::Equals() againts the output from Next*() or be passed to Check*().
+   */
+  Token AddCustomToken(const nsACString& aValue, ECaseSensitivity aCaseInsensitivity, bool aEnabled = true);
+  template <uint32_t N>
+  Token AddCustomToken(const char(&aValue)[N], ECaseSensitivity aCaseInsensitivity, bool aEnabled = true)
+  {
+    return AddCustomToken(nsDependentCSubstring(aValue, N - 1), aCaseInsensitivity, aEnabled);
+  }
+  void RemoveCustomToken(Token& aToken);
+  /**
+   * Only applies to a custom type of a Token (see AddCustomToken above.)
+   * This turns on and off token recognition.  When a custom token is disabled,
+   * it's ignored as never added as a custom token.
+   */
+  void EnableCustomToken(Token const& aToken, bool aEnable);
+
+  /**
+   * Mode of tokenization.
+   * FULL tokenization, the default, recognizes built-in tokens and any custom tokens,
+   * if added.
+   * CUSTOM_ONLY will only recognize custom tokens, the rest is seen as 'raw'.
+   * This mode can be understood as a 'binary' mode.
+   */
+  enum class Mode
+  {
+    FULL,
+    CUSTOM_ONLY
+  };
+  void SetTokenizingMode(Mode aMode);
+
+  /**
+   * Return false iff the last Check*() call has returned false or when we've read past
+   * the end of the input string.
+   */
+  MOZ_MUST_USE bool HasFailed() const;
+
+protected:
+  explicit TokenizerBase(const char* aWhitespaces = nullptr,
+                         const char* aAdditionalWordChars = nullptr);
+
+  // false if we have already read the EOF token.
+  bool HasInput() const;
+  // Main parsing function, it doesn't shift the read cursor, just returns the next
+  // token position.
+  nsACString::const_char_iterator Parse(Token& aToken) const;
+  // Is read cursor at the end?
+  bool IsEnd(const nsACString::const_char_iterator& caret) const;
+  // True, when we are at the end of the input data, but it has not been marked
+  // as complete yet.  In that case we cannot proceed with providing a multi-char token.
+  bool IsPending(const nsACString::const_char_iterator & caret) const;
+  // Is read cursor on a character that is a word start?
+  bool IsWordFirst(const char aInput) const;
+  // Is read cursor on a character that is an in-word letter?
+  bool IsWord(const char aInput) const;
+  // Is read cursor on a character that is a valid number?
+  // TODO - support multiple radix
+  bool IsNumber(const char aInput) const;
+  // Is equal to the given custom token?
+  bool IsCustom(const nsACString::const_char_iterator& caret,
+                const Token& aCustomToken, uint32_t* aLongest = nullptr) const;
+
+  // Friendly helper to assign a fragment on a Token
+  static void AssignFragment(Token& aToken,
+                             nsACString::const_char_iterator begin,
+                             nsACString::const_char_iterator end);
+
+  // true iff we have already read the EOF token
+  bool mPastEof;
+  // true iff the last Check*() call has returned false, reverts to true on Rollback() call
+  bool mHasFailed;
+  // true if the input string is final (finished), false when we expect more data
+  // yet to be fed to the tokenizer (see IncrementalTokenizer derived class).
+  bool mInputFinished;
+  // custom only vs full tokenizing mode, see the Parse() method
+  Mode mMode;
+  // minimal raw data chunked delivery during incremental feed
+  uint32_t mMinRawDelivery;
+
+  // Customizable list of whitespaces
+  const char* mWhitespaces;
+  // Additinal custom word characters
+  const char* mAdditionalWordChars;
+
+  // All these point to the original buffer passed to the constructor or to the incremental
+  // buffer after FeedInput.
+  nsACString::const_char_iterator mCursor; // Position of the current (actually next to read) token start
+  nsACString::const_char_iterator mEnd; // End of the input position
+
+  // This is the list of tokens user has registered with AddCustomToken()
+  nsTArray<UniquePtr<Token>> mCustomTokens;
+  uint32_t mNextCustomTokenID;
+
+private:
+  TokenizerBase() = delete;
+  TokenizerBase(const TokenizerBase&) = delete;
+  TokenizerBase(TokenizerBase&&) = delete;
+  TokenizerBase(const TokenizerBase&&) = delete;
+  TokenizerBase &operator=(const TokenizerBase&) = delete;
+};
+
+/**
+ * This is a simple implementation of a lexical analyzer or maybe better
+ * called a tokenizer.  It doesn't allow any user dictionaries or
+ * user define token types.
+ *
+ * It is limited only to ASCII input for now. UTF-8 or any other input
+ * encoding must yet be implemented.
+ */
+class Tokenizer : public TokenizerBase
+{
 public:
   /**
    * @param aSource
    *    The string to parse.
    *    IMPORTANT NOTE: Tokenizer doesn't ensure the input string buffer lifetime.
    *    It's up to the consumer to make sure the string's buffer outlives the Tokenizer!
    * @param aWhitespaces
    *    If non-null Tokenizer will use this custom set of whitespaces for CheckWhite()
@@ -129,23 +253,16 @@ public:
    * Same as above method, just compares both token type and token value passed in aToken.
    * When both the type and the value equals, shift the cursor and return true.  Otherwise
    * return false.
    */
   MOZ_MUST_USE
   bool Check(const Token& aToken);
 
   /**
-   * Return false iff the last Check*() call has returned false or when we've read past
-   * the end of the input string.
-   */
-  MOZ_MUST_USE
-  bool HasFailed() const;
-
-  /**
    * SkipWhites method (below) may also skip new line characters automatically.
    */
   enum WhiteSkipping {
     /**
      * SkipWhites will only skip what is defined as a white space (default).
      */
     DONT_INCLUDE_NEW_LINE = 0,
     /**
@@ -307,46 +424,19 @@ public:
    * position it had before ReadUntil was called.
    */
   MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsDependentCSubstring& aResult,
                               ClaimInclusion aInclude = EXCLUDE_LAST);
   MOZ_MUST_USE bool ReadUntil(Token const& aToken, nsACString& aResult,
                               ClaimInclusion aInclude = EXCLUDE_LAST);
 
 protected:
-  // false if we have already read the EOF token.
-  bool HasInput() const;
-  // Main parsing function, it doesn't shift the read cursor, just returns the next
-  // token position.
-  nsACString::const_char_iterator Parse(Token& aToken) const;
-  // Is read cursor at the end?
-  bool IsEnd(const nsACString::const_char_iterator& caret) const;
-  // Is read cursor on a character that is a word start?
-  bool IsWordFirst(const char aInput) const;
-  // Is read cursor on a character that is an in-word letter?
-  bool IsWord(const char aInput) const;
-  // Is read cursor on a character that is a valid number?
-  // TODO - support multiple radix
-  bool IsNumber(const char aInput) const;
-
-  // true iff we have already read the EOF token
-  bool mPastEof;
-  // true iff the last Check*() call has returned false, reverts to true on Rollback() call
-  bool mHasFailed;
-
-  // Customizable list of whitespaces
-  const char* mWhitespaces;
-  // Additinal custom word characters
-  const char* mAdditionalWordChars;
-
-  // All these point to the original buffer passed to the Tokenizer
+  // All these point to the original buffer passed to the Tokenizer's constructor
   nsACString::const_char_iterator mRecord; // Position where the recorded sub-string for Claim() is
   nsACString::const_char_iterator mRollback; // Position of the previous token start
-  nsACString::const_char_iterator mCursor; // Position of the current (actually next to read) token start
-  nsACString::const_char_iterator mEnd; // End of the input position
 
 private:
   Tokenizer() = delete;
   Tokenizer(const Tokenizer&) = delete;
   Tokenizer(Tokenizer&&) = delete;
   Tokenizer(const Tokenizer&&) = delete;
   Tokenizer &operator=(const Tokenizer&) = delete;
 };
--- a/xpcom/ds/moz.build
+++ b/xpcom/ds/moz.build
@@ -78,22 +78,24 @@ EXPORTS += [
     'nsTObserverArray.h',
     'nsTPriorityQueue.h',
     'nsVariant.h',
     'nsWhitespaceTokenizer.h',
     'PLDHashTable.h',
 ]
 
 EXPORTS.mozilla += [
+    'IncrementalTokenizer.h',
     'Observer.h',
     'StickyTimeDuration.h',
     'Tokenizer.h',
 ]
 
 UNIFIED_SOURCES += [
+    'IncrementalTokenizer.cpp',
     'nsArray.cpp',
     'nsArrayEnumerator.cpp',
     'nsArrayUtils.cpp',
     'nsAtomService.cpp',
     'nsAtomTable.cpp',
     'nsCOMArray.cpp',
     'nsCRT.cpp',
     'nsDeque.cpp',
--- a/xpcom/tests/gtest/TestTokenizer.cpp
+++ b/xpcom/tests/gtest/TestTokenizer.cpp
@@ -1,15 +1,17 @@
 /* -*- 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/Tokenizer.h"
+#include "mozilla/IncrementalTokenizer.h"
+#include "mozilla/Unused.h"
 #include "gtest/gtest.h"
 
 using namespace mozilla;
 
 static bool IsOperator(char const c)
 {
   return c == '+' || c == '*';
 }
@@ -727,8 +729,406 @@ TEST(Tokenizer, SkipUntil)
 
     p.SkipUntil(Tokenizer::Token::Char(','));
     p.Rollback();
 
     EXPECT_TRUE(p.CheckWord("test2"));
     EXPECT_TRUE(p.CheckEOF());
   }
 }
+
+TEST(Tokenizer, Custom)
+{
+  Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+  Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+  Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+  // It's expected to NOT FIND the custom token if it's not on an edge
+  // between other recognizable tokens.
+  EXPECT_TRUE(p.CheckWord("aaaaaacustom"));
+  EXPECT_TRUE(p.CheckChar('-'));
+  EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+  EXPECT_TRUE(p.CheckEOL());
+  EXPECT_TRUE(p.CheckChar(','));
+
+  EXPECT_TRUE(p.Check(c1));
+  EXPECT_TRUE(p.CheckChar(','));
+
+  EXPECT_TRUE(p.Check(c1));
+  EXPECT_TRUE(p.CheckChar(','));
+
+  p.EnableCustomToken(c1, false);
+  EXPECT_TRUE(p.CheckWord("Custom"));
+  EXPECT_TRUE(p.CheckChar('-'));
+  EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+  EXPECT_TRUE(p.CheckChar(','));
+
+  EXPECT_TRUE(p.Check(Tokenizer::Token::Number(0)));
+  EXPECT_TRUE(p.Check(c2));
+  EXPECT_TRUE(p.CheckWord("xxxx"));
+  EXPECT_TRUE(p.CheckChar(','));
+
+  EXPECT_TRUE(p.CheckWord("CUSTOM"));
+  EXPECT_TRUE(p.CheckChar('-'));
+  EXPECT_TRUE(p.Check(Tokenizer::Token::Number(2)));
+
+  EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, CustomRaw)
+{
+  Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+  Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+  Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+  // In this mode it's expected to find all custom tokens among any kind of input.
+  p.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+  Tokenizer::Token t;
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral("aaaaaa"));
+
+  EXPECT_TRUE(p.Check(c1));
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral("\r,"));
+
+  EXPECT_TRUE(p.Check(c1));
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+  EXPECT_TRUE(p.Check(c1));
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+  EXPECT_TRUE(p.Check(c1));
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral(",00"));
+
+  EXPECT_TRUE(p.Check(c2));
+
+  EXPECT_TRUE(p.Next(t));
+  EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+  EXPECT_TRUE(t.Fragment().EqualsLiteral("xxxx,CUSTOM-2"));
+
+  EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, Incremental)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+      case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+      case 4: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 7: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+      case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    return NS_OK;
+  });
+
+  NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+  auto cur = input.BeginReading();
+  auto end = input.EndReading();
+  for (; cur < end; ++cur) {
+    i.FeedInput(nsDependentCSubstring(cur, 1));
+  }
+
+  EXPECT_TRUE(test == 6);
+  i.FinishInput();
+  EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalRollback)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+      case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2"))));
+        i.Rollback(); // so that we get the token again
+        break;
+      case 4: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+      case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 7: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+      case 8: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+      case 9: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    return NS_OK;
+  });
+
+  NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+  auto cur = input.BeginReading();
+  auto end = input.EndReading();
+  for (; cur < end; ++cur) {
+    i.FeedInput(nsDependentCSubstring(cur, 1));
+  }
+
+  EXPECT_TRUE(test == 7);
+  i.FinishInput();
+  EXPECT_TRUE(test == 9);
+}
+
+TEST(Tokenizer, IncrementalNeedMoreInput)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    Token t2;
+    switch (++test) {
+    case 1:
+      EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("a"))));
+      break;
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+      EXPECT_TRUE(t.Equals(Token::Whitespace()));
+      if (i.Next(t2)) {
+        EXPECT_TRUE(test == 5);
+        EXPECT_TRUE(t2.Equals(Token::Word(NS_LITERAL_CSTRING("bb"))));
+      } else {
+        EXPECT_TRUE(test < 5);
+        i.NeedMoreInput();
+      }
+      break;
+    case 6:
+      EXPECT_TRUE(t.Equals(Token::Char(',')));
+      break;
+    case 7:
+      EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("c"))));
+      return NS_ERROR_FAILURE;
+    default:
+      EXPECT_TRUE(false);
+      break;
+    }
+
+    return NS_OK;
+  });
+
+  NS_NAMED_LITERAL_CSTRING(input, "a bb,c");
+  auto cur = input.BeginReading();
+  auto end = input.EndReading();
+
+  nsresult rv;
+  for (; cur < end; ++cur) {
+    rv = i.FeedInput(nsDependentCSubstring(cur, 1));
+    if (NS_FAILED(rv)) {
+      break;
+    }
+  }
+
+  EXPECT_TRUE(rv == NS_OK);
+  EXPECT_TRUE(test == 6);
+
+  rv = i.FinishInput();
+  EXPECT_TRUE(rv == NS_ERROR_FAILURE);
+  EXPECT_TRUE(test == 7);
+}
+
+TEST(Tokenizer, IncrementalCustom)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  Token custom;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Equals(custom)); break;
+      case 2: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("bla")))); break;
+      case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    return NS_OK;
+  }, nullptr, "-");
+
+  custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE);
+  i.FeedInput(NS_LITERAL_CSTRING("some-"));
+  EXPECT_TRUE(test == 0);
+  i.FeedInput(NS_LITERAL_CSTRING("tes"));
+  EXPECT_TRUE(test == 0);
+  i.FeedInput(NS_LITERAL_CSTRING("tbla"));
+  EXPECT_TRUE(test == 1);
+  i.FinishInput();
+  EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalCustomRaw)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  Token custom;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("test1,")); break;
+      case 2: EXPECT_TRUE(t.Equals(custom)); break;
+      case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("!,,test3"));
+        i.Rollback();
+        i.SetTokenizingMode(Tokenizer::Mode::FULL);
+        break;
+      case 4: EXPECT_TRUE(t.Equals(Token::Char('!')));
+        i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+        break;
+      case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral(",,test3")); break;
+      case 6: EXPECT_TRUE(t.Equals(custom)); break;
+      case 7: EXPECT_TRUE(t.Fragment().EqualsLiteral("tes")); break;
+      case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    return NS_OK;
+  });
+
+  custom = i.AddCustomToken("test2", Tokenizer::CASE_SENSITIVE);
+  i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+  NS_NAMED_LITERAL_CSTRING(input, "test1,test2!,,test3test2tes");
+  auto cur = input.BeginReading();
+  auto end = input.EndReading();
+  for (; cur < end; ++cur) {
+    i.FeedInput(nsDependentCSubstring(cur, 1));
+  }
+
+  EXPECT_TRUE(test == 6);
+  i.FinishInput();
+  EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalCustomRemove)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  Token custom;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Equals(custom));
+        i.RemoveCustomToken(custom);
+        break;
+      case 2: EXPECT_FALSE(t.Equals(custom)); break;
+      case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    return NS_OK;
+  });
+
+  custom = i.AddCustomToken("custom1", Tokenizer::CASE_SENSITIVE);
+
+  NS_NAMED_LITERAL_CSTRING(input, "custom1custom1");
+  i.FeedInput(input);
+  EXPECT_TRUE(test == 1);
+  i.FinishInput();
+  EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalBuffering1)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  Token custom;
+  nsDependentCSubstring observedFragment;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("012")); break;
+      case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("3456789")); break;
+      case 3: EXPECT_TRUE(t.Equals(custom)); break;
+      case 4: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwe")); break;
+      case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("rt")); break;
+      case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+
+    observedFragment.Rebind(t.Fragment().BeginReading(),
+                            t.Fragment().Length());
+    return NS_OK;
+  }, nullptr, nullptr, 3);
+
+  custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+  // This externally unused token is added only to check the internal algorithm
+  // does work correctly as expected when there are two different length tokens.
+  Unused << i.AddCustomToken("bb", Tokenizer::CASE_SENSITIVE);
+  i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+  i.FeedInput(NS_LITERAL_CSTRING("01234"));
+  EXPECT_TRUE(test == 1);
+  EXPECT_TRUE(observedFragment.EqualsLiteral("012"));
+
+  i.FeedInput(NS_LITERAL_CSTRING("5"));
+  EXPECT_TRUE(test == 1);
+  i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+  EXPECT_TRUE(test == 2);
+  EXPECT_TRUE(observedFragment.EqualsLiteral("3456789"));
+
+  i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+  EXPECT_TRUE(test == 4);
+  EXPECT_TRUE(observedFragment.EqualsLiteral("qwe"));
+
+  i.FinishInput();
+  EXPECT_TRUE(test == 6);
+}
+
+TEST(Tokenizer, IncrementalBuffering2)
+{
+  typedef TokenizerBase::Token Token;
+
+  int test = 0;
+  Token custom;
+  IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+  {
+    switch (++test) {
+      case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("01")); break;
+      case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("234567")); break;
+      case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("89")); break;
+      case 4: EXPECT_TRUE(t.Equals(custom)); break;
+      case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwert")); break;
+      case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+    }
+    return NS_OK;
+  }, nullptr, nullptr, 3);
+
+  custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+  // This externally unused token is added only to check the internal algorithm
+  // does work correctly as expected when there are two different length tokens.
+  Unused << i.AddCustomToken("bbbbb", Tokenizer::CASE_SENSITIVE);
+  i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+  i.FeedInput(NS_LITERAL_CSTRING("01234"));
+  EXPECT_TRUE(test == 0);
+  i.FeedInput(NS_LITERAL_CSTRING("5"));
+  EXPECT_TRUE(test == 1);
+  i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+  EXPECT_TRUE(test == 2);
+  i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+  EXPECT_TRUE(test == 4);
+  i.FinishInput();
+  EXPECT_TRUE(test == 6);
+}