merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 09 Nov 2016 16:36:48 +0100
changeset 321692 6b7f1acfbb4b3fab041817a7dfbe39743416181f
parent 321628 df664b94c0054570335a3952da4fa22926356cbc (current diff)
parent 321691 17dd918189c147111537bf8ae372dfccd838f135 (diff)
child 321813 336759fad4621dfcd0a3293840edbed67018accd
push id30933
push usercbook@mozilla.com
push dateWed, 09 Nov 2016 15:37:07 +0000
treeherdermozilla-central@6b7f1acfbb4b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
security/manager/pki/resources/content/password.js
testing/puppeteer/firefox/firefox_puppeteer/testcases/__init__.py
testing/puppeteer/firefox/firefox_puppeteer/testcases/base.py
testing/puppeteer/firefox/firefox_puppeteer/ui_base_lib.py
--- a/accessible/ipc/win/PlatformChild.cpp
+++ b/accessible/ipc/win/PlatformChild.cpp
@@ -23,20 +23,24 @@ namespace a11y {
  * outparams. Instead we manually define the relevant metadata here, and
  * register it in a call to mozilla::mscom::RegisterArrayData.
  * @see mozilla::mscom::ArrayData
  */
 static const mozilla::mscom::ArrayData sPlatformChildArrayData[] = {
   {IID_IEnumVARIANT, 3, 1, VT_DISPATCH, IID_IDispatch, 2},
   {IID_IAccessible2, 30, 1, VT_UNKNOWN | VT_BYREF, IID_IAccessibleRelation, 2},
   {IID_IAccessibleRelation, 7, 1, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 2},
-  {IID_IAccessible2_2, 48, 2, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 3},
-  {IID_IAccessibleTableCell, 4, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1},
-  {IID_IAccessibleTableCell, 7, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1},
-  {IID_IAccessibleHypertext2, 25, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1}
+  {IID_IAccessible2_2, 48, 2, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 3,
+   mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+  {IID_IAccessibleTableCell, 4, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+   mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+  {IID_IAccessibleTableCell, 7, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+   mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+  {IID_IAccessibleHypertext2, 25, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+   mozilla::mscom::ArrayData::Flag::eAllocatedByServer}
 };
 
 // Type libraries are thread-neutral, so we can register those from any
 // apartment. OTOH, proxies must be registered from within the apartment where
 // we intend to instantiate them. Therefore RegisterProxy() must be called
 // via EnsureMTA.
 PlatformChild::PlatformChild()
   : mAccTypelib(mozilla::mscom::RegisterTypelib(L"oleacc.dll",
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -319,21 +319,21 @@ function openLinkIn(url, where, params) 
 
   let uriObj;
   if (where == "current") {
     try {
       uriObj = Services.io.newURI(url, null, null);
     } catch (e) {}
   }
 
-  // NB: we avoid using |w| here because in the 'popup window' case,
+  // We avoid using |w| here because in the 'popup window' case,
   // if we pass a currentBrowser param |w.gBrowser| might not be the
   // tabbrowser that contains |aCurrentBrowser|. We really only care
   // about the tab linked to |aCurrentBrowser|.
-  let tab = aCurrentBrowser.ownerGlobal.gBrowser.getTabForBrowser(aCurrentBrowser);
+  let tab = aCurrentBrowser.getTabBrowser().getTabForBrowser(aCurrentBrowser);
   if (where == "current" && tab.pinned &&
       !aAllowPinnedTabHostChange) {
     try {
       // nsIURI.host can throw for non-nsStandardURL nsIURIs.
       if (!uriObj || (!uriObj.schemeIs("javascript") &&
                       aCurrentBrowser.currentURI.host != uriObj.host)) {
         where = "tab";
         loadInBackground = false;
@@ -343,16 +343,17 @@ function openLinkIn(url, where, params) 
       loadInBackground = false;
     }
   }
 
   // Raise the target window before loading the URI, since loading it may
   // result in a new frontmost window (e.g. "javascript:window.open('');").
   w.focus();
 
+  let browserUsedForLoad = null;
   switch (where) {
   case "current":
     let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
 
     if (aAllowThirdPartyFixup) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
     }
@@ -378,39 +379,45 @@ function openLinkIn(url, where, params) 
 
     aCurrentBrowser.loadURIWithFlags(url, {
       flags: flags,
       referrerURI: aNoReferrer ? null : aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       postData: aPostData,
       userContextId: aUserContextId
     });
+    browserUsedForLoad = aCurrentBrowser;
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
-    w.gBrowser.loadOneTab(url, {
+    let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
       referrerURI: aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipTabAnimation,
       allowMixedContent: aAllowMixedContent,
       noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aPrincipal,
     });
+    browserUsedForLoad = tabUsedForLoad.linkedBrowser;
     break;
   }
 
-  aCurrentBrowser.focus();
+  // Focus the content, but only if the browser used for the load is selected.
+  if (browserUsedForLoad &&
+      browserUsedForLoad == browserUsedForLoad.getTabBrowser().selectedBrowser) {
+    browserUsedForLoad.focus();
+  }
 
   if (!loadInBackground && w.isBlankPageURL(url)) {
     w.focusAndSelectUrlBar();
   }
 }
 
 // Used as an onclick handler for UI elements with link-like behavior.
 // e.g. onclick="checkForMiddleClick(this, event);"
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -1,21 +1,28 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+const {
+  SingletonEventManager,
+} = ExtensionUtils;
 
+XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
+                                  "resource://devtools/shared/event-emitter.js");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
+let listenerCount = 0;
+
 function getTree(rootGuid, onlyChildren) {
   function convert(node, parent) {
     let treenode = {
       id: node.guid,
       title: node.title || "",
       index: node.index,
       dateAdded: node.dateAdded / 1000,
     };
@@ -72,16 +79,113 @@ function convert(result) {
     node.url = result.url.href; // Output is always URL object.
   } else {
     node.dateGroupModified = result.lastModified.getTime();
   }
 
   return node;
 }
 
+let observer = {
+  skipTags: true,
+  skipDescendantsOnItemRemoval: true,
+
+  onBeginUpdateBatch() {},
+  onEndUpdateBatch() {},
+
+  onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
+    if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+      return;
+    }
+
+    let bookmark = {
+      id: guid,
+      parentId: parentGuid,
+      index,
+      title,
+      dateAdded: dateAdded / 1000,
+    };
+
+    if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+      bookmark.url = uri.spec;
+    } else {
+      bookmark.dateGroupModified = bookmark.dateAdded;
+    }
+
+    this.emit("created", bookmark);
+  },
+
+  onItemVisited() {},
+
+  onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
+    if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+      return;
+    }
+
+    let info = {
+      parentId: newParentGuid,
+      index: newIndex,
+      oldParentId: oldParentGuid,
+      oldIndex,
+    };
+    this.emit("moved", {guid, info});
+  },
+
+  onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid, source) {
+    if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+      return;
+    }
+
+    let node = {
+      id: guid,
+      parentId: parentGuid,
+      index,
+    };
+
+    if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+      node.url = uri.spec;
+    }
+
+    this.emit("removed", {guid, info: {parentId: parentGuid, index, node}});
+  },
+
+  onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal, source) {
+    if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+      return;
+    }
+
+    let info = {};
+    if (prop == "title") {
+      info.title = val;
+    } else if (prop == "uri") {
+      info.url = val;
+    } else {
+      // Not defined yet.
+      return;
+    }
+
+    this.emit("changed", {guid, info});
+  },
+};
+EventEmitter.decorate(observer);
+
+function decrementListeners() {
+  listenerCount -= 1;
+  if (!listenerCount) {
+    PlacesUtils.bookmarks.removeObserver(observer);
+  }
+}
+
+function incrementListeners() {
+  listenerCount++;
+  if (listenerCount == 1) {
+    PlacesUtils.bookmarks.addObserver(observer, false);
+  }
+}
+
 extensions.registerSchemaAPI("bookmarks", "addon_parent", context => {
   return {
     bookmarks: {
       get: function(idOrIdList) {
         let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
 
         return Task.spawn(function* () {
           let bookmarks = [];
@@ -208,11 +312,63 @@ extensions.registerSchemaAPI("bookmarks"
 
         try {
           return PlacesUtils.bookmarks.remove(info).then(result => {})
             .catch(error => Promise.reject({message: error.message}));
         } catch (e) {
           return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
+
+      onCreated: new SingletonEventManager(context, "bookmarks.onCreated", fire => {
+        let listener = (event, bookmark) => {
+          context.runSafe(fire, bookmark.id, bookmark);
+        };
+
+        observer.on("created", listener);
+        incrementListeners();
+        return () => {
+          observer.off("created", listener);
+          decrementListeners();
+        };
+      }).api(),
+
+      onRemoved: new SingletonEventManager(context, "bookmarks.onRemoved", fire => {
+        let listener = (event, data) => {
+          context.runSafe(fire, data.guid, data.info);
+        };
+
+        observer.on("removed", listener);
+        incrementListeners();
+        return () => {
+          observer.off("removed", listener);
+          decrementListeners();
+        };
+      }).api(),
+
+      onChanged: new SingletonEventManager(context, "bookmarks.onChanged", fire => {
+        let listener = (event, data) => {
+          context.runSafe(fire, data.guid, data.info);
+        };
+
+        observer.on("changed", listener);
+        incrementListeners();
+        return () => {
+          observer.off("changed", listener);
+          decrementListeners();
+        };
+      }).api(),
+
+      onMoved: new SingletonEventManager(context, "bookmarks.onMoved", fire => {
+        let listener = (event, data) => {
+          context.runSafe(fire, data.guid, data.info);
+        };
+
+        observer.on("moved", listener);
+        incrementListeners();
+        return () => {
+          observer.off("moved", listener);
+          decrementListeners();
+        };
+      }).api(),
     },
   };
 });
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -1,12 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+  promiseObserved,
+} = ExtensionUtils;
+
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
 function getRecentlyClosed(maxResults, extension) {
   let recentlyClosed = [];
 
   // Get closed windows
   let closedWindowData = SessionStore.getClosedWindowData(false);
@@ -26,19 +31,62 @@ function getRecentlyClosed(maxResults, e
     }
   }
 
   // Sort windows and tabs
   recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
   return recentlyClosed.slice(0, maxResults);
 }
 
+function createSession(restored, extension, sessionId) {
+  if (!restored) {
+    return Promise.reject({message: `Could not restore object using sessionId ${sessionId}.`});
+  }
+  let sessionObj = {lastModified: Date.now()};
+  if (restored instanceof Ci.nsIDOMChromeWindow) {
+    return promiseObserved("sessionstore-single-window-restored", subject => subject == restored).then(() => {
+      sessionObj.window = WindowManager.convert(extension, restored, {populate: true});
+      return Promise.resolve([sessionObj]);
+    });
+  }
+  sessionObj.tab = TabManager.for(extension).convert(restored);
+  return Promise.resolve([sessionObj]);
+}
+
 extensions.registerSchemaAPI("sessions", "addon_parent", context => {
   let {extension} = context;
   return {
     sessions: {
       getRecentlyClosed: function(filter) {
         let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
         return Promise.resolve(getRecentlyClosed(maxResults, extension));
       },
+      restore: function(sessionId) {
+        let session, closedId;
+        if (sessionId) {
+          closedId = sessionId;
+          session = SessionStore.undoCloseById(closedId);
+        } else if (SessionStore.lastClosedObjectType == "window") {
+          // If the most recently closed object is a window, just undo closing the most recent window.
+          session = SessionStore.undoCloseWindow(0);
+        } else {
+          // It is a tab, and we cannot call SessionStore.undoCloseTab without a window,
+          // so we must find the tab in which case we can just use its closedId.
+          let recentlyClosedTabs = [];
+          for (let window of WindowListManager.browserWindows()) {
+            let closedTabData = SessionStore.getClosedTabData(window, false);
+            for (let tab of closedTabData) {
+              recentlyClosedTabs.push(tab);
+            }
+          }
+
+          // Sort the tabs.
+          recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
+
+          // Use the closedId of the most recently closed tab to restore it.
+          closedId = recentlyClosedTabs[0].closedId;
+          session = SessionStore.undoCloseById(closedId);
+        }
+        return createSession(session, extension, closedId);
+      },
     },
   };
 });
--- a/browser/components/extensions/schemas/bookmarks.json
+++ b/browser/components/extensions/schemas/bookmarks.json
@@ -446,33 +446,31 @@
             "parameters": []
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onCreated",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a bookmark or folder is created.",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "$ref": "BookmarkTreeNode",
             "name": "bookmark"
           }
         ]
       },
       {
         "name": "onRemoved",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a bookmark or folder is removed.  When a folder is removed recursively, a single notification is fired for the folder, and none for its contents.",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
@@ -483,17 +481,16 @@
               "index": { "type": "integer" },
               "node": { "$ref": "BookmarkTreeNode" }
             }
           }
         ]
       },
       {
         "name": "onChanged",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a bookmark or folder changes.  <b>Note:</b> Currently, only title and url changes trigger this.",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
@@ -506,17 +503,16 @@
                 "optional": true
               }
             }
           }
         ]
       },
       {
         "name": "onMoved",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a bookmark or folder is moved to a different parent folder.",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
--- a/browser/components/extensions/schemas/sessions.json
+++ b/browser/components/extensions/schemas/sessions.json
@@ -98,17 +98,16 @@
                 "name": "devices", "type": "array", "items": { "$ref": "Device" }, "description": "The list of $(ref:sessions.Device) objects for each synced session, sorted in order from device with most recently modified session to device with least recently modified session. $(ref:tabs.Tab) objects are sorted by recency in the $(ref:windows.Window) of the $(ref:sessions.Session) objects."
               }
             ]
           }
         ]
       },
       {
         "name": "restore",
-        "unsupported": true,
         "type": "function",
         "description": "Reopens a $(ref:windows.Window) or $(ref:tabs.Tab), with an optional callback to run when the entry has been restored.",
         "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "sessionId",
             "optional": true,
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -56,16 +56,17 @@ tags = webextensions
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
 [browser_ext_popup_shutdown.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
+[browser_ext_sessions_restore.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_detectLanguage.js]
 [browser_ext_tabs_duplicate.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
@@ -0,0 +1,134 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+                                  "resource:///modules/sessionstore/SessionStore.jsm");
+
+add_task(function* test_sessions_restore() {
+  function background() {
+    browser.test.onMessage.addListener((msg, data) => {
+      if (msg == "check-sessions") {
+        browser.sessions.getRecentlyClosed().then(recentlyClosed => {
+          browser.test.sendMessage("recentlyClosed", recentlyClosed);
+        });
+      } else if (msg == "restore") {
+        browser.sessions.restore(data).then(sessions => {
+          browser.test.sendMessage("restored", sessions);
+        });
+      } else if (msg == "restore-reject") {
+        browser.sessions.restore("not-a-valid-session-id").then(
+          sessions => {
+            browser.test.fail("restore rejected with an invalid sessionId");
+          },
+          error => {
+            browser.test.assertTrue(
+              error.message.includes("Could not restore object using sessionId not-a-valid-session-id."));
+            browser.test.sendMessage("restore-rejected");
+          }
+        );
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  yield extension.startup();
+
+  let {Management: {global: {WindowManager, TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+  function checkLocalTab(tab, expectedUrl) {
+    let realTab = TabManager.getTab(tab.id);
+    let tabState = JSON.parse(SessionStore.getTabState(realTab));
+    is(tabState.entries[0].url, expectedUrl, "restored tab has the expected url");
+  }
+
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:config");
+  yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+  for (let url of ["about:robots", "about:mozilla"]) {
+    yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+  }
+  yield BrowserTestUtils.closeWindow(win);
+
+  extension.sendMessage("check-sessions");
+  let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+
+  // Check that our expected window is the most recently closed.
+  is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
+
+  // Restore the window.
+  extension.sendMessage("restore");
+  let restored = yield extension.awaitMessage("restored");
+
+  is(restored.length, 1, "restore returned the expected number of sessions");
+  is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
+  checkLocalTab(restored[0].window.tabs[0], "about:config");
+  checkLocalTab(restored[0].window.tabs[1], "about:robots");
+  checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
+
+  // Close the window again.
+  let window = WindowManager.getWindow(restored[0].window.id);
+  yield BrowserTestUtils.closeWindow(window);
+
+  // Restore the window using the sessionId.
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
+  restored = yield extension.awaitMessage("restored");
+
+  is(restored.length, 1, "restore returned the expected number of sessions");
+  is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
+  checkLocalTab(restored[0].window.tabs[0], "about:config");
+  checkLocalTab(restored[0].window.tabs[1], "about:robots");
+  checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
+
+  // Close the window again.
+  window = WindowManager.getWindow(restored[0].window.id);
+  yield BrowserTestUtils.closeWindow(window);
+
+  // Open and close a tab.
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+  yield BrowserTestUtils.removeTab(tab);
+
+  // Restore the most recently closed item.
+  extension.sendMessage("restore");
+  restored = yield extension.awaitMessage("restored");
+
+  is(restored.length, 1, "restore returned the expected number of sessions");
+  tab = restored[0].tab;
+  ok(tab, "restore returned a tab");
+  checkLocalTab(tab, "about:robots");
+
+  // Close the tab again.
+  let realTab = TabManager.getTab(tab.id);
+  yield BrowserTestUtils.removeTab(realTab);
+
+  // Restore the tab using the sessionId.
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
+  restored = yield extension.awaitMessage("restored");
+
+  is(restored.length, 1, "restore returned the expected number of sessions");
+  tab = restored[0].tab;
+  ok(tab, "restore returned a tab");
+  checkLocalTab(tab, "about:robots");
+
+  // Close the tab again.
+  realTab = TabManager.getTab(tab.id);
+  yield BrowserTestUtils.removeTab(realTab);
+
+  // Try to restore something with an invalid sessionId.
+  extension.sendMessage("restore-reject");
+  restored = yield extension.awaitMessage("restore-rejected");
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
@@ -1,16 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function backgroundScript() {
   let unsortedId, ourId;
   let initialBookmarkCount = 0;
   let createdBookmarks = new Set();
+  let createdFolderId;
+  let collectedEvents = [];
   const nonExistentId = "000000000000";
   const bookmarkGuids = {
     menuGuid:    "menu________",
     toolbarGuid: "toolbar_____",
     unfiledGuid: "unfiled_____",
   };
 
   function checkOurBookmark(bookmark) {
@@ -32,16 +34,83 @@ function backgroundScript() {
       browser.test.assertEq(expected.parentId, bookmark.parentId, "Bookmark has the expected parentId");
     }
   }
 
   function expectedError() {
     browser.test.fail("Did not get expected error");
   }
 
+  function checkOnCreated(id, parentId, index, title, url, dateAdded) {
+    let createdData = collectedEvents.pop();
+    browser.test.assertEq("onCreated", createdData.event, "onCreated was the last event received");
+    browser.test.assertEq(id, createdData.id, "onCreated event received the expected id");
+    let bookmark = createdData.bookmark;
+    browser.test.assertEq(id, bookmark.id, "onCreated event received the expected bookmark id");
+    browser.test.assertEq(parentId, bookmark.parentId, "onCreated event received the expected bookmark parentId");
+    browser.test.assertEq(index, bookmark.index, "onCreated event received the expected bookmark index");
+    browser.test.assertEq(title, bookmark.title, "onCreated event received the expected bookmark title");
+    browser.test.assertEq(url, bookmark.url, "onCreated event received the expected bookmark url");
+    browser.test.assertEq(dateAdded, bookmark.dateAdded, "onCreated event received the expected bookmark dateAdded");
+  }
+
+  function checkOnChanged(id, url, title) {
+    // If both url and title are changed, then url is fired last.
+    let changedData = collectedEvents.pop();
+    browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+    browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+    browser.test.assertEq(url, changedData.info.url, "onChanged event received the expected url");
+    // title is fired first.
+    changedData = collectedEvents.pop();
+    browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+    browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+    browser.test.assertEq(title, changedData.info.title, "onChanged event received the expected title");
+  }
+
+  function checkOnMoved(id, parentId, oldParentId, index, oldIndex) {
+    let movedData = collectedEvents.pop();
+    browser.test.assertEq("onMoved", movedData.event, "onMoved was the last event received");
+    browser.test.assertEq(id, movedData.id, "onMoved event received the expected id");
+    let info = movedData.info;
+    browser.test.assertEq(parentId, info.parentId, "onMoved event received the expected parentId");
+    browser.test.assertEq(oldParentId, info.oldParentId, "onMoved event received the expected oldParentId");
+    browser.test.assertEq(index, info.index, "onMoved event received the expected index");
+    browser.test.assertEq(oldIndex, info.oldIndex, "onMoved event received the expected oldIndex");
+  }
+
+  function checkOnRemoved(id, parentId, index, url) {
+    let removedData = collectedEvents.pop();
+    browser.test.assertEq("onRemoved", removedData.event, "onRemoved was the last event received");
+    browser.test.assertEq(id, removedData.id, "onRemoved event received the expected id");
+    let info = removedData.info;
+    browser.test.assertEq(parentId, removedData.info.parentId, "onRemoved event received the expected parentId");
+    browser.test.assertEq(index, removedData.info.index, "onRemoved event received the expected index");
+    let node = info.node;
+    browser.test.assertEq(id, node.id, "onRemoved event received the expected node id");
+    browser.test.assertEq(parentId, node.parentId, "onRemoved event received the expected node parentId");
+    browser.test.assertEq(index, node.index, "onRemoved event received the expected node index");
+    browser.test.assertEq(url, node.url, "onRemoved event received the expected node url");
+  }
+
+  browser.bookmarks.onChanged.addListener((id, info) => {
+    collectedEvents.push({event: "onChanged", id, info});
+  });
+
+  browser.bookmarks.onCreated.addListener((id, bookmark) => {
+    collectedEvents.push({event: "onCreated", id, bookmark});
+  });
+
+  browser.bookmarks.onMoved.addListener((id, info) => {
+    collectedEvents.push({event: "onMoved", id, info});
+  });
+
+  browser.bookmarks.onRemoved.addListener((id, info) => {
+    collectedEvents.push({event: "onRemoved", id, info});
+  });
+
   browser.bookmarks.get(["not-a-bookmark-guid"]).then(expectedError, error => {
     browser.test.assertTrue(
       error.message.includes("Invalid value for property 'guid': not-a-bookmark-guid"),
       "Expected error thrown when trying to get a bookmark using an invalid guid"
     );
 
     return browser.bookmarks.get([nonExistentId]).then(expectedError, error => {
       browser.test.assertTrue(
@@ -52,16 +121,18 @@ function backgroundScript() {
   }).then(() => {
     return browser.bookmarks.search({});
   }).then(results => {
     initialBookmarkCount = results.length;
     return browser.bookmarks.create({title: "test bookmark", url: "http://example.org"});
   }).then(result => {
     ourId = result.id;
     checkOurBookmark(result);
+    browser.test.assertEq(1, collectedEvents.length, "1 expected event received");
+    checkOnCreated(ourId, bookmarkGuids.unfiledGuid, 0, "test bookmark", "http://example.org/", result.dateAdded);
 
     return browser.bookmarks.get(ourId);
   }).then(results => {
     browser.test.assertEq(results.length, 1);
     checkOurBookmark(results[0]);
 
     unsortedId = results[0].parentId;
     return browser.bookmarks.get(unsortedId);
@@ -91,16 +162,19 @@ function backgroundScript() {
 
       return browser.bookmarks.update(ourId, {title: "new test title", url: "http://example.com/"});
     });
   }).then(result => {
     browser.test.assertEq("new test title", result.title, "Updated bookmark has the expected title");
     browser.test.assertEq("http://example.com/", result.url, "Updated bookmark has the expected URL");
     browser.test.assertEq(ourId, result.id, "Updated bookmark has the expected id");
 
+    browser.test.assertEq(2, collectedEvents.length, "2 expected events received");
+    checkOnChanged(ourId, "http://example.com/", "new test title");
+
     return Promise.resolve().then(() => {
       return browser.bookmarks.update(ourId, {url: "this is not a valid url"});
     }).then(expectedError, error => {
       browser.test.assertTrue(
         error.message.includes("Invalid bookmark:"),
         "Expected error thrown when trying update with an invalid url"
       );
       return browser.bookmarks.getTree();
@@ -124,58 +198,78 @@ function backgroundScript() {
           "Expected error thrown when trying to create a bookmark with an invalid parentId"
       );
     });
   }).then(() => {
     return browser.bookmarks.remove(ourId);
   }).then(result => {
     browser.test.assertEq(undefined, result, "Removing a bookmark returns undefined");
 
+    browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+    checkOnRemoved(ourId, bookmarkGuids.unfiledGuid, 0, "http://example.com/");
+
     return browser.bookmarks.get(ourId).then(expectedError, error => {
       browser.test.assertTrue(
         error.message.includes("Bookmark not found"),
         "Expected error thrown when trying to get a removed bookmark"
       );
     });
   }).then(() => {
     return browser.bookmarks.remove(nonExistentId).then(expectedError, error => {
       browser.test.assertTrue(
         error.message.includes("No bookmarks found for the provided GUID"),
         "Expected error thrown when trying removed a non-existent bookmark"
       );
     });
   }).then(() => {
     // test bookmarks.search
     return Promise.all([
-      browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg"}),
-      browser.bookmarks.create({title: "Example", url: "http://example.org"}),
+      browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg/"}),
+      browser.bookmarks.create({title: "Example", url: "http://example.org/"}),
       browser.bookmarks.create({title: "Mozilla Folder"}),
-      browser.bookmarks.create({title: "EFF", url: "http://eff.org"}),
-      browser.bookmarks.create({title: "Menu Item", url: "http://menu.org", parentId: bookmarkGuids.menuGuid}),
-      browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org", parentId: bookmarkGuids.toolbarGuid}),
+      browser.bookmarks.create({title: "EFF", url: "http://eff.org/"}),
+      browser.bookmarks.create({title: "Menu Item", url: "http://menu.org/", parentId: bookmarkGuids.menuGuid}),
+      browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org/", parentId: bookmarkGuids.toolbarGuid}),
     ]);
   }).then(results => {
+    browser.test.assertEq(6, collectedEvents.length, "6 expected events received");
+    checkOnCreated(results[5].id, bookmarkGuids.toolbarGuid, 0, "Toolbar Item", "http://toolbar.org/", results[5].dateAdded);
+    checkOnCreated(results[4].id, bookmarkGuids.menuGuid, 0, "Menu Item", "http://menu.org/", results[4].dateAdded);
+    checkOnCreated(results[3].id, bookmarkGuids.unfiledGuid, 0, "EFF", "http://eff.org/", results[3].dateAdded);
+    checkOnCreated(results[2].id, bookmarkGuids.unfiledGuid, 0, "Mozilla Folder", undefined, results[2].dateAdded);
+    checkOnCreated(results[1].id, bookmarkGuids.unfiledGuid, 0, "Example", "http://example.org/", results[1].dateAdded);
+    checkOnCreated(results[0].id, bookmarkGuids.unfiledGuid, 0, "MØzillä", "http://møzîllä.örg/", results[0].dateAdded);
+
     for (let result of results) {
       if (result.title !== "Mozilla Folder") {
         createdBookmarks.add(result.id);
       }
     }
-    let createdFolderId = results[2].id;
+    let folderResult = results[2];
+    createdFolderId = folderResult.id;
     return Promise.all([
-      browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org", parentId: createdFolderId}),
-      browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com", parentId: createdFolderId}),
-      browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox", parentId: createdFolderId}),
+      browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org/", parentId: createdFolderId}),
+      browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com/", parentId: createdFolderId}),
+      browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox/", parentId: createdFolderId}),
     ]).then(results => {
+      browser.test.assertEq(3, collectedEvents.length, "3 expected events received");
+      checkOnCreated(results[2].id, createdFolderId, 0, "Firefox", "http://allizom.org/firefox/", results[2].dateAdded);
+      checkOnCreated(results[1].id, createdFolderId, 0, "Mozilla Corporation", "http://allizom.com/", results[1].dateAdded);
+      checkOnCreated(results[0].id, createdFolderId, 0, "Mozilla", "http://allizom.org/", results[0].dateAdded);
+
       return browser.bookmarks.create({
         title: "About Mozilla",
-        url: "http://allizom.org/about",
+        url: "http://allizom.org/about/",
         parentId: createdFolderId,
         index: 1,
       });
-    }).then(() => {
+    }).then(result => {
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnCreated(result.id, createdFolderId, 1, "About Mozilla", "http://allizom.org/about/", result.dateAdded);
+
       // returns all items on empty object
       return browser.bookmarks.search({});
     }).then(results => {
       browser.test.assertTrue(results.length >= 9, "At least as many bookmarks as added were returned by search({})");
 
       return Promise.resolve().then(() => {
         return browser.bookmarks.remove(createdFolderId);
       }).then(expectedError, error => {
@@ -370,30 +464,43 @@ function backgroundScript() {
     let corporationBookmark = results[0][0];
     let childCount = results[1].length;
 
     browser.test.assertEq(2, corporationBookmark.index, "Bookmark has the expected index");
 
     return browser.bookmarks.move(corporationBookmark.id, {index: 0}).then(result => {
       browser.test.assertEq(0, result.index, "Bookmark has the expected index");
 
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnMoved(corporationBookmark.id, createdFolderId, createdFolderId, 0, 2);
+
       return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
     }).then(result => {
       browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
       browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
 
-      return browser.bookmarks.move(corporationBookmark.id, {index: 1});
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, createdFolderId, 1, 0);
+
+      return browser.bookmarks.move(corporationBookmark.id, {index: 0});
     }).then(result => {
       browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
-      browser.test.assertEq(1, result.index, "Bookmark has the expected index");
+      browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, bookmarkGuids.menuGuid, 0, 1);
 
       return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.toolbarGuid, index: 1});
     }).then(result => {
       browser.test.assertEq(bookmarkGuids.toolbarGuid, result.parentId, "Bookmark has the expected parent");
       browser.test.assertEq(1, result.index, "Bookmark has the expected index");
+
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnMoved(corporationBookmark.id, bookmarkGuids.toolbarGuid, bookmarkGuids.menuGuid, 1, 0);
+
       createdBookmarks.add(corporationBookmark.id);
     });
   }).then(() => {
     return browser.bookmarks.getRecent(4);
   }).then(results => {
     browser.test.assertEq(4, results.length, "Expected number of results returned by getRecent");
     let prevDate = results[0].dateAdded;
     for (let bookmark of results) {
@@ -410,29 +517,39 @@ function backgroundScript() {
 
     return browser.bookmarks.search({});
   }).then(results => {
     let startBookmarkCount = results.length;
 
     return browser.bookmarks.search({title: "Mozilla Folder"}).then(result => {
       return browser.bookmarks.removeTree(result[0].id);
     }).then(() => {
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnRemoved(createdFolderId, bookmarkGuids.unfiledGuid, 1);
+
       return browser.bookmarks.search({}).then(results => {
         browser.test.assertEq(
           startBookmarkCount - 4,
           results.length,
           "Expected number of results returned after removeTree");
       });
     });
   }).then(() => {
     return browser.bookmarks.create({title: "Empty Folder"});
   }).then(result => {
     let emptyFolderId = result.id;
+
+    browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+    checkOnCreated(emptyFolderId, bookmarkGuids.unfiledGuid, 3, "Empty Folder", undefined, result.dateAdded);
+
     browser.test.assertEq("Empty Folder", result.title, "Folder has the expected title");
     return browser.bookmarks.remove(emptyFolderId).then(() => {
+      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+      checkOnRemoved(emptyFolderId, bookmarkGuids.unfiledGuid, 3);
+
       return browser.bookmarks.get(emptyFolderId).then(expectedError, error => {
         browser.test.assertTrue(
           error.message.includes("Bookmark not found"),
           "Expected error thrown when trying to get a removed folder"
         );
       });
     });
   }).then(() => {
@@ -451,19 +568,22 @@ function backgroundScript() {
         "Expected error thrown when calling move with a non-existent bookmark"
       );
     });
   }).then(() => {
     // remove all created bookmarks
     let promises = Array.from(createdBookmarks, guid => browser.bookmarks.remove(guid));
     return Promise.all(promises);
   }).then(() => {
+    browser.test.assertEq(createdBookmarks.size, collectedEvents.length, "expected number of events received");
+
     return browser.bookmarks.search({});
   }).then(results => {
     browser.test.assertEq(initialBookmarkCount, results.length, "All created bookmarks have been removed");
+
     return browser.test.notifyPass("bookmarks");
   }).catch(error => {
     browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);
     browser.test.notifyFail("bookmarks");
   });
 }
 
 let extensionData = {
--- a/browser/components/search/test/browser_oneOffContextMenu.js
+++ b/browser/components/search/test/browser_oneOffContextMenu.js
@@ -65,16 +65,22 @@ function* doTest() {
   });
   yield promise;
 
   // Click the Search in New Tab menu item.
   promise = BrowserTestUtils.waitForNewTab(gBrowser);
   EventUtils.synthesizeMouseAtCenter(searchInNewTabMenuItem, {});
   let tab = yield promise;
 
+  // By default the search will open in the background and the popup will stay open:
+  promise = promiseEvent(searchPopup, "popuphidden");
+  info("Closing search panel");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+
   // Check the loaded tab.
   Assert.equal(tab.linkedBrowser.currentURI.spec,
                "http://mochi.test:8888/browser/browser/components/search/test/",
                "Expected search tab should have loaded");
 
   yield BrowserTestUtils.removeTab(tab);
 
   // Move the cursor out of the panel area to avoid messing with other tests.
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/FormAutofillParent.jsm
@@ -0,0 +1,173 @@
+/* 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/. */
+
+/*
+ * Implements a service used to access storage and communicate with content.
+ *
+ * A "fields" array is used to communicate with FormAutofillContent. Each item
+ * represents a single input field in the content page as well as its
+ * @autocomplete properties. The schema is as below. Please refer to
+ * FormAutofillContent.jsm for more details.
+ *
+ * [
+ *   {
+ *     section,
+ *     addressType,
+ *     contactType,
+ *     fieldName,
+ *     value,
+ *     index
+ *   },
+ *   {
+ *     // ...
+ *   }
+ * ]
+ */
+
+/* exported FormAutofillParent */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileStorage",
+                                  "resource://formautofill/ProfileStorage.jsm");
+
+const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
+
+let FormAutofillParent = {
+  _profileStore: null,
+
+  /**
+   * Initializes ProfileStorage and registers the message handler.
+   */
+  init: function() {
+    let storePath =
+      OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
+
+    this._profileStore = new ProfileStorage(storePath);
+    this._profileStore.initialize();
+
+    let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+               .getService(Ci.nsIMessageListenerManager);
+    mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
+  },
+
+  /**
+   * Handles the message coming from FormAutofillContent.
+   *
+   * @param   {string} message.name The name of the message.
+   * @param   {object} message.data The data of the message.
+   * @param   {nsIFrameMessageManager} message.target Caller's message manager.
+   */
+  receiveMessage: function({name, data, target}) {
+    switch (name) {
+      case "FormAutofill:PopulateFieldValues":
+        this._populateFieldValues(data, target);
+        break;
+    }
+  },
+
+  /**
+   * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
+   * who needs to access the profile should request the instance by this instead
+   * of creating a new one.
+   *
+   * @returns {ProfileStorage}
+   */
+  getProfileStore: function() {
+    return this._profileStore;
+  },
+
+  /**
+   * Uninitializes FormAutofillParent. This is for testing only.
+   *
+   * @private
+   */
+  _uninit: function() {
+    if (this._profileStore) {
+      this._profileStore._saveImmediately();
+      this._profileStore = null;
+    }
+
+    let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+               .getService(Ci.nsIMessageListenerManager);
+    mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
+  },
+
+  /**
+   * Populates the field values and notifies content to fill in. Exception will
+   * be thrown if there's no matching profile.
+   *
+   * @private
+   * @param  {string} data.guid
+   *         Indicates which profile to populate
+   * @param  {Fields} data.fields
+   *         The "fields" array collected from content.
+   * @param  {nsIFrameMessageManager} target
+   *         Content's message manager.
+   */
+  _populateFieldValues({guid, fields}, target) {
+    this._profileStore.notifyUsed(guid);
+    this._fillInFields(this._profileStore.get(guid), fields);
+    target.sendAsyncMessage("FormAutofill:fillForm", {fields});
+  },
+
+  /**
+   * Transforms a word with hyphen into camel case.
+   * (e.g. transforms "address-type" into "addressType".)
+   *
+   * @private
+   * @param   {string} str The original string with hyphen.
+   * @returns {string} The camel-cased output string.
+   */
+  _camelCase(str) {
+    return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
+  },
+
+  /**
+   * Get the corresponding value from the specified profile according to a valid
+   * @autocomplete field name.
+   *
+   * Note that the field name doesn't need to match the property name defined in
+   * Profile object. This method can transform the raw data to fulfill it. (e.g.
+   * inputting "country-name" as "fieldName" will get a full name transformed
+   * from the country code that is recorded in "country" field.)
+   *
+   * @private
+   * @param   {Profile} profile   The specified profile.
+   * @param   {string}  fieldName A valid @autocomplete field name.
+   * @returns {string}  The corresponding value. Returns "undefined" if there's
+   *                    no matching field.
+   */
+  _getDataByFieldName(profile, fieldName) {
+    let key = this._camelCase(fieldName);
+
+    // TODO: Transform the raw profile data to fulfill "fieldName" here.
+
+    return profile[key];
+  },
+
+  /**
+   * Fills in the "fields" array by the specified profile.
+   *
+   * @private
+   * @param   {Profile} profile The specified profile to fill in.
+   * @param   {Fields}  fields  The "fields" array collected from content.
+   */
+  _fillInFields(profile, fields) {
+    for (let field of fields) {
+      let value = this._getDataByFieldName(profile, field.fieldName);
+      if (value !== undefined) {
+        field.value = value;
+      }
+    }
+  },
+};
+
+this.EXPORTED_SYMBOLS = ["FormAutofillParent"];
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -7,16 +7,26 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://testing-common/MockDocument.jsm");
 
+// Redirect the path of the resouce in addon to the exact file path.
+let defineLazyModuleGetter = XPCOMUtils.defineLazyModuleGetter;
+XPCOMUtils.defineLazyModuleGetter = function() {
+  let result = /^resource\:\/\/formautofill\/(.+)$/.exec(arguments[2]);
+  if (result) {
+    arguments[2] = Services.io.newFileURI(do_get_file(result[1])).spec;
+  }
+  return defineLazyModuleGetter.apply(this, arguments);
+};
+
 // Load the module by Service newFileURI API for running extension's XPCShell test
 function importAutofillModule(module) {
   return Cu.import(Services.io.newFileURI(do_get_file(module)).spec);
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_populateFieldValues.js
@@ -0,0 +1,106 @@
+/*
+ * Test for populating field values in Form Autofill Parent.
+ */
+
+/* global FormAutofillParent */
+
+"use strict";
+
+importAutofillModule("FormAutofillParent.jsm");
+
+do_get_profile();
+
+const TEST_FIELDS = [
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "organization"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "postal-code"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+  {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+];
+
+const TEST_GUID = "test-guid";
+
+const TEST_PROFILE = {
+  guid: TEST_GUID,
+  organization: "World Wide Web Consortium",
+  streetAddress: "32 Vassar Street\nMIT Room 32-G524",
+  addressLevel2: "Cambridge",
+  addressLevel1: "MA",
+  postalCode: "02139",
+  country: "US",
+  tel: "+1 617 253 5702",
+  email: "timbl@w3.org",
+};
+
+function camelCase(str) {
+  return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
+}
+
+add_task(function* test_populateFieldValues() {
+  FormAutofillParent.init();
+
+  let store = FormAutofillParent.getProfileStore();
+  do_check_neq(store, null);
+
+  store.get = function(guid) {
+    do_check_eq(guid, TEST_GUID);
+    return store._clone(TEST_PROFILE);
+  };
+
+  let notifyUsedCalledCount = 0;
+  store.notifyUsed = function(guid) {
+    do_check_eq(guid, TEST_GUID);
+    notifyUsedCalledCount++;
+  };
+
+  yield new Promise((resolve) => {
+    FormAutofillParent.receiveMessage({
+      name: "FormAutofill:PopulateFieldValues",
+      data: {
+        guid: TEST_GUID,
+        fields: TEST_FIELDS,
+      },
+      target: {
+        sendAsyncMessage: function(name, data) {
+          do_check_eq(name, "FormAutofill:fillForm");
+
+          let fields = data.fields;
+          do_check_eq(fields.length, TEST_FIELDS.length);
+
+          for (let i = 0; i < fields.length; i++) {
+            do_check_eq(fields[i].fieldName, TEST_FIELDS[i].fieldName);
+            do_check_eq(fields[i].value,
+              TEST_PROFILE[camelCase(fields[i].fieldName)]);
+          }
+
+          resolve();
+        },
+      },
+    });
+  });
+
+  do_check_eq(notifyUsedCalledCount, 1);
+
+  FormAutofillParent._uninit();
+  do_check_null(FormAutofillParent.getProfileStore());
+});
+
+add_task(function* test_populateFieldValues_with_invalid_guid() {
+  FormAutofillParent.init();
+
+  Assert.throws(() => {
+    FormAutofillParent.receiveMessage({
+      name: "FormAutofill:PopulateFieldValues",
+      data: {
+        guid: "invalid-guid",
+        fields: TEST_FIELDS,
+      },
+      target: {},
+    });
+  }, /No matching profile\./);
+
+  FormAutofillParent._uninit();
+});
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 head = head.js
 tail =
 support-files =
   ../../content/FormAutofillContent.jsm
+  ../../content/FormAutofillParent.jsm
   ../../content/ProfileStorage.jsm
 
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
+[test_populateFieldValues.js]
 [test_profileStorage.js]
--- a/browser/installer/allowed-dupes.mn
+++ b/browser/installer/allowed-dupes.mn
@@ -215,8 +215,12 @@ modules/commonjs/sdk/ui/button/view/even
 modules/commonjs/sdk/ui/state/events.js
 plugin-container.app/Contents/PkgInfo
 res/table-remove-column-active.gif
 res/table-remove-column-hover.gif
 res/table-remove-column.gif
 res/table-remove-row-active.gif
 res/table-remove-row-hover.gif
 res/table-remove-row.gif
+# Aurora branding
+browser/chrome/browser/content/browser/defaultthemes/devedition.icon.png
+browser/chrome/browser/content/branding/icon64.png
+browser/chrome/devtools/content/framework/dev-edition-promo/dev-edition-logo.png
--- a/config/recurse.mk
+++ b/config/recurse.mk
@@ -167,14 +167,14 @@ endif
 ifdef MOZ_LDAP_XPCOM
 ldap/target: config/external/nss/target mozglue/build/target
 toolkit/library/target: ldap/target
 endif
 ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library)
 mozglue/build/target memory/replace/logalloc/replay/target: memory/replace/dummy/target
 endif
 endif
-ifeq (,$(MOZ_SYSTEM_NSS)$(MOZ_FOLD_LIBS))
+ifeq (,$(MOZ_SYSTEM_NSPR)$(MOZ_SYSTEM_NSS)$(MOZ_FOLD_LIBS))
 config/external/nss/target: config/external/nspr/pr/target config/external/nspr/ds/target config/external/nspr/libc/target
 endif
 # Most things are built during compile (target/host), but some things happen during export
 # Those need to depend on config/export for system wrappers.
 $(addprefix build/unix/stdc++compat/,target host) build/clang-plugin/target: config/export
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -916,16 +916,25 @@ endif
 ifdef MOZ_CARGO_SUPPORTS_FROZEN
 cargo_build_flags += --frozen
 endif
 
 cargo_build_flags += --manifest-path $(CARGO_FILE)
 cargo_build_flags += --target=$(RUST_TARGET)
 cargo_build_flags += --verbose
 
+# Enable color output if original stdout was a TTY and color settings
+# aren't already present. This essentially restores the default behavior
+# of cargo when running via `mach`.
+ifdef MACH_STDOUT_ISATTY
+ifeq (,$(findstring --color,$(cargo_build_flags)))
+cargo_build_flags += --color=always
+endif
+endif
+
 # Assume any system libraries rustc links against are already in the target's LIBS.
 #
 # We need to run cargo unconditionally, because cargo is the only thing that
 # has full visibility into how changes in Rust sources might affect the final
 # build.
 force-cargo-build:
 	$(REPORT_BUILD)
 	env CARGO_TARGET_DIR=. RUSTC=$(RUSTC) $(CARGO) build $(cargo_build_flags) --
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -25,70 +25,83 @@ define(function (require, exports, modul
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
       provider: React.PropTypes.object,
     },
 
     getLength: function (grip) {
-      return grip.preview ? grip.preview.length : 0;
+      if (!grip.preview) {
+        return 0;
+      }
+
+      return grip.preview.length || grip.preview.childNodesLength || 0;
     },
 
     getTitle: function (object, context) {
       let objectLink = this.props.objectLink || span;
       if (this.props.mode != "tiny") {
         return objectLink({
           object: object
         }, object.class + " ");
       }
       return "";
     },
 
+    getPreviewItems: function (grip) {
+      if (!grip.preview) {
+        return null;
+      }
+
+      return grip.preview.items || grip.preview.childNodes || null;
+    },
+
     arrayIterator: function (grip, max) {
       let items = [];
+      const gripLength = this.getLength(grip);
 
-      if (!grip.preview || !grip.preview.length) {
+      if (!gripLength) {
         return items;
       }
 
-      let array = grip.preview.items;
-      if (!array) {
+      const previewItems = this.getPreviewItems(grip);
+      if (!previewItems) {
         return items;
       }
 
       let delim;
-      // number of grip.preview.items is limited to 10, but we may have more
-      // items in grip-array
-      let delimMax = grip.preview.length > array.length ?
-        array.length : array.length - 1;
+      // number of grip preview items is limited to 10, but we may have more
+      // items in grip-array.
+      let delimMax = gripLength > previewItems.length ?
+        previewItems.length : previewItems.length - 1;
       let provider = this.props.provider;
 
-      for (let i = 0; i < array.length && i < max; i++) {
+      for (let i = 0; i < previewItems.length && i < max; i++) {
         try {
-          let itemGrip = array[i];
+          let itemGrip = previewItems[i];
           let value = provider ? provider.getValue(itemGrip) : itemGrip;
 
           delim = (i == delimMax ? "" : ", ");
 
           items.push(GripArrayItem(Object.assign({}, this.props, {
             object: value,
             delim: delim
           })));
         } catch (exc) {
           items.push(GripArrayItem(Object.assign({}, this.props, {
             object: exc,
             delim: delim
           })));
         }
       }
-      if (array.length > max || grip.preview.length > array.length) {
+      if (previewItems.length > max || gripLength > previewItems.length) {
         let objectLink = this.props.objectLink || span;
-        let leftItemNum = grip.preview.length - max > 0 ?
-          grip.preview.length - max : grip.preview.length - array.length;
+        let leftItemNum = gripLength - max > 0 ?
+          gripLength - max : gripLength - previewItems.length;
         items.push(Caption({
           object: objectLink({
             object: this.props.object
           }, leftItemNum + " more…")
         }));
       }
 
       return items;
@@ -165,17 +178,21 @@ define(function (require, exports, modul
     }
   }));
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
 
-    return (grip.preview && grip.preview.kind == "ArrayLike");
+    return (grip.preview && (
+        grip.preview.kind == "ArrayLike" ||
+        type === "DocumentFragment"
+      )
+    );
   }
 
   // Exports from this module
   exports.GripArray = {
     rep: GripArray,
     supportsObject: supportsObject
   };
 });
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -32,16 +32,17 @@ window.onload = Task.async(function* () 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
     yield testPreviewLimit();
     yield testNamedNodeMap();
     yield testNodeList();
+    yield testDocumentFragment();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test array: `[]`
@@ -267,16 +268,48 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testDocumentFragment() {
+    const testName = "testDocumentFragment";
+
+    const defaultOutput = "DocumentFragment [ li#li-0.list-element, " +
+      "li#li-1.list-element, li#li-2.list-element, 2 more… ]";
+
+    const longOutput = "DocumentFragment [ " +
+      "li#li-0.list-element, li#li-1.list-element, li#li-2.list-element, " +
+      "li#li-3.list-element, li#li-4.list-element ]";
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[5]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: longOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Array",
           "actor": "server1.conn0.obj35",
           "extensible": true,
@@ -407,77 +440,78 @@ window.onload = Task.async(function* () 
                   "kind": "ArrayLike",
                   "length": 1
                 }
               }
             ]
           }
         };
 
-        case "testNamedNodeMap":
-          return {
-            "type": "object",
-            "class": "NamedNodeMap",
-            "actor": "server1.conn3.obj42",
-            "extensible": true,
-            "frozen": false,
-            "sealed": false,
-            "ownPropertyLength": 6,
-            "preview": {
-              "kind": "ArrayLike",
-              "length": 3,
-              "items": [
-                {
-                  "type": "object",
-                  "class": "Attr",
-                  "actor": "server1.conn3.obj43",
-                  "extensible": true,
-                  "frozen": false,
-                  "sealed": false,
-                  "ownPropertyLength": 0,
-                  "preview": {
-                    "kind": "DOMNode",
-                    "nodeType": 2,
-                    "nodeName": "class",
-                    "value": "myclass"
-                  }
-                },
-                {
-                  "type": "object",
-                  "class": "Attr",
-                  "actor": "server1.conn3.obj44",
-                  "extensible": true,
-                  "frozen": false,
-                  "sealed": false,
-                  "ownPropertyLength": 0,
-                  "preview": {
-                    "kind": "DOMNode",
-                    "nodeType": 2,
-                    "nodeName": "cellpadding",
-                    "value": "7"
-                  }
-                },
-                {
-                  "type": "object",
-                  "class": "Attr",
-                  "actor": "server1.conn3.obj44",
-                  "extensible": true,
-                  "frozen": false,
-                  "sealed": false,
-                  "ownPropertyLength": 0,
-                  "preview": {
-                    "kind": "DOMNode",
-                    "nodeType": 2,
-                    "nodeName": "border",
-                    "value": "3"
-                  }
+      case "testNamedNodeMap":
+        return {
+          "type": "object",
+          "class": "NamedNodeMap",
+          "actor": "server1.conn3.obj42",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 6,
+          "preview": {
+            "kind": "ArrayLike",
+            "length": 3,
+            "items": [
+              {
+                "type": "object",
+                "class": "Attr",
+                "actor": "server1.conn3.obj43",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 2,
+                  "nodeName": "class",
+                  "value": "myclass"
                 }
-              ]
-            }
-          };
+              },
+              {
+                "type": "object",
+                "class": "Attr",
+                "actor": "server1.conn3.obj44",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 2,
+                  "nodeName": "cellpadding",
+                  "value": "7"
+                }
+              },
+              {
+                "type": "object",
+                "class": "Attr",
+                "actor": "server1.conn3.obj44",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 2,
+                  "nodeName": "border",
+                  "value": "3"
+                }
+              }
+            ]
+          }
+        };
+
       case "testNodeList":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj51",
           "class": "NodeList",
           "extensible": true,
           "frozen": false,
           "sealed": false,
@@ -544,15 +578,130 @@ window.onload = Task.async(function* () 
                     "type": "button"
                   },
                   "attributesLength": 3
                 }
               }
             ]
           }
         };
+
+      case "testDocumentFragment":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj45",
+          "class": "DocumentFragment",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "DOMNode",
+            "nodeType": 11,
+            "nodeName": "#document-fragment",
+            "childNodesLength": 5,
+            "childNodes": [
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj46",
+                "class": "HTMLLIElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "li",
+                  "attributes": {
+                    "id": "li-0",
+                    "class": "list-element"
+                  },
+                  "attributesLength": 2
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj47",
+                "class": "HTMLLIElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "li",
+                  "attributes": {
+                    "id": "li-1",
+                    "class": "list-element"
+                  },
+                  "attributesLength": 2
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj48",
+                "class": "HTMLLIElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "li",
+                  "attributes": {
+                    "id": "li-2",
+                    "class": "list-element"
+                  },
+                  "attributesLength": 2
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj49",
+                "class": "HTMLLIElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "li",
+                  "attributes": {
+                    "id": "li-3",
+                    "class": "list-element"
+                  },
+                  "attributesLength": 2
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj50",
+                "class": "HTMLLIElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "li",
+                  "attributes": {
+                    "id": "li-4",
+                    "class": "list-element"
+                  },
+                  "attributesLength": 2
+                }
+              }
+            ]
+          }
+        };
     }
+    return null;
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -25,16 +25,17 @@
 #include "nsContentUtils.h"
 #include "nsGenericDOMDataNode.h"
 #include "nsTextFrame.h"
 #include "nsFontFaceList.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/RangeBinding.h"
 #include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Likely.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsStyleStruct.h"
 #include "nsStyleStructInlines.h"
 #include "nsComputedDOMStyle.h"
@@ -2892,54 +2893,78 @@ GetTextFrameForContent(nsIContent* aCont
     if (frame && frame->GetType() == nsGkAtoms::textFrame) {
       return static_cast<nsTextFrame*>(frame);
     }
   }
   return nullptr;
 }
 
 static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback,
+                                   mozilla::dom::DOMStringList* aTextList,
                                    nsIContent* aContent, int32_t aStartOffset,
                                    int32_t aEndOffset, bool aClampToEdge,
                                    bool aFlushLayout)
 {
   nsTextFrame* textFrame = GetTextFrameForContent(aContent, aFlushLayout);
   if (textFrame) {
+    // If we'll need it later, collect the full content text now.
+    nsAutoString textContent;
+    if (aTextList) {
+      mozilla::ErrorResult err; // ignored
+      aContent->GetTextContent(textContent, err);
+    }
+
     nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
     for (nsTextFrame* f = textFrame; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
       int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
       if (fend <= aStartOffset || fstart >= aEndOffset)
         continue;
 
+      // Calculate the text content offsets we'll need if text is requested.
+      int32_t textContentStart = fstart;
+      int32_t textContentEnd = fend;
+
       // overlapping with the offset we want
       f->EnsureTextRun(nsTextFrame::eInflated);
       NS_ENSURE_TRUE(f->GetTextRun(nsTextFrame::eInflated), NS_ERROR_OUT_OF_MEMORY);
       bool rtl = f->GetTextRun(nsTextFrame::eInflated)->IsRightToLeft();
       nsRect r = f->GetRectRelativeToSelf();
       if (fstart < aStartOffset) {
         // aStartOffset is within this frame
         ExtractRectFromOffset(f, aStartOffset, &r, rtl, aClampToEdge);
+        textContentStart = aStartOffset;
       }
       if (fend > aEndOffset) {
         // aEndOffset is in the middle of this frame
         ExtractRectFromOffset(f, aEndOffset, &r, !rtl, aClampToEdge);
+        textContentEnd = aEndOffset;
       }
       r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
       aCallback->AddRect(r);
+
+      // Finally capture the text, if requested.
+      if (aTextList) {
+        const nsAString& textSubstring =
+          Substring(textContent,
+                    textContentStart,
+                    (textContentEnd - textContentStart));
+        aTextList->Add(textSubstring);
+      }
     }
   }
   return NS_OK;
 }
 
 /* static */ void
-nsRange::CollectClientRects(nsLayoutUtils::RectCallback* aCollector,
-                            nsRange* aRange,
-                            nsINode* aStartParent, int32_t aStartOffset,
-                            nsINode* aEndParent, int32_t aEndOffset,
-                            bool aClampToEdge, bool aFlushLayout)
+nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
+                                   mozilla::dom::DOMStringList* aTextList,
+                                   nsRange* aRange,
+                                   nsINode* aStartParent, int32_t aStartOffset,
+                                   nsINode* aEndParent, int32_t aEndOffset,
+                                   bool aClampToEdge, bool aFlushLayout)
 {
   // Hold strong pointers across the flush
   nsCOMPtr<nsINode> startContainer = aStartParent;
   nsCOMPtr<nsINode> endContainer = aEndParent;
 
   // Flush out layout so our frames are up to date.
   if (!aStartParent->IsInUncomposedDoc()) {
     return;
@@ -2987,30 +3012,31 @@ nsRange::CollectClientRects(nsLayoutUtil
     iter.Next();
     nsCOMPtr<nsIContent> content = do_QueryInterface(node);
     if (!content)
       continue;
     if (content->IsNodeOfType(nsINode::eTEXT)) {
        if (node == startContainer) {
          int32_t offset = startContainer == endContainer ?
            aEndOffset : content->GetText()->GetLength();
-         GetPartialTextRect(aCollector, content, aStartOffset, offset,
+         GetPartialTextRect(aCollector, aTextList, content, aStartOffset, offset,
                             aClampToEdge, aFlushLayout);
          continue;
        } else if (node == endContainer) {
-         GetPartialTextRect(aCollector, content, 0, aEndOffset,
+         GetPartialTextRect(aCollector, aTextList, content, 0, aEndOffset,
                             aClampToEdge, aFlushLayout);
          continue;
        }
     }
 
     nsIFrame* frame = content->GetPrimaryFrame();
     if (frame) {
-      nsLayoutUtils::GetAllInFlowRects(frame,
+      nsLayoutUtils::GetAllInFlowRectsAndTexts(frame,
         nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector,
+        aTextList,
         nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
     }
   } while (!iter.IsDone());
 }
 
 NS_IMETHODIMP
 nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult)
 {
@@ -3022,18 +3048,18 @@ already_AddRefed<DOMRect>
 nsRange::GetBoundingClientRect(bool aClampToEdge, bool aFlushLayout)
 {
   RefPtr<DOMRect> rect = new DOMRect(ToSupports(this));
   if (!mStartParent) {
     return rect.forget();
   }
 
   nsLayoutUtils::RectAccumulator accumulator;
-  CollectClientRects(&accumulator, this, mStartParent, mStartOffset, 
-    mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
+  CollectClientRectsAndText(&accumulator, nullptr, this, mStartParent,
+    mStartOffset, mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
 
   nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : 
     accumulator.mResultRect;
   rect->SetLayoutRect(r);
   return rect.forget();
 }
 
 NS_IMETHODIMP
@@ -3050,21 +3076,39 @@ nsRange::GetClientRects(bool aClampToEdg
     return nullptr;
   }
 
   RefPtr<DOMRectList> rectList =
     new DOMRectList(static_cast<nsIDOMRange*>(this));
 
   nsLayoutUtils::RectListBuilder builder(rectList);
 
-  CollectClientRects(&builder, this, mStartParent, mStartOffset, 
-    mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
+  CollectClientRectsAndText(&builder, nullptr, this, mStartParent,
+    mStartOffset, mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
   return rectList.forget();
 }
 
+void
+nsRange::GetClientRectsAndTexts(
+  mozilla::dom::ClientRectsAndTexts& aResult,
+  ErrorResult& aErr)
+{
+  if (!mStartParent) {
+    return;
+  }
+
+  aResult.mRectList = new DOMRectList(static_cast<nsIDOMRange*>(this));
+  aResult.mTextList = new DOMStringList();
+
+  nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
+
+  CollectClientRectsAndText(&builder, aResult.mTextList, this,
+    mStartParent, mStartOffset, mEndParent, mEndOffset, true, true);
+}
+
 NS_IMETHODIMP
 nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult)
 {
   *aResult = nullptr;
 
   NS_ENSURE_TRUE(mStartParent, NS_ERROR_UNEXPECTED);
 
   nsCOMPtr<nsINode> startContainer = do_QueryInterface(mStartParent);
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -20,16 +20,17 @@
 #include "prmon.h"
 #include "nsStubMutationObserver.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
 
 namespace mozilla {
 class ErrorResult;
 namespace dom {
+struct ClientRectsAndTexts;
 class DocumentFragment;
 class DOMRect;
 class DOMRectList;
 class Selection;
 } // namespace dom
 } // namespace mozilla
 
 class nsRange final : public nsIDOMRange,
@@ -207,16 +208,19 @@ public:
   void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
   void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
   void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
   void SurroundContents(nsINode& aNode, ErrorResult& aErr);
   already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
                                                   bool aFlushLayout = true);
   already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
                                                bool aFlushLayout = true);
+  void GetClientRectsAndTexts(
+    mozilla::dom::ClientRectsAndTexts& aResult,
+    ErrorResult& aErr);
   static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
                                   mozilla::ErrorResult& aError,
                                   nsIContent* aStartParent,
                                   uint32_t aStartOffset,
                                   nsIContent* aEndParent,
                                   uint32_t aEndOffset);
 
   nsINode* GetParentObject() const { return mOwner; }
@@ -258,21 +262,26 @@ public:
    * where aNode is a descendant of a range's common ancestor node).
    * If a nsRange starts in (aNode, aEndOffset) or if it ends in
    * (aNode, aStartOffset) then it is non-overlapping and the result is false
    * for that nsRange.  Collapsed ranges always counts as non-overlapping.
    */
   static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
                              uint32_t aEndOffset);
 
-  static void CollectClientRects(nsLayoutUtils::RectCallback* aCollector,
-                                 nsRange* aRange,
-                                 nsINode* aStartParent, int32_t aStartOffset,
-                                 nsINode* aEndParent, int32_t aEndOffset,
-                                 bool aClampToEdge, bool aFlushLayout);
+  /**
+   * This helper function gets rects and correlated text for the given range.
+   * @param aTextList optional where nullptr = don't retrieve text
+   */
+  static void CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
+                                        mozilla::dom::DOMStringList* aTextList,
+                                        nsRange* aRange,
+                                        nsINode* aStartParent, int32_t aStartOffset,
+                                        nsINode* aEndParent, int32_t aEndOffset,
+                                        bool aClampToEdge, bool aFlushLayout);
 
   /**
    * Scan this range for -moz-user-select:none nodes and split it up into
    * multiple ranges to exclude those nodes.  The resulting ranges are put
    * in aOutRanges.  If no -moz-user-select:none node is found in the range
    * then |this| is unmodified and is the only range in aOutRanges.
    * Otherwise, |this| will be modified so that it ends before the first
    * -moz-user-select:none node and additional ranges may also be created.
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -63,12 +63,13 @@ support-files = ../file_bug357450.js
 [test_bug1209621.xul]
 [test_cpows.xul]
 [test_registerElement_content.xul]
 [test_registerElement_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_fileconstructor_tempfile.xul]
 [test_nsITextInputProcessor.xul]
+[test_range_getClientRectsAndTexts.html]
 [test_title.xul]
 [test_windowroot.xul]
 [test_swapFrameLoaders.xul]
 [test_groupedSHistory.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests()
+{
+  let range = document.createRange();
+
+  let attempts = [
+    {startNode: "one", start:0, endNode:"one", end:0, textList:[], message:"Empty rect"},
+    {startNode: "one", start:2, endNode:"one", end:6, textList:["l on"], message:"Single line"},
+    {startNode: "implicit", start:6, endNode:"implicit", end:12, textList:["it\nbre"], message:"Implicit break"},
+    {startNode: "two.a", start:1, endNode:"two.b", end:2, textList:["wo", "", "li"], message:"Two lines"},
+    {startNode: "embed.a", start:7, endNode:"embed.b", end:2, textList:["th ", "simple nested", " ", "te"], message:"Simple nested"},
+    {startNode: "deep.a", start:2, endNode:"deep.b", end:2, textList:["ne with ", "complex, more deeply nested", " ", "te"], message:"Complex nested"},
+    {startNode: "image.a", start:7, endNode:"image.b", end:2, textList:["th inline ", "", " ", "im"], message:"Inline image"},
+  ];
+
+  for (let attempt of attempts) {
+    range.setStart(document.getElementById(attempt.startNode).childNodes[0], attempt.start);
+    range.setEnd(document.getElementById(attempt.endNode).childNodes[0], attempt.end);
+
+    let result = range.getClientRectsAndTexts();
+
+    is(result.textList.length, attempt.textList.length, attempt.message + " range has expected number of texts.");
+    let i = 0;
+    for (let text of result.textList) {
+      is(text, attempt.textList[i], attempt.message + " matched text index " + i + ".");
+      i++;
+    }
+  }
+
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onLoad="runTests()">
+
+<div id="one">All on one line</div>
+
+<div id="implicit">Implicit
+break in one line</div>
+
+<div id="two.a">Two<br/
+><span id="two.b">lines</span></div>
+
+<div id="embed.a">Line with <span>simple nested</span> <span id="embed.b">text</span></div>
+
+<div id="deep.a">Line with <span>complex, <span>more <span>deeply <span>nested</span></span></span></span> <span id="deep.b">text</span></div>
+
+<div id="image.a">Line with inline <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" width="20" height="20"/> <span id="image.b">image</span></div>
+
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_receiveMessage.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script>
+    window.onmessage = event => {
+      document.body.textContent = `${event.origin}|${event.data}`;
+    };
+  </script>
+</head>
+
+<body></body>
+</html>
+
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -736,16 +736,18 @@ skip-if = toolkit == 'android' #CLICK_TO
 tags = audiochannel
 skip-if = toolkit == 'android' # Plugins don't work on Android
 [test_pluginMutedBeforePlay.html]
 tags = audiochannel
 skip-if = toolkit == 'android' # Plugins don't work on Android
 [test_postMessage_solidus.html]
 [test_postMessages.html]
 support-files = worker_postMessages.js
+[test_postMessage_originAttributes.html]
+support-files = file_receiveMessage.html
 [test_processing_instruction_update_stylesheet.xhtml]
 [test_progress_events_for_gzip_data.html]
 [test_range_bounds.html]
 skip-if = toolkit == 'android'
 [test_reentrant_flush.html]
 skip-if = toolkit == 'android'
 [test_referrer_redirect.html]
 [test_root_iframe.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_postMessage_originAttributes.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test window.postMessages from system principal to window with non-default originAttributes</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<iframe id="target-iframe"></iframe>
+<script type="application/javascript">
+add_task(function*() {
+  let iframe = document.querySelector("#target-iframe");
+
+  let win = SpecialPowers.wrap(iframe).contentWindow;
+  let docShell = win.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+                    .getInterface(SpecialPowers.Ci.nsIDocShell);
+
+  // Add private browsing ID to docShell origin and load document.
+  docShell.setOriginAttributes({privateBrowsingId: 1});
+
+  yield new Promise(resolve => {
+    iframe.addEventListener("load", resolve, true);
+
+    iframe.src = SimpleTest.getTestFileURL("file_receiveMessage.html");
+  });
+
+  // Set up console monitor to wait for warning.
+  const expectedMessage = (
+    'Attempting to post a message to window with url ' +
+    '"http://mochi.test:8888/tests/dom/base/test/file_receiveMessage.html" ' +
+    'and origin "http://mochi.test:8888^privateBrowsingId=1" from a system ' +
+    'principal scope with mismatched origin "[System Principal]".');
+
+  let consolePromise = new Promise(resolve => {
+    SimpleTest.monitorConsole(resolve, [e => e.message == expectedMessage]);
+  });
+
+  // Post to the content window via SpecialPowers' system principal scope.
+  win.postMessage("Hello. o/", "http://mochi.test:8888");
+  yield new Promise(resolve => setTimeout(resolve, 0));
+
+  SimpleTest.endMonitorConsole();
+  yield consolePromise;
+
+  // Check that the window received and handled the message.
+  is(win.document.body.textContent, "|Hello. o/",
+     "Content window received the expected message");
+});
+</script>
+</body>
+</html>
+
--- a/dom/gamepad/Gamepad.h
+++ b/dom/gamepad/Gamepad.h
@@ -28,24 +28,16 @@ const int kStandardGamepadAxes = 4;
 const int kButtonLeftTrigger = 6;
 const int kButtonRightTrigger = 7;
 
 const int kLeftStickXAxis = 0;
 const int kLeftStickYAxis = 1;
 const int kRightStickXAxis = 2;
 const int kRightStickYAxis = 3;
 
-// Standard channel is used for managing gamepads that
-// are from GamepadPlatformService. VR channel
-// is for gamepads that are from VRManagerChild.
-enum class GamepadServiceType : uint16_t {
-  Standard,
-  VR,
-  NumGamepadServiceType
-};
 
 class Gamepad final : public nsISupports,
                       public nsWrapperCache
 {
 public:
   Gamepad(nsISupports* aParent,
           const nsAString& aID, uint32_t aIndex,
           GamepadMappingType aMapping,
--- a/dom/gamepad/GamepadManager.cpp
+++ b/dom/gamepad/GamepadManager.cpp
@@ -585,17 +585,18 @@ GamepadManager::SetWindowHasSeenGamepad(
 }
 
 void
 GamepadManager::Update(const GamepadChangeEvent& aEvent)
 {
   if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
     const GamepadAdded& a = aEvent.get_GamepadAdded();
     AddGamepad(a.index(), a.id(),
-               a.mapping(), a.service_type(),
+               static_cast<GamepadMappingType>(a.mapping()),
+               a.service_type(),
                a.num_buttons(), a.num_axes());
     return;
   }
   if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
     const GamepadRemoved& a = aEvent.get_GamepadRemoved();
     RemoveGamepad(a.index(), a.service_type());
     return;
   }
--- a/dom/gamepad/GamepadManager.h
+++ b/dom/gamepad/GamepadManager.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_GamepadManager_h_
 #define mozilla_dom_GamepadManager_h_
 
 #include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsIObserver.h"
 // Needed for GamepadMappingType
 #include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/dom/GamepadServiceType.h"
 
 class nsGlobalWindow;
 
 namespace mozilla {
 namespace gfx {
 class VRManagerChild;
 } // namespace gfx
 namespace dom {
--- a/dom/gamepad/GamepadPlatformService.cpp
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -90,17 +90,17 @@ GamepadPlatformService::AddGamepad(const
 {
   // This method is called by monitor thread populated in
   // platform-dependent backends
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(!NS_IsMainThread());
 
   uint32_t index = ++mGamepadIndex;
   GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
-                 aMapping, GamepadServiceType::Standard, aNumButtons, aNumAxes);
+                 static_cast<uint32_t>(aMapping), GamepadServiceType::Standard, aNumButtons, aNumAxes);
   NotifyGamepadChange<GamepadAdded>(a);
   return index;
 }
 
 void
 GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
 {
   // This method is called by monitor thread populated in
--- a/dom/gamepad/GamepadServiceTest.cpp
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -117,20 +117,18 @@ GamepadServiceTest::AddGamepad(const nsA
                                uint32_t aNumButtons,
                                uint32_t aNumAxes,
                                ErrorResult& aRv)
 {
   if (mShuttingDown) {
     return nullptr;
   }
 
-  // Because GamepadServiceTest::AddGamepad() is opened for Web API,
-  // we need to convert aMapping from uint32_t to GamepadMappingType here.
   GamepadAdded a(nsString(aID), 0,
-                 static_cast<GamepadMappingType>(aMapping),
+                 aMapping,
                  GamepadServiceType::Standard,
                  aNumButtons, aNumAxes);
   GamepadChangeEvent e(a);
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
 
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
--- a/dom/gamepad/ipc/GamepadEventTypes.ipdlh
+++ b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
@@ -1,23 +1,25 @@
 /* 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/. */
 
-using mozilla::dom::GamepadMappingType from "mozilla/dom/GamepadMessageUtils.h";
 using mozilla::dom::GamepadServiceType from "mozilla/dom/GamepadMessageUtils.h";
 
 
 namespace mozilla {
 namespace dom {
 
 struct GamepadAdded {
   nsString id;
   uint32_t index;
-  GamepadMappingType mapping;
+  // Ideally, mapping should be a GamepadMappingType
+  // But, we have dependency problems in non MOZ_GAMEPAD
+  // platforms. Therefore, we make it as an uint32_t here.
+  uint32_t mapping;
   GamepadServiceType service_type;
   uint32_t num_buttons;
   uint32_t num_axes;
 };
 
 struct GamepadRemoved {
   uint32_t index;
   GamepadServiceType service_type;
--- a/dom/gamepad/ipc/GamepadMessageUtils.h
+++ b/dom/gamepad/ipc/GamepadMessageUtils.h
@@ -1,24 +1,18 @@
 
 #ifndef mozilla_dom_gamepad_GamepadMessageUtils_h
 #define mozilla_dom_gamepad_GamepadMessageUtils_h
 
 #include "ipc/IPCMessageUtils.h"
-#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadServiceType.h"
 
 namespace IPC {
 
 template<>
-struct ParamTraits<mozilla::dom::GamepadMappingType> :
-  public ContiguousEnumSerializer<mozilla::dom::GamepadMappingType,
-                                  mozilla::dom::GamepadMappingType(mozilla::dom::GamepadMappingType::_empty),
-                                  mozilla::dom::GamepadMappingType(mozilla::dom::GamepadMappingType::EndGuard_)> {};
-
-template<>
 struct ParamTraits<mozilla::dom::GamepadServiceType> :
   public ContiguousEnumSerializer<mozilla::dom::GamepadServiceType,
                                   mozilla::dom::GamepadServiceType(0),
                                   mozilla::dom::GamepadServiceType(
                                   mozilla::dom::GamepadServiceType::NumGamepadServiceType)> {};
 } // namespace IPC
 
 #endif // mozilla_dom_gamepad_GamepadMessageUtils_h
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadServiceType.h
@@ -0,0 +1,20 @@
+
+#ifndef mozilla_dom_GamepadServiceType_h_
+#define mozilla_dom_GamepadServiceType_h_
+
+namespace mozilla{
+namespace dom{
+
+// Standard channel is used for managing gamepads that
+// are from GamepadPlatformService. VR channel
+// is for gamepads that are from VRManagerChild.
+enum class GamepadServiceType : uint16_t {
+  Standard,
+  VR,
+  NumGamepadServiceType
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif // mozilla_dom_GamepadServiceType_h_
\ No newline at end of file
--- a/dom/gamepad/ipc/GamepadTestChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
@@ -18,17 +18,17 @@ GamepadTestChannelParent::RecvGamepadTes
   RefPtr<GamepadPlatformService>  service =
     GamepadPlatformService::GetParentService();
   MOZ_ASSERT(service);
   if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
     const GamepadAdded& a = aEvent.get_GamepadAdded();
     nsCString gamepadID;
     LossyCopyUTF16toASCII(a.id(), gamepadID);
     uint32_t index = service->AddGamepad(gamepadID.get(),
-                                         a.mapping(),
+                                         static_cast<GamepadMappingType>(a.mapping()),
                                          a.num_buttons(),
                                          a.num_axes());
     if (!mShuttingdown) {
       Unused << SendReplyGamepadIndex(aID, index);
     }
     return true;
   }
   if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -5,27 +5,31 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 IPDL_SOURCES += [
     'ipc/GamepadEventTypes.ipdlh',
     'ipc/PGamepadEventChannel.ipdl',
     'ipc/PGamepadTestChannel.ipdl'
 ]
 
+EXPORTS.mozilla.dom += [
+    'ipc/GamepadMessageUtils.h',
+    'ipc/GamepadServiceType.h'
+]
+
 if CONFIG['MOZ_GAMEPAD']:
     EXPORTS.mozilla.dom += [
         'Gamepad.h',
         'GamepadButton.h',
         'GamepadManager.h',
         'GamepadMonitoring.h',
         'GamepadPlatformService.h',
         'GamepadServiceTest.h',
         'ipc/GamepadEventChannelChild.h',
         'ipc/GamepadEventChannelParent.h',
-        'ipc/GamepadMessageUtils.h',
         'ipc/GamepadTestChannelChild.h',
         'ipc/GamepadTestChannelParent.h'
         ]
 
     UNIFIED_SOURCES = [
         'Gamepad.cpp',
         'GamepadButton.cpp',
         'GamepadManager.cpp',
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2408,18 +2408,19 @@ TabChild::RecvSetDocShellIsActive(const 
     if (aIsActive) {
       ProcessHangMonitor::ClearForcePaint();
     }
   });
 
   // We send the current layer observer epoch to the compositor so that
   // TabParent knows whether a layer update notification corresponds to the
   // latest SetDocShellIsActive request that was made.
-  ClientLayerManager *manager = mPuppetWidget->GetLayerManager()->AsClientLayerManager();
-  manager->SetLayerObserverEpoch(aLayerObserverEpoch);
+  if (ClientLayerManager* clm = mPuppetWidget->GetLayerManager()->AsClientLayerManager()) {
+    clm->SetLayerObserverEpoch(aLayerObserverEpoch);
+  }
 
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !aIsActive;
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   if (docShell) {
     bool wasActive;
     docShell->GetIsActive(&wasActive);
     if (aIsActive && wasActive) {
@@ -2583,44 +2584,50 @@ TabChild::InitRenderingState(const Textu
     // Pushing layers transactions directly to a separate
     // compositor context.
     PCompositorBridgeChild* compositorChild = CompositorBridgeChild::Get();
     if (!compositorChild) {
       NS_WARNING("failed to get CompositorBridgeChild instance");
       PRenderFrameChild::Send__delete__(remoteFrame);
       return false;
     }
-    nsTArray<LayersBackend> backends;
-    backends.AppendElement(mTextureFactoryIdentifier.mParentBackend);
-    bool success;
-    PLayerTransactionChild* shadowManager =
-        compositorChild->SendPLayerTransactionConstructor(backends,
-                                                          aLayersId, &mTextureFactoryIdentifier, &success);
-    if (!success) {
-      NS_WARNING("failed to properly allocate layer transaction");
-      PRenderFrameChild::Send__delete__(remoteFrame);
-      return false;
-    }
-
-    if (!shadowManager) {
-      NS_WARNING("failed to construct LayersChild");
-      // This results in |remoteFrame| being deleted.
-      PRenderFrameChild::Send__delete__(remoteFrame);
-      return false;
-    }
 
     ShadowLayerForwarder* lf =
         mPuppetWidget->GetLayerManager(
-            shadowManager, mTextureFactoryIdentifier.mParentBackend)
+            nullptr, mTextureFactoryIdentifier.mParentBackend)
                 ->AsShadowForwarder();
-    MOZ_ASSERT(lf && lf->HasShadowManager(),
-               "PuppetWidget should have shadow manager");
-    lf->IdentifyTextureHost(mTextureFactoryIdentifier);
-    ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier);
-    gfx::VRManagerChild::IdentifyTextureHost(mTextureFactoryIdentifier);
+    // As long as we are creating a ClientLayerManager for the puppet widget,
+    // lf must be non-null here.
+    MOZ_ASSERT(lf);
+
+    if (lf) {
+      nsTArray<LayersBackend> backends;
+      backends.AppendElement(mTextureFactoryIdentifier.mParentBackend);
+      bool success;
+      PLayerTransactionChild* shadowManager =
+          compositorChild->SendPLayerTransactionConstructor(backends,
+                                                            aLayersId, &mTextureFactoryIdentifier, &success);
+      if (!success) {
+        NS_WARNING("failed to properly allocate layer transaction");
+        PRenderFrameChild::Send__delete__(remoteFrame);
+        return false;
+      }
+
+      if (!shadowManager) {
+        NS_WARNING("failed to construct LayersChild");
+        // This results in |remoteFrame| being deleted.
+        PRenderFrameChild::Send__delete__(remoteFrame);
+        return false;
+      }
+
+      lf->SetShadowManager(shadowManager);
+      lf->IdentifyTextureHost(mTextureFactoryIdentifier);
+      ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier);
+      gfx::VRManagerChild::IdentifyTextureHost(mTextureFactoryIdentifier);
+    }
 
     mRemoteFrame = remoteFrame;
     if (aLayersId != 0) {
       if (!sTabChildren) {
         sTabChildren = new TabChildMap;
       }
       MOZ_ASSERT(!sTabChildren->Get(aLayersId));
       sTabChildren->Put(aLayersId, this);
@@ -2995,16 +3002,17 @@ TabChild::ReinitRendering()
 
 void
 TabChild::CompositorUpdated(const TextureFactoryIdentifier& aNewIdentifier)
 {
   gfxPlatform::GetPlatform()->CompositorUpdated();
 
   RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager();
   ClientLayerManager* clm = lm->AsClientLayerManager();
+  MOZ_ASSERT(clm);
 
   mTextureFactoryIdentifier = aNewIdentifier;
   clm->UpdateTextureFactoryIdentifier(aNewIdentifier);
   FrameLayerBuilder::InvalidateAllLayers(clm);
 }
 
 NS_IMETHODIMP
 TabChild::OnShowTooltip(int32_t aXCoords, int32_t aYCoords, const char16_t *aTipText,
--- a/dom/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -97,32 +97,36 @@ AudioCaptureStream::ProcessInput(GraphTi
     track->Get<AudioSegment>()->AppendNullData(aTo - aFrom);
   } else {
     // We mix down all the tracks of all inputs, to a stereo track. Everything
     // is {up,down}-mixed to stereo.
     mMixer.StartMixing();
     AudioSegment output;
     for (uint32_t i = 0; i < inputCount; i++) {
       MediaStream* s = mInputs[i]->GetSource();
-      for (StreamTracks::TrackIter track(s->GetStreamTracks(),
-                                         MediaSegment::AUDIO);
-           !track.IsEnded(); track.Next()) {
+      StreamTracks::TrackIter track(s->GetStreamTracks(), MediaSegment::AUDIO);
+      if (track.IsEnded()) {
+        // No tracks for this input. Still we append data to trigger the mixer.
+        AudioSegment toMix;
+        toMix.AppendNullData(aTo - aFrom);
+        toMix.Mix(mMixer, MONO, Graph()->GraphRate());
+      }
+      for (; !track.IsEnded(); track.Next()) {
         AudioSegment* inputSegment = track->Get<AudioSegment>();
         StreamTime inputStart = s->GraphTimeToStreamTimeWithBlocking(aFrom);
         StreamTime inputEnd = s->GraphTimeToStreamTimeWithBlocking(aTo);
-        if (track->IsEnded() && inputSegment->GetDuration() <= inputEnd) {
-          // If the input track has ended and we have consumed all its data it
-          // can be ignored.
-          continue;
-        }
         AudioSegment toMix;
-        toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
-        // Care for streams blocked in the [aTo, aFrom] range.
-        if (inputEnd - inputStart < aTo - aFrom) {
-          toMix.AppendNullData((aTo - aFrom) - (inputEnd - inputStart));
+        if (track->IsEnded() && inputSegment->GetDuration() <= inputStart) {
+          toMix.AppendNullData(aTo - aFrom);
+        } else {
+          toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
+          // Care for streams blocked in the [aTo, aFrom] range.
+          if (inputEnd - inputStart < aTo - aFrom) {
+            toMix.AppendNullData((aTo - aFrom) - (inputEnd - inputStart));
+          }
         }
         toMix.Mix(mMixer, MONO, Graph()->GraphRate());
       }
     }
     // This calls MixerCallback below
     mMixer.FinishMixing();
   }
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1139,24 +1139,22 @@ MediaDecoder::OnSeekResolved(SeekResolve
     fireEnded = aVal.mAtEnd;
     if (aVal.mAtEnd) {
       ChangeState(PLAY_STATE_ENDED);
     }
     mLogicallySeeking = false;
   }
 
   // Ensure logical position is updated after seek.
-  UpdateLogicalPositionInternal(aVal.mEventVisibility);
+  UpdateLogicalPositionInternal();
 
-  if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-    mOwner->SeekCompleted();
-    AsyncResolveSeekDOMPromiseIfExists();
-    if (fireEnded) {
-      mOwner->PlaybackEnded();
-    }
+  mOwner->SeekCompleted();
+  AsyncResolveSeekDOMPromiseIfExists();
+  if (fireEnded) {
+    mOwner->PlaybackEnded();
   }
 }
 
 void
 MediaDecoder::OnSeekRejected()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSeekRequest.Complete();
@@ -1188,17 +1186,17 @@ MediaDecoder::ChangeState(PlayState aSta
   if (mPlayState == PLAY_STATE_PLAYING) {
     ConstructMediaTracks();
   } else if (IsEnded()) {
     RemoveMediaTracks();
   }
 }
 
 void
-MediaDecoder::UpdateLogicalPositionInternal(MediaDecoderEventVisibility aEventVisibility)
+MediaDecoder::UpdateLogicalPositionInternal()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
 
   double currentPosition = static_cast<double>(CurrentPosition()) / static_cast<double>(USECS_PER_S);
   if (mPlayState == PLAY_STATE_ENDED) {
     currentPosition = std::max(currentPosition, mDuration);
   }
@@ -1206,18 +1204,17 @@ MediaDecoder::UpdateLogicalPositionInter
   mLogicalPosition = currentPosition;
 
   // Invalidate the frame so any video data is displayed.
   // Do this before the timeupdate event so that if that
   // event runs JavaScript that queries the media size, the
   // frame has reflowed and the size updated beforehand.
   Invalidate();
 
-  if (logicalPositionChanged &&
-      aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+  if (logicalPositionChanged) {
     FireTimeUpdate();
   }
 }
 
 void
 MediaDecoder::DurationChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -54,20 +54,17 @@ enum class MediaEventType : int8_t;
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
 class MediaDecoder : public AbstractMediaDecoder
 {
 public:
   struct SeekResolveValue {
-    SeekResolveValue(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility)
-      : mAtEnd(aAtEnd), mEventVisibility(aEventVisibility) {}
     bool mAtEnd;
-    MediaDecoderEventVisibility mEventVisibility;
   };
 
   // Used to register with MediaResource to receive notifications which will
   // be forwarded to MediaDecoder.
   class ResourceCallback : public MediaResourceCallback {
     // Throttle calls to MediaDecoder::NotifyDataArrived()
     // to be at most once per 500ms.
     static const uint32_t sDelay = 500;
@@ -397,26 +394,26 @@ private:
     // seeking to prevent wild changes to the progress notification.
     MOZ_ASSERT(NS_IsMainThread());
     mIgnoreProgressData = mLogicallySeeking;
   }
 
   // Seeking has started. Inform the element on the main thread.
   void SeekingStarted();
 
-  void UpdateLogicalPositionInternal(MediaDecoderEventVisibility aEventVisibility);
+  void UpdateLogicalPositionInternal();
   void UpdateLogicalPosition()
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!IsShutdown());
     // Per spec, offical position remains stable during pause and seek.
     if (mPlayState == PLAY_STATE_PAUSED || IsSeeking()) {
       return;
     }
-    UpdateLogicalPositionInternal(MediaDecoderEventVisibility::Observable);
+    UpdateLogicalPositionInternal();
   }
 
   // Find the end of the cached data starting at the current decoder
   // position.
   int64_t GetDownloadPosition();
 
   // Notifies the element that decoding has failed.
   void DecodeError(const MediaResult& aError);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -195,33 +195,39 @@ public:
   // Event handlers for various events.
   virtual void HandleCDMProxyReady() {}
   virtual void HandleAudioDecoded(MediaData* aAudio) {}
   virtual void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) {}
   virtual void HandleEndOfStream() {}
   virtual void HandleWaitingForData() {}
   virtual void HandleAudioCaptured() {}
 
-  virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) = 0;
+  virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget);
 
   virtual RefPtr<ShutdownPromise> HandleShutdown();
 
   virtual void HandleVideoSuspendTimeout() = 0;
 
   virtual void HandleResumeVideoDecoding();
 
   virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
 
   virtual void DumpDebugInfo() {}
 
 private:
   template <class S, typename R, typename... As>
   auto ReturnTypeHelper(R(S::*)(As...)) -> R;
 
 protected:
+  enum class EventVisibility : int8_t
+  {
+    Observable,
+    Suppressed
+  };
+
   using Master = MediaDecoderStateMachine;
   explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
   TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
   MediaResource* Resource() const { return mMaster->mResource; }
   MediaDecoderReaderWrapper* Reader() const { return mMaster->mReader; }
   const MediaInfo& Info() const { return mMaster->Info(); }
   bool IsExpectingMoreData() const
   {
@@ -389,49 +395,55 @@ public:
 private:
   SeekJob mPendingSeek;
 };
 
 /**
  * Purpose: release decoder resources to save memory and hardware resources.
  *
  * Transition to:
- *   DECODING_FIRSTFRAME when play state changes to PLAYING.
- *   SEEKING if any seek request.
+ *   SEEKING if any seek request or play state changes to PLAYING.
  */
 class MediaDecoderStateMachine::DormantState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit DormantState(Master* aPtr) : StateObject(aPtr) {}
 
-  void Enter(SeekJob aPendingSeek)
+  void Enter()
   {
-    mPendingSeek = Move(aPendingSeek);
     if (mMaster->IsPlaying()) {
       mMaster->StopPlayback();
     }
+
+    // Calculate the position to seek to when exiting dormant.
+    auto t = mMaster->mMediaSink->IsStarted()
+      ? mMaster->GetClock()
+      : mMaster->GetMediaTime();
+    mPendingSeek.mTarget = SeekTarget(t, SeekTarget::Accurate);
+    // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
+    // need to create the promise even it is not used at all.
+    RefPtr<MediaDecoder::SeekPromise> x = mPendingSeek.mPromise.Ensure(__func__);
+
     mMaster->Reset();
     mMaster->mReader->ReleaseResources();
   }
 
   void Exit() override
   {
     // mPendingSeek is either moved when exiting dormant or
     // should be rejected here before transition to SHUTDOWN.
     mPendingSeek.RejectIfExists(__func__);
   }
 
   State GetState() const override
   {
     return DECODER_STATE_DORMANT;
   }
 
-  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
-
   void HandleVideoSuspendTimeout() override
   {
     // Do nothing since we've released decoders in Enter().
   }
 
   void HandleResumeVideoDecoding() override
   {
     // Do nothing since we won't resume decoding until exiting dormant.
@@ -576,18 +588,16 @@ public:
 
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     mMaster->Push(aVideo, MediaData::VIDEO_DATA);
     MaybeStopPrerolling();
     CheckSlowDecoding(aDecodeStart);
   }
 
-  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
-
   void HandleEndOfStream() override;
 
   void HandleWaitingForData() override
   {
     MaybeStopPrerolling();
   }
 
   void HandleAudioCaptured() override
@@ -681,26 +691,17 @@ private:
       mIsPrerolling = false;
       // Check if we can start playback.
       mMaster->ScheduleStateMachine();
     }
   }
 
   void EnterDormant()
   {
-    auto t = mMaster->mMediaSink->IsStarted()
-      ? mMaster->GetClock()
-      : mMaster->GetMediaTime();
-    SeekJob seekJob;
-    seekJob.mTarget = SeekTarget(t, SeekTarget::Accurate,
-                                 MediaDecoderEventVisibility::Suppressed);
-    // SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
-    // need to create the promise even it is not used at all.
-    RefPtr<MediaDecoder::SeekPromise> unused = seekJob.mPromise.Ensure(__func__);
-    SetState<DormantState>(Move(seekJob));
+    SetState<DormantState>();
   }
 
   void StartDormantTimer()
   {
     auto timeout = MediaPrefs::DormantOnPauseTimeout();
     if (timeout < 0) {
       // Disabled when timeout is negative.
       return;
@@ -748,17 +749,18 @@ private:
  *   DECODING otherwise.
  */
 class MediaDecoderStateMachine::SeekingState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit SeekingState(Master* aPtr) : StateObject(aPtr) {}
 
-  RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob aSeekJob)
+  RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob aSeekJob,
+                                          EventVisibility aVisibility)
   {
     mSeekJob = Move(aSeekJob);
 
     // Always switch off the blank decoder otherwise we might become visible
     // in the middle of seeking and won't have a valid video frame to show
     // when seek is done.
     if (mMaster->mVideoDecodeSuspended) {
       mMaster->mVideoDecodeSuspended = false;
@@ -791,18 +793,17 @@ public:
 
     // mSeekJob.mTarget.mTime might be different from
     // mSeekTask->GetSeekTarget().mTime because the seek task might clamp the
     // seek target to [0, duration]. We want to update the playback position to
     // the clamped value.
     mMaster->UpdatePlaybackPositionInternal(
       mSeekTask->GetSeekTarget().GetTime().ToMicroseconds());
 
-    if (mSeekJob.mTarget.mEventVisibility ==
-        MediaDecoderEventVisibility::Observable) {
+    if (aVisibility == EventVisibility::Observable) {
       mMaster->mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
     }
 
     // Reset our state machine and decoding pipeline before seeking.
     if (mSeekTask->NeedToResetMDSM()) {
       if (mSeekJob.mTarget.IsVideoOnly()) {
         mMaster->Reset(TrackInfo::kVideoTrack);
       } else {
@@ -843,18 +844,16 @@ public:
     MOZ_ASSERT(false);
   }
 
   void HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     MOZ_ASSERT(false);
   }
 
-  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
-
   void HandleVideoSuspendTimeout() override
   {
     // Do nothing since we want a valid video frame to show when seek is done.
   }
 
   void HandleResumeVideoDecoding() override
   {
     // We set mVideoDecodeSuspended to false in Enter().
@@ -962,18 +961,16 @@ public:
     // This might be the sample we need to exit buffering.
     // Schedule Step() to check it.
     mMaster->Push(aVideo, MediaData::VIDEO_DATA);
     mMaster->ScheduleStateMachine();
   }
 
   void HandleEndOfStream() override;
 
-  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
-
   void HandleVideoSuspendTimeout() override
   {
     if (mMaster->HasVideo()) {
       mMaster->mVideoDecodeSuspended = true;
       mMaster->mOnPlaybackEvent.Notify(MediaEventType::EnterVideoSuspend);
       Reader()->SetVideoBlankDecode(true);
     }
   }
@@ -1054,18 +1051,16 @@ public:
     }
   }
 
   State GetState() const override
   {
     return DECODER_STATE_COMPLETED;
   }
 
-  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
-
   void HandleAudioCaptured() override
   {
     // MediaSink is changed. Schedule Step() to check if we can start playback.
     mMaster->ScheduleStateMachine();
   }
 
   void HandleVideoSuspendTimeout() override
   {
@@ -1129,16 +1124,26 @@ public:
   }
 
   void HandleResumeVideoDecoding() override
   {
     MOZ_DIAGNOSTIC_ASSERT(false, "Already shutting down.");
   }
 };
 
+RefPtr<MediaDecoder::SeekPromise>
+MediaDecoderStateMachine::
+StateObject::HandleSeek(SeekTarget aTarget)
+{
+  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
+  SeekJob seekJob;
+  seekJob.mTarget = aTarget;
+  return SetState<SeekingState>(Move(seekJob), EventVisibility::Observable);
+}
+
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::
 StateObject::HandleShutdown()
 {
   return SetState<ShutdownState>();
 }
 
 static void
@@ -1199,20 +1204,19 @@ StateObject::HandleResumeVideoDecoding()
   SeekJob seekJob;
 
   const SeekTarget::Type type = mMaster->HasAudio()
                                 ? SeekTarget::Type::Accurate
                                 : SeekTarget::Type::PrevSyncPoint;
 
   seekJob.mTarget = SeekTarget(mMaster->GetMediaTime(),
                                type,
-                               MediaDecoderEventVisibility::Suppressed,
                                true /* aVideoOnly */);
 
-  SetState<SeekingState>(Move(seekJob))->Then(
+  SetState<SeekingState>(Move(seekJob), EventVisibility::Suppressed)->Then(
     AbstractThread::MainThread(), __func__,
     [start, info, hw](){ ReportRecoveryTelemetry(start, info, hw); },
     [](){});
 }
 
 void
 MediaDecoderStateMachine::
 DecodeMetadataState::OnMetadataRead(MetadataHolder* aMetadata)
@@ -1271,35 +1275,25 @@ DecodeMetadataState::OnMetadataRead(Meta
     // Metadata parsing was successful but we're still waiting for CDM caps
     // to become available so that we can build the correct decryptor/decoder.
     SetState<WaitForCDMState>();
   } else {
     SetState<DecodingFirstFrameState>(SeekJob{});
   }
 }
 
-RefPtr<MediaDecoder::SeekPromise>
-MediaDecoderStateMachine::
-DormantState::HandleSeek(SeekTarget aTarget)
-{
-  // Exit dormant when the user wants to seek.
-  mPendingSeek.RejectIfExists(__func__);
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
-}
-
 void
 MediaDecoderStateMachine::
 DormantState::HandlePlayStateChanged(MediaDecoder::PlayState aPlayState)
 {
   if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
     // Exit dormant when the user wants to play.
     MOZ_ASSERT(!Info().IsEncrypted() || mMaster->mCDMProxy);
-    SetState<DecodingFirstFrameState>(Move(mPendingSeek));
+    MOZ_ASSERT(mMaster->mSentFirstFrameLoadedEvent);
+    SetState<SeekingState>(Move(mPendingSeek), EventVisibility::Suppressed);
   }
 }
 
 void
 MediaDecoderStateMachine::
 WaitForCDMState::HandleCDMProxyReady()
 {
   SetState<DecodingFirstFrameState>(Move(mPendingSeek));
@@ -1308,17 +1302,17 @@ WaitForCDMState::HandleCDMProxyReady()
 void
 MediaDecoderStateMachine::
 DecodingFirstFrameState::Enter(SeekJob aPendingSeek)
 {
   // Handle pending seek.
   if (aPendingSeek.Exists() &&
       (mMaster->mSentFirstFrameLoadedEvent ||
        Reader()->ForceZeroStartTime())) {
-    SetState<SeekingState>(Move(aPendingSeek));
+    SetState<SeekingState>(Move(aPendingSeek), EventVisibility::Observable);
     return;
   }
 
   // Transition to DECODING if we've decoded first frames.
   if (mMaster->mSentFirstFrameLoadedEvent) {
     SetState<DecodingState>();
     return;
   }
@@ -1345,37 +1339,34 @@ DecodingFirstFrameState::HandleSeek(Seek
     mPendingSeek.mTarget = aTarget;
     return mPendingSeek.mPromise.Ensure(__func__);
   }
 
   // Since ForceZeroStartTime() is true, we should've transitioned to SEEKING
   // in Enter() if there is any pending seek.
   MOZ_ASSERT(!mPendingSeek.Exists());
 
-  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
+  return StateObject::HandleSeek(aTarget);
 }
 
 void
 MediaDecoderStateMachine::
 DecodingFirstFrameState::MaybeFinishDecodeFirstFrame()
 {
   MOZ_ASSERT(!mMaster->mSentFirstFrameLoadedEvent);
 
   if ((mMaster->IsAudioDecoding() && mMaster->AudioQueue().GetSize() == 0) ||
       (mMaster->IsVideoDecoding() && mMaster->VideoQueue().GetSize() == 0)) {
     return;
   }
 
   mMaster->FinishDecodeFirstFrame();
 
   if (mPendingSeek.Exists()) {
-    SetState<SeekingState>(Move(mPendingSeek));
+    SetState<SeekingState>(Move(mPendingSeek), EventVisibility::Observable);
   } else {
     SetState<DecodingState>();
   }
 }
 
 void
 MediaDecoderStateMachine::
 DecodingState::Enter()
@@ -1405,26 +1396,16 @@ DecodingState::Enter()
   mMaster->ScheduleStateMachine();
 
   // Will enter dormant when playback is paused for a while.
   if (mMaster->mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
     StartDormantTimer();
   }
 }
 
-RefPtr<MediaDecoder::SeekPromise>
-MediaDecoderStateMachine::
-DecodingState::HandleSeek(SeekTarget aTarget)
-{
-  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
-}
-
 void
 MediaDecoderStateMachine::
 DecodingState::HandleEndOfStream()
 {
   if (mMaster->CheckIfDecodeComplete()) {
     SetState<CompletedState>();
   } else {
     MaybeStopPrerolling();
@@ -1460,26 +1441,16 @@ DecodingState::MaybeStartBuffering()
       (mMaster->OutOfDecodedAudio() && Reader()->IsWaitingAudioData()) ||
       (mMaster->OutOfDecodedVideo() && Reader()->IsWaitingVideoData());
   }
   if (shouldBuffer) {
     SetState<BufferingState>();
   }
 }
 
-RefPtr<MediaDecoder::SeekPromise>
-MediaDecoderStateMachine::
-SeekingState::HandleSeek(SeekTarget aTarget)
-{
-  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
-}
-
 void
 MediaDecoderStateMachine::
 SeekingState::SeekCompleted()
 {
   int64_t seekTime = mSeekTask->GetSeekTarget().GetTime().ToMicroseconds();
   int64_t newCurrentTime = seekTime;
 
   // Setup timestamp state.
@@ -1607,36 +1578,16 @@ BufferingState::HandleEndOfStream()
   if (mMaster->CheckIfDecodeComplete()) {
     SetState<CompletedState>();
   } else {
     // Check if we can exit buffering.
     mMaster->ScheduleStateMachine();
   }
 }
 
-RefPtr<MediaDecoder::SeekPromise>
-MediaDecoderStateMachine::
-BufferingState::HandleSeek(SeekTarget aTarget)
-{
-  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
-}
-
-RefPtr<MediaDecoder::SeekPromise>
-MediaDecoderStateMachine::
-CompletedState::HandleSeek(SeekTarget aTarget)
-{
-  SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
-  SeekJob seekJob;
-  seekJob.mTarget = aTarget;
-  return SetState<SeekingState>(Move(seekJob));
-}
-
 RefPtr<ShutdownPromise>
 MediaDecoderStateMachine::
 ShutdownState::Enter()
 {
   auto master = mMaster;
 
   master->mIsShutdown = true;
   master->mDelayedScheduler.Reset();
--- a/dom/media/SeekJob.cpp
+++ b/dom/media/SeekJob.cpp
@@ -36,17 +36,18 @@ SeekJob& SeekJob::operator=(SeekJob&& aO
 bool SeekJob::Exists() const
 {
   MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty());
   return mTarget.IsValid();
 }
 
 void SeekJob::Resolve(bool aAtEnd, const char* aCallSite)
 {
-  MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility);
+  MediaDecoder::SeekResolveValue val;
+  val.mAtEnd = aAtEnd;
   mPromise.Resolve(val, aCallSite);
   mTarget.Reset();
 }
 
 void SeekJob::RejectIfExists(const char* aCallSite)
 {
   mTarget.Reset();
   mPromise.RejectIfExists(true, aCallSite);
--- a/dom/media/SeekTarget.h
+++ b/dom/media/SeekTarget.h
@@ -22,47 +22,39 @@ enum class MediaDecoderEventVisibility :
 struct SeekTarget {
   enum Type {
     Invalid,
     PrevSyncPoint,
     Accurate,
     NextFrame,
   };
   SeekTarget()
-    : mEventVisibility(MediaDecoderEventVisibility::Observable)
-    , mTime(media::TimeUnit::Invalid())
+    : mTime(media::TimeUnit::Invalid())
     , mType(SeekTarget::Invalid)
     , mVideoOnly(false)
   {
   }
   SeekTarget(int64_t aTimeUsecs,
              Type aType,
-             MediaDecoderEventVisibility aEventVisibility =
-               MediaDecoderEventVisibility::Observable,
              bool aVideoOnly = false)
-    : mEventVisibility(aEventVisibility)
-    , mTime(media::TimeUnit::FromMicroseconds(aTimeUsecs))
+    : mTime(media::TimeUnit::FromMicroseconds(aTimeUsecs))
     , mType(aType)
     , mVideoOnly(aVideoOnly)
   {
   }
   SeekTarget(const media::TimeUnit& aTime,
              Type aType,
-             MediaDecoderEventVisibility aEventVisibility =
-               MediaDecoderEventVisibility::Observable,
              bool aVideoOnly = false)
-    : mEventVisibility(aEventVisibility)
-    , mTime(aTime)
+    : mTime(aTime)
     , mType(aType)
     , mVideoOnly(aVideoOnly)
   {
   }
   SeekTarget(const SeekTarget& aOther)
-    : mEventVisibility(aOther.mEventVisibility)
-    , mTime(aOther.mTime)
+    : mTime(aOther.mTime)
     , mType(aOther.mType)
     , mVideoOnly(aOther.mVideoOnly)
   {
   }
   bool IsValid() const {
     return mType != SeekTarget::Invalid;
   }
   void Reset() {
@@ -92,18 +84,16 @@ struct SeekTarget {
   }
   bool IsNextFrame() const {
     return mType == SeekTarget::Type::NextFrame;
   }
   bool IsVideoOnly() const {
     return mVideoOnly;
   }
 
-  MediaDecoderEventVisibility mEventVisibility;
-
 private:
   // Seek target time.
   media::TimeUnit mTime;
   // Whether we should seek "Fast", or "Accurate".
   // "Fast" seeks to the seek point preceding mTime, whereas
   // "Accurate" seeks as close as possible to mTime.
   Type mType;
   bool mVideoOnly;
--- a/dom/media/test/external/external_media_harness/testcase.py
+++ b/dom/media/test/external/external_media_harness/testcase.py
@@ -6,25 +6,25 @@ import re
 import os
 import time
 
 from marionette import BrowserMobProxyTestCaseMixin, MarionetteTestCase, Marionette
 from marionette_driver import Wait
 from marionette_driver.errors import TimeoutException
 from marionette.marionette_test import SkipTest
 
-from firefox_puppeteer.testcases import BaseFirefoxTestCase
+from firefox_puppeteer import PuppeteerMixin
 from external_media_tests.utils import (timestamp_now, verbose_until)
 from external_media_tests.media_utils.video_puppeteer import (
     VideoException,
     VideoPuppeteer
 )
 
 
-class MediaTestCase(BaseFirefoxTestCase, MarionetteTestCase):
+class MediaTestCase(PuppeteerMixin, MarionetteTestCase):
 
     """
     Necessary methods for MSE playback
 
     :param video_urls: Urls you are going to play as part of the tests.
     """
 
     def __init__(self, *args, **kwargs):
--- a/dom/media/test/external/requirements.txt
+++ b/dom/media/test/external/requirements.txt
@@ -12,9 +12,9 @@ moznetwork==0.27
 mozprocess==0.23
 mozprofile==0.28
 mozrunner==6.12
 moztest==0.7
 mozversion==1.4
 wptserve==1.3.0
 marionette-client==3.1.0
 marionette-driver==2.0.0
-firefox-puppeteer >= 52.0.0, <53.0.0
+firefox-puppeteer >= 52.1.0, <53.0.0
--- a/dom/webidl/Range.webidl
+++ b/dom/webidl/Range.webidl
@@ -81,8 +81,18 @@ partial interface Range {
   DocumentFragment createContextualFragment(DOMString fragment);
 };
 
 // http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
 partial interface Range {
   DOMRectList? getClientRects();
   DOMRect getBoundingClientRect();
 };
+
+dictionary ClientRectsAndTexts {
+  required DOMRectList rectList;
+  required DOMStringList textList;
+};
+
+partial interface Range {
+  [ChromeOnly, Throws]
+  ClientRectsAndTexts getClientRectsAndTexts();
+};
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -1995,36 +1995,41 @@ EditorBase::RestorePreservedSelection(Se
 
 void
 EditorBase::StopPreservingSelection()
 {
   mRangeUpdater.DropSelectionState(mSavedSel);
   mSavedSel.MakeEmpty();
 }
 
-void
+bool
 EditorBase::EnsureComposition(WidgetCompositionEvent* aCompositionEvent)
 {
   if (mComposition) {
-    return;
+    return true;
   }
   // The compositionstart event must cause creating new TextComposition
   // instance at being dispatched by IMEStateManager.
   mComposition = IMEStateManager::GetTextCompositionFor(aCompositionEvent);
   if (!mComposition) {
-    MOZ_CRASH("IMEStateManager doesn't return proper composition");
+    // However, TextComposition may be committed before the composition
+    // event comes here.
+    return false;
   }
   mComposition->StartHandlingComposition(this);
+  return true;
 }
 
 nsresult
 EditorBase::BeginIMEComposition(WidgetCompositionEvent* aCompositionEvent)
 {
   MOZ_ASSERT(!mComposition, "There is composition already");
-  EnsureComposition(aCompositionEvent);
+  if (!EnsureComposition(aCompositionEvent)) {
+    return NS_OK;
+  }
   if (mPhonetic) {
     mPhonetic->Truncate(0);
   }
   return NS_OK;
 }
 
 void
 EditorBase::EndIMEComposition()
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -436,18 +436,24 @@ protected:
     // or not.
     return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() &&
            !ShouldSkipSpellCheck();
   }
 
   /**
    * EnsureComposition() should be called by composition event handlers.  This
    * tries to get the composition for the event and set it to mComposition.
+   * However, this may fail because the composition may be committed before
+   * the event comes to the editor.
+   *
+   * @return            true if there is a composition.  Otherwise, for example,
+   *                    a composition event handler in web contents moved focus
+   *                    for committing the composition, returns false.
    */
-  void EnsureComposition(WidgetCompositionEvent* aCompositionEvent);
+  bool EnsureComposition(WidgetCompositionEvent* aCompositionEvent);
 
   nsresult GetSelection(SelectionType aSelectionType,
                         nsISelection** aSelection);
 
 public:
   /**
    * All editor operations which alter the doc should be prefaced
    * with a call to StartOperation, naming the action and direction.
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -839,17 +839,19 @@ TextEditor::UpdateIMEComposition(nsIDOME
   MOZ_ASSERT(aDOMTextEvent, "aDOMTextEvent must not be nullptr");
 
   WidgetCompositionEvent* compositionChangeEvent =
     aDOMTextEvent->WidgetEventPtr()->AsCompositionEvent();
   NS_ENSURE_TRUE(compositionChangeEvent, NS_ERROR_INVALID_ARG);
   MOZ_ASSERT(compositionChangeEvent->mMessage == eCompositionChange,
              "The internal event should be eCompositionChange");
 
-  EnsureComposition(compositionChangeEvent);
+  if (!EnsureComposition(compositionChangeEvent)) {
+    return NS_OK;
+  }
 
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
 
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_STATE(selection);
 
   // NOTE: TextComposition should receive selection change notification before
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -194,16 +194,17 @@ subsuite = clipboard
 skip-if = toolkit == 'android' # bug 1299578
 [test_bug1153237.html]
 [test_bug1154791.html]
 skip-if = os == 'android'
 [test_bug1162952.html]
 [test_bug1181130-1.html]
 [test_bug1181130-2.html]
 [test_bug1186799.html]
+[test_bug1230473.html]
 [test_bug1247483.html]
 subsuite = clipboard
 skip-if = toolkit == 'android'
 [test_bug1248128.html]
 [test_bug1250010.html]
 [test_bug1257363.html]
 [test_bug1248185.html]
 [test_bug1258085.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1230473.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1230473
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1230473</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1230473">Mozilla Bug 1230473</a>
+<input id="input">
+<textarea id="textarea"></textarea>
+<div id="div" contenteditable></div>
+<script type="application/javascript">
+/** Test for Bug 1230473 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(()=>{
+  function runTest(aEditor) {
+    function committer() {
+      aEditor.blur();
+      aEditor.focus();
+    }
+    function isNSEditableElement() {
+      return aEditor.tagName.toLowerCase() == "input" || aEditor.tagName.toLowerCase() == "textarea";
+    }
+    function value() {
+      return isNSEditableElement() ? aEditor.value : aEditor.textContent;
+    }
+    function isComposing() {
+      return isNSEditableElement() ?  SpecialPowers.wrap(aEditor)
+                                                   .QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement)
+                                                   .editor
+                                                   .QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
+                                                   .composing :
+                                      SpecialPowers.wrap(window)
+                                                   .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+                                                   .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+                                                   .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+                                                   .editor
+                                                   .QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
+                                                   .composing;
+    }
+    function clear() {
+      if (isNSEditableElement()) {
+        aEditor.value = "";
+      } else {
+        aEditor.textContent = "";
+      }
+    }
+
+    clear();
+
+    // Committing at compositionstart
+    aEditor.focus();
+    aEditor.addEventListener("compositionstart", committer, true);
+    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 1, length: 0 }});
+    aEditor.removeEventListener("compositionstart", committer, true);
+    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionstart event handler");
+    is(value(), "", "composition in " + aEditor.id + " shouldn't insert any text since it's committed at compositionstart");
+    clear();
+
+    // Committing at first compositionupdate
+    aEditor.focus();
+    aEditor.addEventListener("compositionupdate", committer, true);
+    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 1, length: 0 }});
+    aEditor.removeEventListener("compositionupdate", committer, true);
+    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
+    is(value(), "", "composition in " + aEditor.id + " shouldn't have inserted any text since it's committed at first compositionupdate");
+    clear();
+
+    // Committing at first text (eCompositionChange)
+    aEditor.focus();
+    aEditor.addEventListener("text", committer, true);
+    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 1, length: 0 }});
+    aEditor.removeEventListener("text", committer, true);
+    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+    is(value(), "", "composition in " + aEditor.id + " should have inserted any text since it's committed at first text");
+    clear();
+
+    // Committing at second compositionupdate
+    aEditor.focus();
+    synthesizeComposition({ type: "compositionstart" });
+    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 1, length: 0 }});
+    ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second compositionupdate");
+    is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second compositionupdate");
+    aEditor.addEventListener("compositionupdate", committer, true);
+    synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 2, length: 0 }});
+    aEditor.removeEventListener("compositionupdate", committer, true);
+    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
+    todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second compositionupdate");
+    clear();
+
+    // Committing at second text (eCompositionChange)
+    aEditor.focus();
+    synthesizeComposition({ type: "compositionstart" });
+    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 1, length: 0 }});
+    ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second text");
+    is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second text");
+    aEditor.addEventListener("text", committer, true);
+    synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                  caret: { start: 2, length: 0 }});
+    aEditor.removeEventListener("text", committer, true);
+    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+    todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second text");
+    clear();
+  }
+  runTest(document.getElementById("input"));
+  runTest(document.getElementById("textarea"));
+  runTest(document.getElementById("div"));
+  SimpleTest.finish();
+});
+</script>
+</body>
+</html>
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -410,17 +410,17 @@ GPUProcessManager::DestroyProcess()
   if (mVsyncBridge) {
     mVsyncBridge->Close();
     mVsyncBridge = nullptr;
   }
 }
 
 RefPtr<CompositorSession>
 GPUProcessManager::CreateTopLevelCompositor(nsBaseWidget* aWidget,
-                                            ClientLayerManager* aLayerManager,
+                                            LayerManager* aLayerManager,
                                             CSSToLayoutDeviceScale aScale,
                                             bool aUseAPZ,
                                             bool aUseExternalSurfaceSize,
                                             const gfx::IntSize& aSurfaceSize)
 {
   uint64_t layerTreeId = AllocateLayerTreeId();
 
   EnsureGPUReady();
@@ -451,17 +451,17 @@ GPUProcessManager::CreateTopLevelComposi
     aScale,
     aUseAPZ,
     aUseExternalSurfaceSize,
     aSurfaceSize);
 }
 
 RefPtr<CompositorSession>
 GPUProcessManager::CreateRemoteSession(nsBaseWidget* aWidget,
-                                       ClientLayerManager* aLayerManager,
+                                       LayerManager* aLayerManager,
                                        const uint64_t& aRootLayerTreeId,
                                        CSSToLayoutDeviceScale aScale,
                                        bool aUseAPZ,
                                        bool aUseExternalSurfaceSize,
                                        const gfx::IntSize& aSurfaceSize)
 {
 #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
   ipc::Endpoint<PCompositorBridgeParent> parentPipe;
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -20,17 +20,16 @@
 #include "nsThreadUtils.h"
 class nsBaseWidget;
 
 
 namespace mozilla {
 namespace layers {
 class IAPZCTreeManager;
 class CompositorSession;
-class ClientLayerManager;
 class CompositorUpdateObserver;
 class PCompositorBridgeChild;
 class PImageBridgeChild;
 class RemoteCompositorSession;
 } // namespace layers
 namespace widget {
 class CompositorWidget;
 } // namespace widget
@@ -52,20 +51,20 @@ class VsyncIOThreadHolder;
 
 // The GPUProcessManager is a singleton responsible for creating GPU-bound
 // objects that may live in another process. Currently, it provides access
 // to the compositor via CompositorBridgeParent.
 class GPUProcessManager final : public GPUProcessHost::Listener
 {
   friend class layers::RemoteCompositorSession;
 
-  typedef layers::ClientLayerManager ClientLayerManager;
   typedef layers::CompositorSession CompositorSession;
+  typedef layers::CompositorUpdateObserver CompositorUpdateObserver;
   typedef layers::IAPZCTreeManager IAPZCTreeManager;
-  typedef layers::CompositorUpdateObserver CompositorUpdateObserver;
+  typedef layers::LayerManager LayerManager;
   typedef layers::PCompositorBridgeChild PCompositorBridgeChild;
   typedef layers::PImageBridgeChild PImageBridgeChild;
   typedef layers::RemoteCompositorSession RemoteCompositorSession;
 
 public:
   static void Initialize();
   static void Shutdown();
   static GPUProcessManager* Get();
@@ -77,17 +76,17 @@ public:
 
   // Ensure that GPU-bound methods can be used. If no GPU process is being
   // used, or one is launched and ready, this function returns immediately.
   // Otherwise it blocks until the GPU process has finished launching.
   void EnsureGPUReady();
 
   RefPtr<CompositorSession> CreateTopLevelCompositor(
     nsBaseWidget* aWidget,
-    ClientLayerManager* aLayerManager,
+    LayerManager* aLayerManager,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     const gfx::IntSize& aSurfaceSize);
 
   bool CreateContentBridges(
     base::ProcessId aOtherProcess,
     ipc::Endpoint<PCompositorBridgeChild>* aOutCompositor,
@@ -178,17 +177,17 @@ private:
   void EnsureVsyncIOThread();
   void ShutdownVsyncIOThread();
 
   void EnsureImageBridgeChild();
   void EnsureVRManager();
 
   RefPtr<CompositorSession> CreateRemoteSession(
     nsBaseWidget* aWidget,
-    ClientLayerManager* aLayerManager,
+    LayerManager* aLayerManager,
     const uint64_t& aRootLayerTreeId,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     const gfx::IntSize& aSurfaceSize);
 
   DISALLOW_COPY_AND_ASSIGN(GPUProcessManager);
 
--- a/gfx/ipc/InProcessCompositorSession.cpp
+++ b/gfx/ipc/InProcessCompositorSession.cpp
@@ -19,17 +19,17 @@ InProcessCompositorSession::InProcessCom
  : CompositorSession(aWidget->AsDelegate(), aChild, aParent->RootLayerTreeId()),
    mCompositorBridgeParent(aParent),
    mCompositorWidget(aWidget)
 {
 }
 
 /* static */ RefPtr<InProcessCompositorSession>
 InProcessCompositorSession::Create(nsIWidget* aWidget,
-                                   ClientLayerManager* aLayerManager,
+                                   LayerManager* aLayerManager,
                                    const uint64_t& aRootLayerTreeId,
                                    CSSToLayoutDeviceScale aScale,
                                    bool aUseAPZ,
                                    bool aUseExternalSurfaceSize,
                                    const gfx::IntSize& aSurfaceSize)
 {
   CompositorWidgetInitData initData;
   aWidget->GetCompositorWidgetInitData(&initData);
--- a/gfx/ipc/InProcessCompositorSession.h
+++ b/gfx/ipc/InProcessCompositorSession.h
@@ -15,17 +15,17 @@ namespace layers {
 
 // A CompositorSession where both the child and parent CompositorBridge reside
 // in the same process.
 class InProcessCompositorSession final : public CompositorSession
 {
 public:
   static RefPtr<InProcessCompositorSession> Create(
     nsIWidget* aWidget,
-    ClientLayerManager* aLayerManager,
+    LayerManager* aLayerManager,
     const uint64_t& aRootLayerTreeId,
     CSSToLayoutDeviceScale aScale,
     bool aUseAPZ,
     bool aUseExternalSurfaceSize,
     const gfx::IntSize& aSurfaceSize);
 
   CompositorBridgeParent* GetInProcessBridge() const override;
   void SetContentController(GeckoContentController* aController) override;
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -69,17 +69,17 @@ static void ShmemAllocated(CompositorBri
     MOZ_PERFORMANCE_WARNING("gfx", "The number of shmem allocations is too damn high!");
   }
 }
 
 static StaticRefPtr<CompositorBridgeChild> sCompositorBridge;
 
 Atomic<int32_t> KnowsCompositor::sSerialCounter(0);
 
-CompositorBridgeChild::CompositorBridgeChild(ClientLayerManager *aLayerManager)
+CompositorBridgeChild::CompositorBridgeChild(LayerManager *aLayerManager)
   : mLayerManager(aLayerManager)
   , mCanSend(false)
   , mFwdTransactionId(0)
   , mMessageLoop(MessageLoop::current())
   , mSectionAllocator(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
@@ -251,17 +251,17 @@ CompositorBridgeChild::InitSameProcess(w
   MOZ_RELEASE_ASSERT(mCanSend);
 
   mCompositorBridgeParent->InitSameProcess(aWidget, aLayerTreeId, aUseAPZ);
   return mCompositorBridgeParent;
 }
 
 /* static */ RefPtr<CompositorBridgeChild>
 CompositorBridgeChild::CreateRemote(const uint64_t& aProcessToken,
-                                    ClientLayerManager* aLayerManager,
+                                    LayerManager* aLayerManager,
                                     Endpoint<PCompositorBridgeChild>&& aEndpoint)
 {
   RefPtr<CompositorBridgeChild> child = new CompositorBridgeChild(aLayerManager);
   if (!aEndpoint.Bind(child)) {
     return nullptr;
   }
 
   child->mCanSend = true;
@@ -521,17 +521,18 @@ CompositorBridgeChild::RecvHideAllPlugin
 
 bool
 CompositorBridgeChild::RecvDidComposite(const uint64_t& aId, const uint64_t& aTransactionId,
                                         const TimeStamp& aCompositeStart,
                                         const TimeStamp& aCompositeEnd)
 {
   if (mLayerManager) {
     MOZ_ASSERT(aId == 0);
-    RefPtr<ClientLayerManager> m = mLayerManager;
+    RefPtr<ClientLayerManager> m = mLayerManager->AsClientLayerManager();
+    MOZ_ASSERT(m);
     m->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd);
   } else if (aId != 0) {
     RefPtr<dom::TabChild> child = dom::TabChild::GetFrom(aId);
     if (child) {
       child->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd);
     }
   }
 
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -45,17 +45,17 @@ struct FrameMetrics;
 class CompositorBridgeChild final : public PCompositorBridgeChild,
                                     public TextureForwarder
 {
   typedef InfallibleTArray<AsyncParentMessageData> AsyncParentMessageArray;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorBridgeChild, override);
 
-  explicit CompositorBridgeChild(ClientLayerManager *aLayerManager);
+  explicit CompositorBridgeChild(LayerManager *aLayerManager);
 
   void Destroy();
 
   /**
    * Lookup the FrameMetrics shared by the compositor process with the
    * associated FrameMetrics::ViewID. The returned FrameMetrics is used
    * in progressive paint calculations.
    */
@@ -64,17 +64,17 @@ public:
   /**
    * Initialize the singleton compositor bridge for a content process.
    */
   static bool InitForContent(Endpoint<PCompositorBridgeChild>&& aEndpoint);
   static bool ReinitForContent(Endpoint<PCompositorBridgeChild>&& aEndpoint);
 
   static RefPtr<CompositorBridgeChild> CreateRemote(
     const uint64_t& aProcessToken,
-    ClientLayerManager* aLayerManager,
+    LayerManager* aLayerManager,
     Endpoint<PCompositorBridgeChild>&& aEndpoint);
 
   /**
    * Initialize the CompositorBridgeChild, create CompositorBridgeParent, and
    * open a same-process connection.
    */
   CompositorBridgeParent* InitSameProcess(
     widget::CompositorWidget* aWidget,
@@ -277,17 +277,17 @@ private:
     // the shared FrameMetrics
     RefPtr<mozilla::ipc::SharedMemoryBasic> mBuffer;
     CrossProcessMutex* mMutex;
     uint64_t mLayersId;
     // Unique ID of the APZC that is sharing the FrameMetrics
     uint32_t mAPZCId;
   };
 
-  RefPtr<ClientLayerManager> mLayerManager;
+  RefPtr<LayerManager> mLayerManager;
   // When not multi-process, hold a reference to the CompositorBridgeParent to keep it
   // alive. This reference should be null in multi-process.
   RefPtr<CompositorBridgeParent> mCompositorBridgeParent;
 
   // The ViewID of the FrameMetrics is used as the key for this hash table.
   // While this should be safe to use since the ViewID is unique
   nsClassHashtable<nsUint64HashKey, SharedFrameMetricsData> mFrameMetricsTable;
 
--- a/gfx/thebes/gfxQuartzNativeDrawing.cpp
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -19,17 +19,18 @@ gfxQuartzNativeDrawing::gfxQuartzNativeD
 }
 
 CGContextRef
 gfxQuartzNativeDrawing::BeginNativeDrawing()
 {
   NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress");
 
   DrawTarget *dt = mDrawTarget;
-  if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget()) {
+  if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() ||
+      dt->GetBackendType() != BackendType::SKIA) {
     // We need a DrawTarget that we can get a CGContextRef from:
     Matrix transform = dt->GetTransform();
 
     mNativeRect = transform.TransformBounds(mNativeRect);
     mNativeRect.RoundOut();
     // Quartz theme drawing often adjusts drawing rects, so make
     // sure our surface is big enough for that.
     mNativeRect.Inflate(5);
@@ -44,16 +45,17 @@ gfxQuartzNativeDrawing::BeginNativeDrawi
 
     if (mTempDrawTarget) {
         transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
         mTempDrawTarget->SetTransform(transform);
     }
     dt = mTempDrawTarget;
   }
   if (dt) {
+    MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
     mCGContext = mBorrowedContext.Init(dt);
     MOZ_ASSERT(mCGContext);
   }
   return mCGContext;
 }
 
 void
 gfxQuartzNativeDrawing::EndNativeDrawing()
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -168,9 +168,21 @@ VRControllerHost::SetIndex(uint32_t aInd
 {
   mIndex = aIndex;
 }
 
 uint32_t
 VRControllerHost::GetIndex()
 {
   return mIndex;
-}
\ No newline at end of file
+}
+
+void
+VRControllerHost::SetButtonPressed(uint64_t aBit)
+{
+  mButtonPressed = aBit;
+}
+
+uint64_t
+VRControllerHost::GetButtonPressed()
+{
+  return mButtonPressed;
+}
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -85,22 +85,26 @@ private:
 
 class VRControllerHost {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerHost)
 
   const VRControllerInfo& GetControllerInfo() const;
   void SetIndex(uint32_t aIndex);
   uint32_t GetIndex();
+  void SetButtonPressed(uint64_t aBit);
+  uint64_t GetButtonPressed();
 
 protected:
   explicit VRControllerHost(VRDeviceType aType);
   virtual ~VRControllerHost();
 
   VRControllerInfo mControllerInfo;
   // The controller index in VRControllerManager.
   uint32_t mIndex;
+  // The current button pressed bit of button mask.
+  uint64_t mButtonPressed;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_HOST_H */
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -1,17 +1,20 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>
 
 #include "gfxVR.h"
-#include "mozilla/dom/Gamepad.h"
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#endif
 
 #ifndef M_PI
 # define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
@@ -60,18 +63,17 @@ VRFieldOfView::ConstructProjectionMatrix
 
 /* static */ uint32_t
 VRControllerManager::AllocateControllerID()
 {
   return ++sControllerBase;
 }
 
 void
-VRControllerManager::AddGamepad(const char* aID,
-                                dom::GamepadMappingType aMapping,
+VRControllerManager::AddGamepad(const char* aID, uint32_t aMapping,
                                 uint32_t aNumButtons, uint32_t aNumAxes)
 {
   dom::GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), mControllerCount,
                      aMapping, dom::GamepadServiceType::VR, aNumButtons,
                      aNumAxes);
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -10,17 +10,16 @@
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnumBits.h"
-#include "mozilla/dom/GamepadBinding.h"
 
 namespace mozilla {
 namespace layers {
 class PTextureParent;
 }
 namespace gfx {
 class VRLayerParent;
 class VRDisplayHost;
@@ -211,24 +210,24 @@ protected:
   virtual ~VRDisplayManager() { }
 };
 
 struct VRControllerInfo
 {
   VRDeviceType GetType() const { return mType; }
   uint32_t GetControllerID() const { return mControllerID; }
   const nsCString& GetControllerName() const { return mControllerName; }
-  dom::GamepadMappingType GetMappingType() const { return mMappingType; }
+  uint32_t GetMappingType() const { return mMappingType; }
   uint32_t GetNumButtons() const { return mNumButtons; }
   uint32_t GetNumAxes() const { return mNumAxes; }
 
   uint32_t mControllerID;
   VRDeviceType mType;
   nsCString mControllerName;
-  dom::GamepadMappingType mMappingType;
+  uint32_t mMappingType;
   uint32_t mNumButtons;
   uint32_t mNumAxes;
 
   bool operator==(const VRControllerInfo& other) const {
   return mType == other.mType &&
          mControllerID == other.mControllerID &&
          mControllerName == other.mControllerName &&
          mMappingType == other.mMappingType &&
@@ -248,17 +247,17 @@ public:
   static uint32_t AllocateControllerID();
   virtual bool Init() = 0;
   virtual void Destroy() = 0;
   virtual void HandleInput() = 0;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
   virtual void ScanForDevices() = 0;
   void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
   void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
-  void AddGamepad(const char* aID, dom::GamepadMappingType aMapping,
+  void AddGamepad(const char* aID, uint32_t aMapping,
                   uint32_t aNumButtons, uint32_t aNumAxes);
   void RemoveGamepad(uint32_t aIndex);
 
 protected:
   VRControllerManager() : mInstalled(false), mControllerCount(0) {}
   virtual ~VRControllerManager() {}
 
   bool mInstalled;
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -19,17 +19,21 @@
 #include "TextureD3D11.h"
 #endif // XP_WIN
 
 #include "gfxVROpenVR.h"
 
 #include "nsServiceManagerUtils.h"
 #include "nsIScreenManager.h"
 #include "openvr/openvr.h"
-#include "mozilla/dom/Gamepad.h"
+
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#endif
 
 #ifndef M_PI
 # define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::gfx::impl;
@@ -480,17 +484,21 @@ VRDisplayManagerOpenVR::GetHMDs(nsTArray
   }
 }
 
 VRControllerOpenVR::VRControllerOpenVR()
   : VRControllerHost(VRDeviceType::OpenVR)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
   mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
-  mControllerInfo.mMappingType = dom::GamepadMappingType::_empty;
+#ifdef MOZ_GAMEPAD
+  mControllerInfo.mMappingType = static_cast<uint32_t>(dom::GamepadMappingType::_empty);
+#else
+  mControllerInfo.mMappingType = 0;
+#endif
   mControllerInfo.mNumButtons = gNumOpenVRButtonMask;
   mControllerInfo.mNumAxes = gNumOpenVRAxis;
 }
 
 VRControllerOpenVR::~VRControllerOpenVR()
 {
   MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
 }
@@ -578,19 +586,17 @@ VRControllerManagerOpenVR::HandleInput()
   // Process OpenVR controller state
   for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
     controller = mOpenVRController[i];
 
     MOZ_ASSERT(mVRSystem->GetTrackedDeviceClass(controller->GetTrackedIndex())
                == vr::TrackedDeviceClass_Controller);
 
     if (mVRSystem->GetControllerState(controller->GetTrackedIndex(), &state)) {
-      if (state.ulButtonPressed) {
-        HandleButtonPress(controller->GetIndex(), state.ulButtonPressed);
-      }
+      HandleButtonPress(controller->GetIndex(), state.ulButtonPressed);
 
       axis = static_cast<uint32_t>(VRControllerAxisType::TrackpadXAxis);
       HandleAxisMove(controller->GetIndex(), axis,
                      state.rAxis[gOpenVRAxes[axis]].x);
 
       axis = static_cast<uint32_t>(VRControllerAxisType::TrackpadYAxis);
       HandleAxisMove(controller->GetIndex(), axis,
                      state.rAxis[gOpenVRAxes[axis]].y);
@@ -602,21 +608,36 @@ VRControllerManagerOpenVR::HandleInput()
   }
 }
 
 void
 VRControllerManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx,
                                              uint64_t aButtonPressed)
 {
   uint64_t buttonMask = 0;
+  RefPtr<impl::VRControllerOpenVR> controller;
+  controller = mOpenVRController[aControllerIdx];
+  uint64_t diff = (controller->GetButtonPressed() ^ aButtonPressed);
+
+  if (!diff) {
+    return;
+  }
 
   for (uint32_t i = 0; i < gNumOpenVRButtonMask; ++i) {
     buttonMask = gOpenVRButtonMask[i];
-    NewButtonEvent(aControllerIdx, i, aButtonPressed & buttonMask);
+
+    if (diff & buttonMask) {
+      // diff & aButtonPressed would be true while a new button press
+      // event, otherwise it is an old press event and needs to notify
+      // the button has been released.
+      NewButtonEvent(aControllerIdx, i, diff & aButtonPressed);
+    }
   }
+
+  controller->SetButtonPressed(aButtonPressed);
 }
 
 void
 VRControllerManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                                           float aValue)
 {
   if (aValue != 0.0f) {
     NewAxisMove(aControllerIdx, aAxis, aValue);
@@ -660,14 +681,17 @@ VRControllerManagerOpenVR::ScanForDevice
       continue;
     }
 
     RefPtr<VRControllerOpenVR> openVRController = new VRControllerOpenVR();
     openVRController->SetIndex(mControllerCount);
     openVRController->SetTrackedIndex(trackedDevice);
     mOpenVRController.AppendElement(openVRController);
 
+// Only in MOZ_GAMEPAD platform, We add gamepads.
+#ifdef MOZ_GAMEPAD
     // Not already present, add it.
-    AddGamepad("OpenVR Gamepad", GamepadMappingType::_empty,
+    AddGamepad("OpenVR Gamepad", static_cast<uint32_t>(GamepadMappingType::_empty),
                gNumOpenVRButtonMask, gNumOpenVRAxis);
     ++mControllerCount;
+#endif
   }
 }
\ No newline at end of file
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -10,20 +10,23 @@
 #include "VRDisplayClient.h"
 #include "nsGlobalWindow.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/layers/CompositorThread.h" // for CompositorThread
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/VREventObserver.h"
 #include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback
 #include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/GamepadManager.h"
 #include "mozilla/layers/TextureClient.h"
 #include "nsContentUtils.h"
 
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadManager.h"
+#endif
+
 using layers::TextureClient;
 
 namespace {
 const nsTArray<RefPtr<dom::VREventObserver>>::index_type kNoIndex =
   nsTArray<RefPtr<dom::VREventObserver> >::NoIndex;
 } // namespace
 
 namespace mozilla {
@@ -465,22 +468,24 @@ VRManagerChild::RecvNotifyVRVSync(const 
   }
 
   return true;
 }
 
 bool
 VRManagerChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
 {
+#ifdef MOZ_GAMEPAD
   // VRManagerChild could be at other processes, but GamepadManager
   // only exists at the content process or the parent process
   // in non-e10s mode.
   if (mGamepadManager) {
       mGamepadManager->Update(aGamepadEvent);
   }
+#endif
 
   return true;
 }
 
 void
 VRManagerChild::RunFrameRequestCallbacks()
 {
   TimeStamp nowTime = TimeStamp::Now();
--- a/ipc/mscom/MainThreadHandoff.cpp
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -251,20 +251,28 @@ MainThreadHandoff::FixArrayElements(ICal
     // ICallFrame::GetParam is not able to coerce the param into a VARIANT.
     // That's ok, we can try to do it ourselves.
     CALLFRAMEPARAMINFO paramInfo;
     hr = aFrame->GetParamInfo(aArrayData.mArrayParamIndex, &paramInfo);
     if (FAILED(hr)) {
       return hr;
     }
     PVOID stackBase = aFrame->GetStackLocation();
-    // We dereference because we need to obtain the value of a parameter
-    // from a stack offset. This pointer is the base of the array.
-    arrayPtr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(stackBase) +
-                                         paramInfo.stackOffset);
+    if (aArrayData.mFlag == ArrayData::Flag::eAllocatedByServer) {
+      // In order for the server to allocate the array's buffer and store it in
+      // an outparam, the parameter must be typed as Type***. Since the base
+      // of the array is Type*, we must dereference twice.
+      arrayPtr = **reinterpret_cast<PVOID**>(reinterpret_cast<PBYTE>(stackBase) +
+                                             paramInfo.stackOffset);
+    } else {
+      // We dereference because we need to obtain the value of a parameter
+      // from a stack offset. This pointer is the base of the array.
+      arrayPtr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(stackBase) +
+                                           paramInfo.stackOffset);
+    }
   } else if (FAILED(hr)) {
     return hr;
   } else {
     arrayPtr = ResolveArrayPtr(paramVal);
   }
 
   MOZ_ASSERT(arrayPtr);
   if (!arrayPtr) {
--- a/ipc/mscom/Registration.h
+++ b/ipc/mscom/Registration.h
@@ -76,47 +76,59 @@ UniquePtr<RegisteredProxy> RegisterTypel
  * The COM interceptor uses type library information to build its interface
  * proxies. Unfortunately type libraries do not encode size_is and length_is
  * annotations that have been specified in IDL. This structure allows us to
  * explicitly declare such relationships so that the COM interceptor may
  * be made aware of them.
  */
 struct ArrayData
 {
+  enum class Flag
+  {
+    eNone = 0,
+    eAllocatedByServer = 1 // This implies an extra level of indirection
+  };
+
   ArrayData(REFIID aIid, ULONG aMethodIndex, ULONG aArrayParamIndex,
             VARTYPE aArrayParamType, REFIID aArrayParamIid,
-            ULONG aLengthParamIndex)
+            ULONG aLengthParamIndex, Flag aFlag = Flag::eNone)
     : mIid(aIid)
     , mMethodIndex(aMethodIndex)
     , mArrayParamIndex(aArrayParamIndex)
     , mArrayParamType(aArrayParamType)
     , mArrayParamIid(aArrayParamIid)
     , mLengthParamIndex(aLengthParamIndex)
+    , mFlag(aFlag)
   {
   }
+
   ArrayData(const ArrayData& aOther)
   {
     *this = aOther;
   }
+
   ArrayData& operator=(const ArrayData& aOther)
   {
     mIid = aOther.mIid;
     mMethodIndex = aOther.mMethodIndex;
     mArrayParamIndex = aOther.mArrayParamIndex;
     mArrayParamType = aOther.mArrayParamType;
     mArrayParamIid = aOther.mArrayParamIid;
     mLengthParamIndex = aOther.mLengthParamIndex;
+    mFlag = aOther.mFlag;
     return *this;
   }
+
   IID     mIid;
   ULONG   mMethodIndex;
   ULONG   mArrayParamIndex;
   VARTYPE mArrayParamType;
   IID     mArrayParamIid;
   ULONG   mLengthParamIndex;
+  Flag    mFlag;
 };
 
 void RegisterArrayData(const ArrayData* aArrayData, size_t aLength);
 
 template <size_t N>
 inline void
 RegisterArrayData(const ArrayData (&aData)[N])
 {
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -92,16 +92,22 @@ uint8_t gImageLayerUserData;
  */
 uint8_t gLayerManagerUserData;
 /**
  * The address of gMaskLayerUserData is used as the user
  * data key for mask layers managed by FrameLayerBuilder.
  * The user data is a MaskLayerUserData.
  */
 uint8_t gMaskLayerUserData;
+/**
+ * The address of gCSSMaskLayerUserData is used as the user
+ * data key for mask layers of css masking managed by FrameLayerBuilder.
+ * The user data is a CSSMaskLayerUserData.
+ */
+uint8_t gCSSMaskLayerUserData;
 
 // a global cache of image containers used for mask layers
 static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
 
 static inline MaskLayerImageCache* GetMaskLayerImageCache()
 {
   if (!gMaskLayerImageCache) {
     gMaskLayerImageCache = new MaskLayerImageCache();
@@ -1247,17 +1253,19 @@ protected:
    * a layer doesn't exist.
    *
    * Since mask layers can exist either on the layer directly, or as a side-
    * attachment to FrameMetrics (for ancestor scrollframe clips), we key the
    * recycle operation on both the originating layer and the mask layer's
    * index in the layer, if any.
    */
   struct MaskLayerKey;
-  already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey);
+  already_AddRefed<ImageLayer>
+  CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey,
+                                   mozilla::function<void(Layer* aLayer)> aSetUserData);
   /**
    * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes them
    * available for recycling.
    */
   void CollectOldLayers();
   /**
    * If aItem used to belong to a PaintedLayer, invalidates the area of
    * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area of
@@ -1340,16 +1348,22 @@ protected:
    * add it to |aLayer|'s ancestor mask layers, returning an index into
    * the array of ancestor mask layers. Returns an empty Maybe if
    * |aClip| does not have rounded corners, or if no mask layer could
    * be created.
    */
   Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer,
                                               const DisplayItemClip& aClip);
 
+  /*
+   * Create/find a mask layer with suitable size for aMaskItem to paint
+   * css-positioned-masking onto.
+   */
+  void SetupMaskLayerForCSSMask(Layer* aLayer, nsDisplayMask* aMaskItem);
+
   already_AddRefed<Layer> CreateMaskLayer(
     Layer *aLayer, const DisplayItemClip& aClip,
     const Maybe<size_t>& aForAncestorMaskLayer,
     uint32_t aRoundedRectClipCount = UINT32_MAX);
 
   bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
                                   AnimatedGeometryRoot **aAnimatedGeometryRoot);
 
@@ -1499,16 +1513,36 @@ public:
  */
 struct MaskLayerUserData : public LayerUserData
 {
   MaskLayerUserData()
     : mScaleX(-1.0f)
     , mScaleY(-1.0f)
     , mAppUnitsPerDevPixel(-1)
   { }
+  MaskLayerUserData(const DisplayItemClip& aClip,
+                    uint32_t aRoundedRectClipCount,
+                    int32_t aAppUnitsPerDevPixel,
+                    const ContainerLayerParameters& aParams)
+    : mScaleX(aParams.mXScale)
+    , mScaleY(aParams.mYScale)
+    , mOffset(aParams.mOffset)
+    , mAppUnitsPerDevPixel(aAppUnitsPerDevPixel)
+  {
+    aClip.AppendRoundedRects(&mRoundedClipRects, aRoundedRectClipCount);
+  }
+
+  void operator=(MaskLayerUserData&& aOther)
+  {
+    mScaleX = aOther.mScaleX;
+    mScaleY = aOther.mScaleY;
+    mOffset = aOther.mOffset;
+    mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel;
+    mRoundedClipRects.SwapElements(aOther.mRoundedClipRects);
+  }
 
   bool
   operator== (const MaskLayerUserData& aOther) const
   {
     return mRoundedClipRects == aOther.mRoundedClipRects &&
            mScaleX == aOther.mScaleX &&
            mScaleY == aOther.mScaleY &&
            mOffset == aOther.mOffset &&
@@ -1522,39 +1556,139 @@ struct MaskLayerUserData : public LayerU
   nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
   // scale from the masked layer which is applied to the mask
   float mScaleX, mScaleY;
   // The ContainerLayerParameters offset which is applied to the mask's transform.
   nsIntPoint mOffset;
   int32_t mAppUnitsPerDevPixel;
 };
 
+/*
+ * User data for layers which will be used as masks for css positioned mask.
+ */
+struct CSSMaskLayerUserData : public LayerUserData
+{
+  CSSMaskLayerUserData()
+    : mImageLayers(nsStyleImageLayers::LayerType::Mask)
+  { }
+
+  CSSMaskLayerUserData(nsIFrame* aFrame, const nsRect& aBound)
+    : mImageLayers(aFrame->StyleSVGReset()->mMask),
+      mContentRect(aFrame->GetContentRectRelativeToSelf()),
+      mPaddingRect(aFrame->GetPaddingRectRelativeToSelf()),
+      mBorderRect(aFrame->GetRectRelativeToSelf()),
+      mMarginRect(aFrame->GetMarginRectRelativeToSelf()),
+      mBounds(aBound)
+  {
+    Hash(aFrame);
+  }
+
+  CSSMaskLayerUserData& operator=(const CSSMaskLayerUserData& aOther)
+  {
+    mImageLayers = aOther.mImageLayers;
+
+    mContentRect = aOther.mContentRect;
+    mPaddingRect = aOther.mPaddingRect;
+    mBorderRect = aOther.mBorderRect;
+    mMarginRect = aOther.mMarginRect;
+
+    mBounds = aOther.mBounds;
+
+    mHash = aOther.mHash;
+
+    return *this;
+  }
+
+  bool
+  operator==(const CSSMaskLayerUserData& aOther) const
+  {
+    if (mHash != aOther.mHash) {
+      return false;
+    }
+
+    if (mImageLayers.mLayers != aOther.mImageLayers.mLayers) {
+      return false;
+    }
+
+    if (!mContentRect.IsEqualEdges(aOther.mContentRect) ||
+        !mPaddingRect.IsEqualEdges(aOther.mPaddingRect) ||
+        !mBorderRect.IsEqualEdges(aOther.mBorderRect) ||
+        !mMarginRect.IsEqualEdges(aOther.mMarginRect)) {
+      return false;
+    }
+
+    if (!mBounds.IsEqualEdges(aOther.mBounds)) {
+      return false;
+    }
+
+    return true;
+  }
+
+private:
+  void Hash(nsIFrame* aFrame)
+  {
+    uint32_t hash = 0;
+
+    const nsStyleImageLayers& imageLayers = aFrame->StyleSVGReset()->mMask;
+    for (uint32_t i = 0; i < imageLayers.mLayers.Length(); i++) {
+      const nsStyleImageLayers::Layer& newLayer = imageLayers.mLayers[i];
+      hash = AddToHash(hash, HashBytes(&newLayer, sizeof(newLayer)));
+    }
+
+    hash = AddToHash(hash, HashBytes(&mContentRect, sizeof(mContentRect)));
+    hash = AddToHash(hash, HashBytes(&mPaddingRect, sizeof(mPaddingRect)));
+    hash = AddToHash(hash, HashBytes(&mBorderRect, sizeof(mBorderRect)));
+    hash = AddToHash(hash, HashBytes(&mMarginRect, sizeof(mMarginRect)));
+
+    hash = AddToHash(hash, HashBytes(&mBounds, sizeof(mBounds)));
+
+    mHash = hash;
+  }
+
+  nsStyleImageLayers mImageLayers;
+
+  nsRect mContentRect;
+  nsRect mPaddingRect;
+  nsRect mBorderRect;
+  nsRect mMarginRect;
+
+  nsRect mBounds;
+
+  uint32_t mHash;
+};
+
+/*
+ * A helper object to create a draw target for painting mask and create a
+ * image container to hold the drawing result. The caller can then bind this
+ * image container with a image mask layer via ImageLayer::SetContainer.
+ */
 class MaskImageData
 {
 public:
   MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
     : mTextureClientLocked(false)
     , mSize(aSize)
     , mLayerManager(aLayerManager)
   {
+    MOZ_ASSERT(!mSize.IsEmpty());
+    MOZ_ASSERT(mLayerManager);
   }
 
   ~MaskImageData()
   {
     if (mTextureClientLocked) {
       MOZ_ASSERT(mTextureClient);
       // Clear DrawTarget before Unlock.
       mDrawTarget = nullptr;
       mTextureClient->Unlock();
     }
   }
 
   gfx::DrawTarget* CreateDrawTarget()
   {
-    MOZ_ASSERT(mLayerManager);
     if (mDrawTarget) {
       return mDrawTarget;
     }
 
     if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
       mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
       return mDrawTarget;
     }
@@ -1580,16 +1714,30 @@ public:
     if (!mTextureClientLocked) {
       return nullptr;
     }
 
     mDrawTarget = mTextureClient->BorrowDrawTarget();
     return mDrawTarget;
   }
 
+  already_AddRefed<ImageContainer> CreateImageAndImageContainer()
+  {
+    RefPtr<ImageContainer> container = mLayerManager->CreateImageContainer();
+    RefPtr<Image> image = CreateImage();
+
+    if (!image) {
+      return nullptr;
+    }
+    container->SetCurrentImageInTransaction(image);
+
+    return container.forget();
+  }
+
+private:
   already_AddRefed<Image> CreateImage()
   {
     if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
         mDrawTarget) {
       RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
       RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
       // Disallow BIGIMAGE (splitting into multiple textures) for mask
       // layer images
@@ -1599,20 +1747,20 @@ public:
 
     if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT &&
         mTextureClient &&
         mDrawTarget) {
       RefPtr<TextureWrapperImage> image =
           new TextureWrapperImage(mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
       return image.forget();
     }
+
     return nullptr;
   }
 
-private:
   bool mTextureClientLocked;
   gfx::IntSize mSize;
   LayerManager* mLayerManager;
   RefPtr<gfx::DrawTarget> mDrawTarget;
   RefPtr<TextureClient> mTextureClient;
 };
 
 /**
@@ -2102,29 +2250,30 @@ ContainerState::CreateOrRecycleImageLaye
 
     // Remove other layer types we might have stored for this PaintedLayer
     data->mColorLayer = nullptr;
   }
   return layer.forget();
 }
 
 already_AddRefed<ImageLayer>
-ContainerState::CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey)
+ContainerState::CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey,
+                                                 mozilla::function<void(Layer* aLayer)> aSetUserData)
 {
   RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey);
   if (result) {
     mRecycledMaskImageLayers.Remove(aKey);
     aKey.mLayer->ClearExtraDumpInfo();
     // XXX if we use clip on mask layers, null it out here
   } else {
     // Create a new layer
     result = mManager->CreateImageLayer();
     if (!result)
       return nullptr;
-    result->SetUserData(&gMaskLayerUserData, new MaskLayerUserData());
+    aSetUserData(result);
   }
 
   return result.forget();
 }
 
 static const double SUBPIXEL_OFFSET_EPSILON = 0.02;
 
 /**
@@ -3740,16 +3889,90 @@ ContainerState::SetupMaskLayerForScrolle
       aLayer->AddAncestorMaskLayer(maskLayer);
       return maskLayerIndex;
     }
     // Fall through to |return Nothing()|.
   }
   return Nothing();
 }
 
+void
+ContainerState::SetupMaskLayerForCSSMask(Layer* aLayer,
+                                         nsDisplayMask* aMaskItem)
+{
+  MOZ_ASSERT(mManager->IsCompositingCheap());
+
+  RefPtr<ImageLayer> maskLayer =
+    CreateOrRecycleMaskImageLayerFor(MaskLayerKey(aLayer, Nothing()),
+      [](Layer* aMaskLayer)
+      {
+        aMaskLayer->SetUserData(&gCSSMaskLayerUserData,
+                                new CSSMaskLayerUserData());
+      }
+    );
+
+  CSSMaskLayerUserData* oldUserData =
+    static_cast<CSSMaskLayerUserData*>(maskLayer->GetUserData(&gCSSMaskLayerUserData));
+
+  bool snap;
+  nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
+  CSSMaskLayerUserData newUserData(aMaskItem->Frame(), bounds);
+  if (*oldUserData == newUserData) {
+    aLayer->SetMaskLayer(maskLayer);
+    return;
+  }
+
+  const nsIFrame* frame = aMaskItem->Frame();
+  int32_t A2D = frame->PresContext()->AppUnitsPerDevPixel();
+  Rect devBounds = NSRectToRect(bounds, A2D);
+  uint32_t maxSize = mManager->GetMaxTextureSize();
+  gfx::Size surfaceSize(std::min<gfx::Float>(devBounds.Width(), maxSize),
+                        std::min<gfx::Float>(devBounds.Height(), maxSize));
+  IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width),
+                         NSToIntCeil(surfaceSize.height));
+
+  if (surfaceSizeInt.IsEmpty()) {
+    return;
+  }
+
+  MaskImageData imageData(surfaceSizeInt, mManager);
+  RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
+  if (!dt || !dt->IsValid()) {
+    NS_WARNING("Could not create DrawTarget for mask layer.");
+    return;
+  }
+
+  RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
+  gfxPoint offset = nsLayoutUtils::PointToGfxPoint(bounds.TopLeft(), A2D);
+  maskCtx->SetMatrix(gfxMatrix::Translation(-offset));
+
+  if (!aMaskItem->PaintMask(mBuilder, maskCtx)) {
+    // Mostly because of mask resource is not ready.
+    return;
+  }
+
+  // Setup mask layer offset.
+  Matrix4x4 matrix;
+  matrix.PreTranslate(offset.x, offset.y, 0);
+  matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
+  matrix.PreScale(mParameters.mXScale, mParameters.mYScale, 1.0);
+
+  maskLayer->SetBaseTransform(matrix);
+
+  RefPtr<ImageContainer> imgContainer =
+    imageData.CreateImageAndImageContainer();
+  if (!imgContainer) {
+    return;
+  }
+  maskLayer->SetContainer(imgContainer);
+
+  *oldUserData = newUserData;
+  aLayer->SetMaskLayer(maskLayer);
+}
+
 /*
  * Iterate through the non-clip items in aList and its descendants.
  * For each item we compute the effective clip rect. Each item is assigned
  * to a layer. We invalidate the areas in PaintedLayers where an item
  * has moved from one PaintedLayer to another. Also,
  * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
  * We set the clip rect for items that generated their own layer, and
  * create a mask layer to do any rounded rect clipping.
@@ -4143,16 +4366,19 @@ ContainerState::ProcessDisplayItems(nsDi
           ownLayer->SetClipRect(Some(layerClipRect));
 
           // rounded rectangle clipping using mask layers
           // (must be done after visible rect is set on layer)
           if (layerClip.GetRoundedRectCount() > 0) {
             SetupMaskLayer(ownLayer, layerClip);
           }
         }
+      } else if (item->GetType() == nsDisplayItem::TYPE_MASK) {
+        nsDisplayMask* maskItem = static_cast<nsDisplayMask*>(item);
+        SetupMaskLayerForCSSMask(ownLayer, maskItem);
       }
 
       ContainerLayer* oldContainer = ownLayer->GetParent();
       if (oldContainer && oldContainer != mContainerLayer) {
         oldContainer->RemoveChild(ownLayer);
       }
       NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
                    "Layer already in list???");
@@ -6021,28 +6247,37 @@ ContainerState::SetupMaskLayer(Layer *aL
 }
 
 already_AddRefed<Layer>
 ContainerState::CreateMaskLayer(Layer *aLayer,
                                const DisplayItemClip& aClip,
                                const Maybe<size_t>& aForAncestorMaskLayer,
                                uint32_t aRoundedRectClipCount)
 {
+  // aLayer will never be the container layer created by an nsDisplayMask
+  // because nsDisplayMask propagates the DisplayItemClip to its contents
+  // and is not clipped itself.
+  // This assertion will fail if that ever stops being the case.
+  MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData),
+             "A layer contains round clips should not have css-mask on it.");
+
   // check if we can re-use the mask layer
   MaskLayerKey recycleKey(aLayer, aForAncestorMaskLayer);
-  RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(recycleKey);
+  RefPtr<ImageLayer> maskLayer =
+    CreateOrRecycleMaskImageLayerFor(recycleKey,
+      [](Layer* aMaskLayer)
+      {
+        aMaskLayer->SetUserData(&gMaskLayerUserData,
+                                new MaskLayerUserData());
+      }
+    );
   MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer);
 
-  MaskLayerUserData newData;
-  aClip.AppendRoundedRects(&newData.mRoundedClipRects, aRoundedRectClipCount);
-  newData.mScaleX = mParameters.mXScale;
-  newData.mScaleY = mParameters.mYScale;
-  newData.mOffset = mParameters.mOffset;
-  newData.mAppUnitsPerDevPixel = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
-
+  int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
+  MaskLayerUserData newData(aClip, aRoundedRectClipCount, A2D, mParameters);
   if (*userData == newData) {
     return maskLayer.forget();
   }
 
   // calculate a more precise bounding rect
   gfx::Rect boundingRect = CalculateBounds(newData.mRoundedClipRects,
                                            newData.mAppUnitsPerDevPixel);
   boundingRect.Scale(mParameters.mXScale, mParameters.mYScale);
@@ -6109,39 +6344,34 @@ ContainerState::CreateMaskLayer(Layer *a
     // paint the clipping rects with alpha to create the mask
     aClip.FillIntersectionOfRoundedRectClips(context,
                                              Color(1.f, 1.f, 1.f, 1.f),
                                              newData.mAppUnitsPerDevPixel,
                                              0,
                                              aRoundedRectClipCount);
 
     // build the image and container
-    container = aLayer->Manager()->CreateImageContainer();
+    MOZ_ASSERT(aLayer->Manager() == mManager);
+    container = imageData.CreateImageAndImageContainer();
     NS_ASSERTION(container, "Could not create image container for mask layer.");
 
-    RefPtr<Image> image = imageData.CreateImage();
-    if (!image) {
+    if (!container) {
       return nullptr;
     }
-    container->SetCurrentImageInTransaction(image);
 
     GetMaskLayerImageCache()->PutImage(newKey.forget(), container);
   }
 
   maskLayer->SetContainer(container);
 
   maskTransform.Invert();
   Matrix4x4 matrix = Matrix4x4::From2D(maskTransform);
   matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
   maskLayer->SetBaseTransform(matrix);
 
   // save the details of the clip in user data
-  userData->mScaleX = newData.mScaleX;
-  userData->mScaleY = newData.mScaleY;
-  userData->mOffset = newData.mOffset;
-  userData->mAppUnitsPerDevPixel = newData.mAppUnitsPerDevPixel;
-  userData->mRoundedClipRects.SwapElements(newData.mRoundedClipRects);
+  *userData = Move(newData);
   userData->mImageKey.Reset(lookupKey);
 
   return maskLayer.forget();
 }
 
 } // namespace mozilla
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -19,16 +19,17 @@
 #include "Layers.h"
 #include "LayerUserData.h"
 
 class nsDisplayListBuilder;
 class nsDisplayList;
 class nsDisplayItem;
 class gfxContext;
 class nsDisplayItemGeometry;
+class nsDisplayMask;
 
 namespace mozilla {
 class DisplayItemScrollClip;
 namespace layers {
 class ContainerLayer;
 class LayerManager;
 class BasicLayerManager;
 class PaintedLayer;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -7047,24 +7047,85 @@ nsDisplayMask::BuildLayer(nsDisplayListB
 
   RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            aContainerParameters, nullptr);
 
   return container.forget();
 }
 
+bool
+nsDisplayMask::PaintMask(nsDisplayListBuilder* aBuilder,
+                         gfxContext* aMaskContext)
+{
+  MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);
+
+  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
+  nsSVGIntegrationUtils::PaintFramesParams params(*aMaskContext,
+                                                  mFrame,  mVisibleRect,
+                                                  borderArea, aBuilder,
+                                                  nullptr,
+                                                  mHandleOpacity);
+  ComputeMaskGeometry(params);
+  image::DrawResult result = nsSVGIntegrationUtils::PaintMask(params);
+
+  nsDisplayMaskGeometry::UpdateDrawResult(this, result);
+  return (result == image::DrawResult::SUCCESS) ? true : false;
+}
+
 LayerState
 nsDisplayMask::GetLayerState(nsDisplayListBuilder* aBuilder,
                              LayerManager* aManager,
                              const ContainerLayerParameters& aParameters)
 {
+  if (ShouldPaintOnMaskLayer(aManager)) {
+    return RequiredLayerStateForChildren(aBuilder, aManager, aParameters,
+                                         mList, GetAnimatedGeometryRoot());
+  }
+
   return LAYER_SVG_EFFECTS;
 }
 
+bool nsDisplayMask::ShouldPaintOnMaskLayer(LayerManager* aManager)
+{
+  if (!aManager->IsCompositingCheap()) {
+    return false;
+  }
+
+  nsSVGIntegrationUtils::MaskUsage maskUsage;
+  nsSVGIntegrationUtils::DetermineMaskUsage(mFrame, mHandleOpacity, maskUsage);
+
+  if (!maskUsage.shouldGenerateMaskLayer ||
+      maskUsage.opacity != 1.0 || maskUsage.shouldApplyClipPath ||
+      maskUsage.shouldApplyBasicShape ||
+      maskUsage.shouldGenerateClipMaskLayer) {
+    return false;
+  }
+
+  if (!nsSVGIntegrationUtils::IsMaskResourceReady(mFrame)) {
+    return false;
+  }
+
+  // XXX temporary disable drawing SVG mask onto mask layer before bug 1313877
+  // been fixed.
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
+  nsSVGEffects::EffectProperties effectProperties =
+    nsSVGEffects::GetEffectProperties(firstFrame);
+  nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+  for (size_t i = 0; i < maskFrames.Length() ; i++) {
+    nsSVGMaskFrame *maskFrame = maskFrames[i];
+    if (maskFrame) {
+      return false; // Found SVG mask.
+    }
+  }
+
+  return true;
+}
+
 bool nsDisplayMask::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                       nsRegion* aVisibleRegion)
 {
   // Our children may be made translucent or arbitrarily deformed so we should
   // not allow them to subtract area from aVisibleRegion.
   nsRegion childrenVisible(mVisibleRect);
   nsRect r = mVisibleRect.Intersect(mList.GetBounds(aBuilder));
   mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
@@ -7108,16 +7169,18 @@ nsDisplayMask::ComputeInvalidationRegion
   }
 }
 
 void
 nsDisplayMask::PaintAsLayer(nsDisplayListBuilder* aBuilder,
                             nsRenderingContext* aCtx,
                             LayerManager* aManager)
 {
+  MOZ_ASSERT(!ShouldPaintOnMaskLayer(aManager));
+
   nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
   nsSVGIntegrationUtils::PaintFramesParams params(*aCtx->ThebesContext(),
                                                   mFrame,  mVisibleRect,
                                                   borderArea, aBuilder,
                                                   aManager,
                                                   mHandleOpacity);
 
   // Clip the drawing target by mVisibleRect, which contains the visible
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -3873,16 +3873,19 @@ protected:
 };
 
 /**
  * A display item to paint a stacking context with mask and clip effects
  * set by the stacking context root frame's style.
  */
 class nsDisplayMask : public nsDisplaySVGEffects {
 public:
+  typedef mozilla::layers::ImageLayer ImageLayer;
+  typedef class mozilla::gfx::DrawTarget DrawTarget;
+
   nsDisplayMask(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                 nsDisplayList* aList, bool aHandleOpacity,
                 const DisplayItemScrollClip* aScrollClip);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayMask();
 #endif
 
   NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK)
@@ -3908,21 +3911,30 @@ public:
 #ifdef MOZ_DUMP_PAINTING
   void PrintEffects(nsACString& aTo);
 #endif
 
   void PaintAsLayer(nsDisplayListBuilder* aBuilder,
                     nsRenderingContext* aCtx,
                     LayerManager* aManager);
 
+  /*
+   * Paint mask onto aMaskContext in mFrame's coordinate space.
+   */
+  bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext);
+
   const nsTArray<nsRect>& GetDestRects()
   {
     return mDestRects;
   }
 private:
+  // According to mask property and the capability of aManager, determine
+  // whether paint mask onto a dedicate mask layer.
+  bool ShouldPaintOnMaskLayer(LayerManager* aManager);
+
   nsTArray<nsRect> mDestRects;
 };
 
 /**
  * A display item to paint a stacking context with filter effects set by the
  * stacking context root frame's style.
  */
 class nsDisplayFilter : public nsDisplaySVGEffects {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -65,16 +65,17 @@
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "gfxPlatform.h"
 #include <algorithm>
 #include <limits>
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "imgIRequest.h"
 #include "nsIImageLoadingContent.h"
 #include "nsCOMPtr.h"
 #include "nsCSSProps.h"
 #include "nsListControlFrame.h"
 #include "mozilla/dom/Element.h"
@@ -3933,24 +3934,53 @@ struct BoxToRect : public nsLayoutUtils:
       r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
     } else {
       r += outer->GetOffsetTo(mRelativeTo);
     }
     mCallback->AddRect(r);
   }
 };
 
+struct BoxToRectAndText : public BoxToRect {
+  mozilla::dom::DOMStringList* mTextList;
+
+  BoxToRectAndText(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback,
+                   mozilla::dom::DOMStringList* aTextList, uint32_t aFlags)
+    : BoxToRect(aRelativeTo, aCallback, aFlags), mTextList(aTextList) {}
+
+  virtual void AddBox(nsIFrame* aFrame) override {
+    BoxToRect::AddBox(aFrame);
+    if (mTextList) {
+      nsIContent* content = aFrame->GetContent();
+      nsAutoString textContent;
+      mozilla::ErrorResult err; // ignored
+      content->GetTextContent(textContent, err);
+      mTextList->Add(textContent);
+    }
+  }
+};
+
 void
 nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                  RectCallback* aCallback, uint32_t aFlags)
 {
   BoxToRect converter(aRelativeTo, aCallback, aFlags);
   GetAllInFlowBoxes(aFrame, &converter);
 }
 
+void
+nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+                                         RectCallback* aCallback,
+                                         mozilla::dom::DOMStringList* aTextList,
+                                         uint32_t aFlags)
+{
+  BoxToRectAndText converter(aRelativeTo, aCallback, aTextList, aFlags);
+  GetAllInFlowBoxes(aFrame, &converter);
+}
+
 nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
 
 void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
   mResultRect.UnionRect(mResultRect, aRect);
   if (!mSeenFirstRect) {
     mSeenFirstRect = true;
     mFirstRect = aRect;
   }
@@ -8864,17 +8894,17 @@ nsLayoutUtils::GetSelectionBoundingRect(
       nsIFrame* relativeTo = GetContainingBlockForClientRect(frame);
       res = TransformFrameRectToAncestor(frame, res, relativeTo);
     }
   } else {
     int32_t rangeCount = aSel->RangeCount();
     RectAccumulator accumulator;
     for (int32_t idx = 0; idx < rangeCount; ++idx) {
       nsRange* range = aSel->GetRangeAt(idx);
-      nsRange::CollectClientRects(&accumulator, range,
+      nsRange::CollectClientRectsAndText(&accumulator, nullptr, range,
                                   range->GetStartParent(), range->StartOffset(),
                                   range->GetEndParent(), range->EndOffset(),
                                   true, false);
     }
     res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
       accumulator.mResultRect;
   }
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1193,16 +1193,21 @@ public:
    * and SVG transforms) are taken into account.
    * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
    * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
    * Otherwise (by default), the border box is used.
    */
   static void GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                 RectCallback* aCallback, uint32_t aFlags = 0);
 
+  static void GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
+                                        RectCallback* aCallback,
+                                        mozilla::dom::DOMStringList* aTextList,
+                                        uint32_t aFlags = 0);
+
   /**
    * Computes the union of all rects returned by GetAllInFlowRects. If
    * the union is empty, returns the first rect.
    * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
    * the boxes into aRelativeTo coordinates, transforms (including CSS
    * and SVG transforms) are taken into account.
    * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
    * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -5881,17 +5881,17 @@ bool
 Selection::ContainsPoint(const nsPoint& aPoint)
 {
   if (IsCollapsed()) {
     return false;
   }
   PointInRectChecker checker(aPoint);
   for (uint32_t i = 0; i < RangeCount(); i++) {
     nsRange* range = GetRangeAt(i);
-    nsRange::CollectClientRects(&checker, range,
+    nsRange::CollectClientRectsAndText(&checker, nullptr, range,
                                 range->GetStartParent(), range->StartOffset(),
                                 range->GetEndParent(), range->EndOffset(),
                                 true, false);
     if (checker.MatchFound()) {
       return true;
     }
   }
   return false;
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2616,16 +2616,51 @@ nsStyleImageLayers::HasLayerWithImage() 
         !mLayers[i].mImage.IsEmpty()) {
       return true;
     }
   }
 
   return false;
 }
 
+nsStyleImageLayers&
+nsStyleImageLayers::operator=(const nsStyleImageLayers& aOther)
+{
+  mAttachmentCount = aOther.mAttachmentCount;
+  mClipCount = aOther.mClipCount;
+  mOriginCount = aOther.mOriginCount;
+  mRepeatCount = aOther.mRepeatCount;
+  mPositionXCount = aOther.mPositionXCount;
+  mPositionYCount = aOther.mPositionYCount;
+  mImageCount = aOther.mImageCount;
+  mSizeCount = aOther.mSizeCount;
+  mMaskModeCount = aOther.mMaskModeCount;
+  mBlendModeCount = aOther.mBlendModeCount;
+  mCompositeCount = aOther.mCompositeCount;
+  mLayers = aOther.mLayers;
+
+  uint32_t count = mLayers.Length();
+  if (count != aOther.mLayers.Length()) {
+    NS_WARNING("truncating counts due to out-of-memory");
+    mAttachmentCount = std::max(mAttachmentCount, count);
+    mClipCount = std::max(mClipCount, count);
+    mOriginCount = std::max(mOriginCount, count);
+    mRepeatCount = std::max(mRepeatCount, count);
+    mPositionXCount = std::max(mPositionXCount, count);
+    mPositionYCount = std::max(mPositionYCount, count);
+    mImageCount = std::max(mImageCount, count);
+    mSizeCount = std::max(mSizeCount, count);
+    mMaskModeCount = std::max(mMaskModeCount, count);
+    mBlendModeCount = std::max(mBlendModeCount, count);
+    mCompositeCount = std::max(mCompositeCount, count);
+  }
+
+  return *this;
+}
+
 bool
 nsStyleImageLayers::IsInitialPositionForLayerType(Position aPosition, LayerType aType)
 {
   if (aPosition.mXPosition.mPercent == 0.0f &&
       aPosition.mXPosition.mLength == 0 &&
       aPosition.mXPosition.mHasPercent &&
       aPosition.mYPosition.mPercent == 0.0f &&
       aPosition.mYPosition.mLength == 0 &&
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -887,16 +887,17 @@ struct nsStyleImageLayers {
       mLayers[i].ResolveImage(aContext);
     }
   }
 
   nsChangeHint CalcDifference(const nsStyleImageLayers& aNewLayers,
                               nsStyleImageLayers::LayerType aType) const;
 
   bool HasLayerWithImage() const;
+  nsStyleImageLayers& operator=(const nsStyleImageLayers& aOther);
 
   static const nsCSSPropertyID kBackgroundLayerTable[];
   static const nsCSSPropertyID kMaskLayerTable[];
 
   #define NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(var_, layers_) \
     for (uint32_t var_ = (layers_).mImageCount; var_-- != 0; )
   #define NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(var_, layers_, start_, count_) \
     NS_ASSERTION((int32_t)(start_) >= 0 && (uint32_t)(start_) < (layers_).mImageCount, "Invalid layer start!"); \
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -410,114 +410,81 @@ private:
   LayerManager* mLayerManager;
   nsPoint mOffset;
 };
 
 /**
  * Returns true if any of the masks is an image mask (and not an SVG mask).
  */
 static bool
-HasNonSVGMask(const nsTArray<nsSVGMaskFrame *>& aMaskFrames)
+HasNonSVGMask(const nsTArray<nsSVGMaskFrame*>& aMaskFrames)
 {
   for (size_t i = 0; i < aMaskFrames.Length() ; i++) {
     nsSVGMaskFrame *maskFrame = aMaskFrames[i];
     if (!maskFrame) {
       return true;
     }
   }
 
   return false;
 }
 
 typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
 
+/**
+ * Paint css-positioned-mask onto a given target(aMaskDT).
+ */
 static DrawResult
-GenerateMaskSurface(const PaintFramesParams& aParams,
-                    float aOpacity, nsStyleContext* aSC,
-                    const nsTArray<nsSVGMaskFrame *>& aMaskFrames,
-                    const nsPoint& aOffsetToUserSpace,
-                    Matrix& aOutMaskTransform,
-                    RefPtr<SourceSurface>& aOutMaskSurface,
-                    bool& aOpacityApplied)
+PaintMaskSurface(const PaintFramesParams& aParams,
+                 DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC,
+                 const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
+                 const gfxMatrix& aMaskSurfaceMatrix,
+                 const nsPoint& aOffsetToUserSpace)
 {
+  MOZ_ASSERT(aMaskFrames.Length() > 0);
+  MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
+
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
-  MOZ_ASSERT(aMaskFrames.Length() > 0);
-
   gfxMatrix cssPxToDevPxMatrix =
     nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 
-  gfxContext& ctx = aParams.ctx;
-
-  // There is only one SVG mask.
-  if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
-    aOpacityApplied = true;
-    aOutMaskSurface =
-      aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame,
-                                            cssPxToDevPxMatrix, aOpacity,
-                                            &aOutMaskTransform,
-                                            svgReset->mMask.mLayers[0].mMaskMode);
-    return DrawResult::SUCCESS;
-  }
-
-  const IntRect& maskSurfaceRect = aParams.maskRect;
-  if (maskSurfaceRect.IsEmpty()) {
-    return DrawResult::SUCCESS;
-  }
-
-  RefPtr<DrawTarget> maskDT =
-      ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(),
-                                                   SurfaceFormat::A8);
-  if (!maskDT || !maskDT->IsValid()) {
-    return DrawResult::TEMPORARY_ERROR;
-  }
-
-  RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(maskDT);
-  MOZ_ASSERT(maskContext);
-
   nsPresContext* presContext = aParams.frame->PresContext();
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
                                    presContext->AppUnitsPerDevPixel());
 
-  // Set ctx's matrix on maskContext, offset by the maskSurfaceRect's position.
-  // This makes sure that we combine the masks in device space.
-  gfxMatrix maskSurfaceMatrix =
-    ctx.CurrentMatrix() * gfxMatrix::Translation(-maskSurfaceRect.TopLeft());
-  maskContext->SetMatrix(maskSurfaceMatrix);
-
-  // Set aAppliedOpacity as true only if all mask layers are svg mask.
-  // In this case, we will apply opacity into the final mask surface, so the
-  // caller does not need to apply it again.
-  aOpacityApplied = !HasNonSVGMask(aMaskFrames);
+  RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT);
+  MOZ_ASSERT(maskContext);
+  maskContext->SetMatrix(aMaskSurfaceMatrix);
 
   // Multiple SVG masks interleave with image mask. Paint each layer onto
-  // maskDT one at a time.
+  // aMaskDT one at a time.
   for (int i = aMaskFrames.Length() - 1; i >= 0 ; i--) {
     nsSVGMaskFrame *maskFrame = aMaskFrames[i];
 
     CompositionOp compositionOp = (i == int(aMaskFrames.Length() - 1))
       ? CompositionOp::OP_OVER
       : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite);
 
     // maskFrame != nullptr means we get a SVG mask.
     // maskFrame == nullptr means we get an image mask.
     if (maskFrame) {
       Matrix svgMaskMatrix;
       RefPtr<SourceSurface> svgMask =
         maskFrame->GetMaskForMaskedFrame(maskContext, aParams.frame,
                                          cssPxToDevPxMatrix,
-                                         aOpacityApplied ? aOpacity : 1.0,
+                                         aOpacity,
                                          &svgMaskMatrix,
                                          svgReset->mMask.mLayers[i].mMaskMode);
       if (svgMask) {
         gfxContextMatrixAutoSaveRestore matRestore(maskContext);
 
         maskContext->Multiply(ThebesMatrix(svgMaskMatrix));
         Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize()));
-        maskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask,
+        aMaskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask,
                             drawRect.TopLeft(),
                             DrawOptions(1.0, compositionOp));
       }
     } else {
       gfxContextMatrixAutoSaveRestore matRestore(maskContext);
 
       maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
       nsRenderingContext rc(maskContext);
@@ -534,33 +501,93 @@ GenerateMaskSurface(const PaintFramesPar
         nsCSSRendering::PaintBackgroundWithSC(params, aSC,
                                               *aParams.frame->StyleBorder());
       if (result != DrawResult::SUCCESS) {
         return result;
       }
     }
   }
 
+  return DrawResult::SUCCESS;
+}
+
+static DrawResult
+CreateAndPaintMaskSurface(const PaintFramesParams& aParams,
+                          float aOpacity, nsStyleContext* aSC,
+                          const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
+                          const nsPoint& aOffsetToUserSpace,
+                          Matrix& aOutMaskTransform,
+                          RefPtr<SourceSurface>& aOutMaskSurface,
+                          bool& aOpacityApplied)
+{
+  const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
+  MOZ_ASSERT(aMaskFrames.Length() > 0);
+
+  gfxContext& ctx = aParams.ctx;
+
+  // There is only one SVG mask.
+  if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
+    gfxMatrix cssPxToDevPxMatrix =
+    nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+
+    aOpacityApplied = true;
+    aOutMaskSurface =
+      aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame,
+                                            cssPxToDevPxMatrix, aOpacity,
+                                            &aOutMaskTransform,
+                                            svgReset->mMask.mLayers[0].mMaskMode);
+    return DrawResult::SUCCESS;
+  }
+
+  const IntRect& maskSurfaceRect = aParams.maskRect;
+  if (maskSurfaceRect.IsEmpty()) {
+    return DrawResult::SUCCESS;
+  }
+
+  RefPtr<DrawTarget> maskDT =
+      ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(),
+                                                   SurfaceFormat::A8);
+  if (!maskDT || !maskDT->IsValid()) {
+    return DrawResult::TEMPORARY_ERROR;
+  }
+
+  // Set aAppliedOpacity as true only if all mask layers are svg mask.
+  // In this case, we will apply opacity into the final mask surface, so the
+  // caller does not need to apply it again.
+  aOpacityApplied = !HasNonSVGMask(aMaskFrames);
+
+  // Set context's matrix on maskContext, offset by the maskSurfaceRect's
+  // position. This makes sure that we combine the masks in device space.
+  gfxMatrix maskSurfaceMatrix =
+    ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft());
+
+  DrawResult result = PaintMaskSurface(aParams, maskDT,
+                                       aOpacityApplied ? aOpacity : 1.0,
+                                       aSC, aMaskFrames, maskSurfaceMatrix,
+                                       aOffsetToUserSpace);
+  if (result != DrawResult::SUCCESS) {
+    return result;
+  }
+
   aOutMaskTransform = ToMatrix(maskSurfaceMatrix);
   if (!aOutMaskTransform.Invert()) {
     return DrawResult::SUCCESS;
   }
 
   aOutMaskSurface = maskDT->Snapshot();
   return DrawResult::SUCCESS;
 }
 
 static float
-ComputeOpacity(const PaintFramesParams& aParams)
+ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity)
 {
-  nsIFrame* frame = aParams.frame;
-  float opacity = frame->StyleEffects()->mOpacity;
+  float opacity = aFrame->StyleEffects()->mOpacity;
 
   if (opacity != 1.0f &&
-      (nsSVGUtils::CanOptimizeOpacity(frame) || !aParams.handleOpacity)) {
+      (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
     return 1.0f;
   }
 
   return opacity;
 }
 
 static bool
 ValidateSVGFrame(nsIFrame* aFrame)
@@ -653,16 +680,146 @@ SetupContextMatrix(nsIFrame* aFrame, con
     nsRect clipRect =
       aParams.frame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
     context.Clip(NSRectToSnappedRect(clipRect,
                                   aFrame->PresContext()->AppUnitsPerDevPixel(),
                                   *context.GetDrawTarget()));
   }
 }
 
+void
+nsSVGIntegrationUtils::DetermineMaskUsage(nsIFrame* aFrame,
+                                          bool aHandleOpacity,
+                                          MaskUsage& aUsage)
+{
+  aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
+
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+  nsSVGEffects::EffectProperties effectProperties =
+    nsSVGEffects::GetEffectProperties(firstFrame);
+  const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
+
+  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+  // For a HTML doc:
+  //   According to css-masking spec, always create a mask surface when we
+  //   have any item in maskFrame even if all of those items are
+  //   non-resolvable <mask-sources> or <images>, we still need to create a
+  //   transparent black mask layer under this condition.
+  // For a SVG doc:
+  //   SVG 1.1 say that  if we fail to resolve a mask, we should draw the
+  //   object unmasked.
+  aUsage.shouldGenerateMaskLayer =
+    (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)
+      ? maskFrames.Length() == 1 && maskFrames[0]
+      : maskFrames.Length() > 0;
+#else
+  // Since we do not support image mask so far, we should treat any
+  // unresolvable mask as no mask. Otherwise, any object with a valid image
+  // mask, e.g. url("xxx.png"), will become invisible just because we can not
+  // handle image mask correctly. (See bug 1294171)
+  aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0];
+#endif
+
+  bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
+  nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
+  MOZ_ASSERT_IF(clipPathFrame,
+                svgReset->mClipPath.GetType() == StyleShapeSourceType::URL);
+
+  switch (svgReset->mClipPath.GetType()) {
+    case StyleShapeSourceType::URL:
+      if (clipPathFrame) {
+        if (clipPathFrame->IsTrivial()) {
+          aUsage.shouldApplyClipPath = true;
+        } else {
+          aUsage.shouldGenerateClipMaskLayer = true;
+        }
+      }
+      break;
+    case StyleShapeSourceType::Shape:
+    case StyleShapeSourceType::Box:
+      aUsage.shouldApplyBasicShape = true;
+      break;
+    case StyleShapeSourceType::None:
+      MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
+                 !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape);
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
+      break;
+  }
+}
+
+bool
+nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
+{
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+  nsSVGEffects::EffectProperties effectProperties =
+    nsSVGEffects::GetEffectProperties(firstFrame);
+  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+  const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
+
+  for (uint32_t i = 0; i < maskFrames.Length(); i++) {
+    // Refers to a valid SVG mask.
+    if (maskFrames[i]) {
+      continue;
+    }
+
+    // Refers to an external resource, which is not ready yet.
+    if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) {
+      return false;
+    }
+  }
+
+  // Either all mask resources are ready, or no mask resource is needed.
+  return true;
+}
+
+DrawResult
+nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
+{
+  MaskUsage maskUsage;
+  DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
+  MOZ_ASSERT(maskUsage.shouldGenerateMaskLayer);
+
+  nsIFrame* frame = aParams.frame;
+  if (!ValidateSVGFrame(frame)) {
+    return DrawResult::SUCCESS;
+  }
+
+  if (maskUsage.opacity == 0.0f) {
+    return DrawResult::SUCCESS;
+  }
+
+  gfxContext& ctx = aParams.ctx;
+
+  gfxContextMatrixAutoSaveRestore matSR(&ctx);
+
+  nsIFrame* firstFrame =
+    nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+  nsSVGEffects::EffectProperties effectProperties =
+    nsSVGEffects::GetEffectProperties(firstFrame);
+  nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+  bool opacityApplied = !HasNonSVGMask(maskFrames);
+
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
+  SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+                     offsetToUserSpace, false);
+
+  return PaintMaskSurface(aParams, ctx.GetDrawTarget(),
+                            opacityApplied ? maskUsage.opacity : 1.0,
+                            firstFrame->StyleContext(), maskFrames,
+                            ctx.CurrentMatrix(), offsetToUserSpace);
+}
+
 DrawResult
 nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
 {
   MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame),
              "Should not use this method when no mask or clipPath effect"
              "on this frame");
 
   /* SVG defines the following rendering model:
@@ -679,119 +836,75 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
    * + Merge opacity and masking if both used together.
    */
   nsIFrame* frame = aParams.frame;
   DrawResult result = DrawResult::SUCCESS;
   if (!ValidateSVGFrame(frame)) {
     return result;
   }
 
-  float opacity = ComputeOpacity(aParams);
-  if (opacity == 0.0f) {
+  MaskUsage maskUsage;
+  DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
+
+  if (maskUsage.opacity == 0.0f) {
     return DrawResult::SUCCESS;
   }
 
   gfxContext& context = aParams.ctx;
   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
 
   /* Properties are added lazily and may have been removed by a restyle,
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
-  gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
-  const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
-  nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
-
-#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
-  // For a HTML doc:
-  //   According to css-masking spec, always create a mask surface when we
-  //   have any item in maskFrame even if all of those items are
-  //   non-resolvable <mask-sources> or <images>, we still need to create a
-  //   transparent black mask layer under this condition.
-  // For a SVG doc:
-  //   SVG 1.1 say that  if we fail to resolve a mask, we should draw the
-  //   object unmasked.
-  bool hasSVGLayout = (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
-  bool shouldGenerateMaskLayer = hasSVGLayout
-                                 ? maskFrames.Length() == 1 && maskFrames[0]
-                                 : maskFrames.Length() > 0;
-#else
-  // Since we do not support image mask so far, we should treat any
-  // unresolvable mask as no mask. Otherwise, any object with a valid image
-  // mask, e.g. url("xxx.png"), will become invisible just because we can not
-  // handle image mask correctly. (See bug 1294171)
-  bool shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0];
-#endif
-
   bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
   nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
-  MOZ_ASSERT_IF(clipPathFrame,
-                svgReset->mClipPath.GetType() == StyleShapeSourceType::URL);
 
-  bool shouldGenerateClipMaskLayer = false;
-  bool shouldApplyClipPath = false;
-  bool shouldApplyBasicShape = false;
-  switch (svgReset->mClipPath.GetType()) {
-    case StyleShapeSourceType::URL:
-      if (clipPathFrame) {
-        if (clipPathFrame->IsTrivial()) {
-          shouldApplyClipPath = true;
-        } else {
-          shouldGenerateClipMaskLayer = true;
-        }
-      }
-      break;
-    case StyleShapeSourceType::Shape:
-    case StyleShapeSourceType::Box:
-      shouldApplyBasicShape = true;
-      break;
-    case StyleShapeSourceType::None:
-      break;
-    default:
-      MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
-      break;
-  }
+  gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
+  nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
   nsPoint offsetToBoundingBox;
   nsPoint offsetToUserSpace;
 
-  bool shouldGenerateMask = (opacity != 1.0f || shouldGenerateClipMaskLayer ||
-                             shouldGenerateMaskLayer);
+  bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
+                             maskUsage.shouldGenerateClipMaskLayer ||
+                             maskUsage.shouldGenerateMaskLayer);
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (shouldGenerateMask) {
     gfxContextMatrixAutoSaveRestore matSR;
 
     Matrix maskTransform;
     RefPtr<SourceSurface> maskSurface;
     bool opacityApplied = false;
 
-    if (shouldGenerateMaskLayer) {
+    if (maskUsage.shouldGenerateMaskLayer) {
       matSR.SetContext(&context);
 
       // For css-mask, we want to generate a mask for each continuation frame,
       // so we setup context matrix by the position of the current frame,
       // instead of the first continuation frame.
       SetupContextMatrix(frame, aParams, offsetToBoundingBox,
                          offsetToUserSpace, false);
-      result = GenerateMaskSurface(aParams, opacity,
-                                  firstFrame->StyleContext(),
-                                  maskFrames, offsetToUserSpace,
-                                  maskTransform, maskSurface, opacityApplied);
+      result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
+                                         firstFrame->StyleContext(),
+                                         maskFrames, offsetToUserSpace,
+                                         maskTransform, maskSurface,
+                                         opacityApplied);
       if (!maskSurface) {
         // Entire surface is clipped out.
         return result;
       }
     }
 
-    if (shouldGenerateClipMaskLayer) {
+    if (maskUsage.shouldGenerateClipMaskLayer) {
       matSR.Restore();
       matSR.SetContext(&context);
 
       SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
                          offsetToUserSpace, false);
       Matrix clippedMaskTransform;
       RefPtr<SourceSurface> clipMaskSurface =
         clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
@@ -804,62 +917,66 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
       } else {
         // Either entire surface is clipped out, or gfx buffer allocation
         // failure in nsSVGClipPathFrame::GetClipMask.
         return result;
       }
     }
 
     // opacity != 1.0f.
-    if (!shouldGenerateClipMaskLayer && !shouldGenerateMaskLayer) {
-      MOZ_ASSERT(opacity != 1.0f);
+    if (!maskUsage.shouldGenerateClipMaskLayer &&
+        !maskUsage.shouldGenerateMaskLayer) {
+      MOZ_ASSERT(maskUsage.opacity != 1.0f);
 
       matSR.SetContext(&context);
       SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
                          offsetToUserSpace, false);
     }
 
     if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) {
       context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
-                                         opacityApplied ?  1.0 : opacity,
+                                         opacityApplied
+                                           ? 1.0
+                                           : maskUsage.opacity,
                                          maskSurface, maskTransform);
     } else {
       context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
-                                    opacityApplied ?  1.0 : opacity,
+                                    opacityApplied ? 1.0 : maskUsage.opacity,
                                     maskSurface, maskTransform);
     }
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
-  if (shouldApplyClipPath || shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     gfxContextMatrixAutoSaveRestore matSR(&context);
 
     SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
                        offsetToUserSpace, false);
 
-    MOZ_ASSERT(!shouldApplyClipPath || !shouldApplyBasicShape);
-    if (shouldApplyClipPath) {
+    MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
+               !maskUsage.shouldApplyBasicShape);
+    if (maskUsage.shouldApplyClipPath) {
       clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
     } else {
       nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
     }
   }
 
   /* Paint the child */
   context.SetMatrix(matrixAutoSaveRestore.Matrix());
   BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager();
   RefPtr<gfxContext> oldCtx = basic->GetTarget();
   basic->SetTarget(&context);
   aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
                                        aParams.builder);
   basic->SetTarget(oldCtx);
 
-  if (shouldApplyClipPath || shouldApplyBasicShape) {
+  if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     context.PopClip();
   }
 
   if (shouldGenerateMask) {
     context.PopGroupAndBlend();
   }
 
   return result;
@@ -873,17 +990,17 @@ nsSVGIntegrationUtils::PaintFilter(const
   MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
              "Should not use this method when no filter effect on this frame");
 
   nsIFrame* frame = aParams.frame;
   if (!ValidateSVGFrame(frame)) {
     return DrawResult::SUCCESS;
   }
 
-  float opacity = ComputeOpacity(aParams);
+  float opacity = ComputeOpacity(frame, aParams.handleOpacity);
   if (opacity == 0.0f) {
     return DrawResult::SUCCESS;
   }
 
   /* Properties are added lazily and may have been removed by a restyle,
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -156,16 +156,45 @@ public:
 
   /**
    * Paint non-SVG frame with mask, clipPath and opacity effect.
    */
   static DrawResult
   PaintMaskAndClipPath(const PaintFramesParams& aParams);
 
   /**
+   * Paint mask of non-SVG frame onto a given context, aParams.ctx.
+   * aParams.ctx must contain an A8 surface.
+   */
+  static DrawResult
+  PaintMask(const PaintFramesParams& aParams);
+
+  struct MaskUsage {
+    bool shouldGenerateMaskLayer;
+    bool shouldGenerateClipMaskLayer;
+    bool shouldApplyClipPath;
+    bool shouldApplyBasicShape;
+    float opacity;
+
+    MaskUsage()
+      : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false),
+        shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0)
+    { }
+  };
+
+  static void
+  DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, MaskUsage& aUsage);
+
+  /**
+   * Return true if all the mask resource of aFrame are ready.
+   */
+  static bool
+  IsMaskResourceReady(nsIFrame* aFrame);
+
+  /**
    * Paint non-SVG frame with filter and opacity effect.
    */
   static DrawResult
   PaintFilter(const PaintFramesParams& aParams);
 
   /**
    * SVG frames expect to paint in SVG user units, which are equal to CSS px
    * units. This method provides a transform matrix to multiply onto a
--- a/mobile/android/installer/allowed-dupes.mn
+++ b/mobile/android/installer/allowed-dupes.mn
@@ -112,8 +112,10 @@ modules/devtools/shared/Console.jsm
 modules/devtools/shared/Loader.jsm
 modules/devtools/shared/apps/Simulator.jsm
 res/table-remove-column-active.gif
 res/table-remove-column-hover.gif
 res/table-remove-column.gif
 res/table-remove-row-active.gif
 res/table-remove-row-hover.gif
 res/table-remove-row.gif
+modules/commonjs/index.js
+chrome/toolkit/content/global/XPCNativeWrapper.js
--- a/netwerk/protocol/http/AlternateServices.cpp
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -537,16 +537,17 @@ public:
 
   nsresult Start()
   {
     LOG(("WellKnownChecker::Start %p\n", this));
     nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(nsContentUtils::GetSystemPrincipal(),
                                                   nullptr, nullptr,
                                                   nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                                                   nsIContentPolicy::TYPE_OTHER);
+    loadInfo->SetOriginAttributes(mCI->GetOriginAttributes());
 
     RefPtr<nsHttpChannel> chan = new nsHttpChannel();
     nsresult rv;
 
     mTransactionAlternate = new TransactionObserver(chan, this);
     RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
     rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
     if (NS_FAILED(rv)) {
--- a/other-licenses/ia2/ia2_api_all.idl
+++ b/other-licenses/ia2/ia2_api_all.idl
@@ -5441,16 +5441,17 @@ cpp_quote("")
 ]
 
 library IAccessible2Lib
 {
     importlib ("stdole2.tlb");
     importlib ("oleacc.dll");
     interface IAccessible2;
     interface IAccessible2_2;
+    interface IAccessible2_3;
     interface IAccessibleAction;
     interface IAccessibleApplication;
     interface IAccessibleComponent;
     interface IAccessibleDocument;
     interface IAccessibleEditableText;
     interface IAccessibleHyperlink;
     interface IAccessibleHypertext;
     interface IAccessibleHypertext2;
--- a/python/mach/mach/main.py
+++ b/python/mach/mach/main.py
@@ -325,26 +325,35 @@ To see more help for a specific command,
         orig_stdin = sys.stdin
         orig_stdout = sys.stdout
         orig_stderr = sys.stderr
 
         sys.stdin = stdin
         sys.stdout = stdout
         sys.stderr = stderr
 
+        orig_env = dict(os.environ)
+
         try:
             if stdin.encoding is None:
                 sys.stdin = codecs.getreader('utf-8')(stdin)
 
             if stdout.encoding is None:
                 sys.stdout = codecs.getwriter('utf-8')(stdout)
 
             if stderr.encoding is None:
                 sys.stderr = codecs.getwriter('utf-8')(stderr)
 
+            # Allow invoked processes (which may not have a handle on the
+            # original stdout file descriptor) to know if the original stdout
+            # is a TTY. This provides a mechanism to allow said processes to
+            # enable emitting code codes, for example.
+            if os.isatty(orig_stdout.fileno()):
+                os.environ[b'MACH_STDOUT_ISATTY'] = b'1'
+
             return self._run(argv)
         except KeyboardInterrupt:
             print('mach interrupted by signal or user action. Stopping.')
             return 1
 
         except Exception as e:
             # _run swallows exceptions in invoked handlers and converts them to
             # a proper exit code. So, the only scenario where we should get an
@@ -357,16 +366,19 @@ To see more help for a specific command,
             exc_type, exc_value, exc_tb = sys.exc_info()
             stack = traceback.extract_tb(exc_tb)
 
             self._print_exception(sys.stdout, exc_type, exc_value, stack)
 
             return 1
 
         finally:
+            os.environ.clear()
+            os.environ.update(orig_env)
+
             sys.stdin = orig_stdin
             sys.stdout = orig_stdout
             sys.stderr = orig_stderr
 
     def _run(self, argv):
         # Load settings as early as possible so things in dispatcher.py
         # can use them.
         for provider in Registrar.settings_providers:
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
 import os
 import signal
+import sys
 import traceback
 from collections import defaultdict
 from Queue import Empty
 from multiprocessing import (
     Manager,
     Pool,
     cpu_count,
 )
@@ -57,16 +58,18 @@ def _run_linters(queue, paths, **lintarg
 def _run_worker(*args, **lintargs):
     try:
         return _run_linters(*args, **lintargs)
     except Exception:
         # multiprocessing seems to munge worker exceptions, print
         # it here so it isn't lost.
         traceback.print_exc()
         raise
+    finally:
+        sys.stdout.flush()
 
 
 class LintRoller(object):
     """Registers and runs linters.
 
     :param root: Path to which relative paths will be joined. If
                  unspecified, root will either be determined from
                  version control or cwd.
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -553,17 +553,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
     SECItem ocspRequestItem = {
       siBuffer,
       ocspRequest,
       static_cast<unsigned int>(ocspRequestLength)
     };
     // Owned by arena
     SECItem* responseSECItem = nullptr;
     Result tempRV =
-      DoOCSPRequest(arena, url, &ocspRequestItem,
+      DoOCSPRequest(arena, url, mFirstPartyDomain, &ocspRequestItem,
                     OCSPFetchingTypeToTimeoutTime(mOCSPFetching),
                     mOCSPGetConfig == CertVerifier::ocspGetEnabled,
                     responseSECItem);
     MOZ_ASSERT((tempRV != Success) || responseSECItem);
     if (tempRV != Success) {
       rv = tempRV;
     } else if (response.Init(responseSECItem->data, responseSECItem->len)
                  != Success) {
--- a/security/certverifier/OCSPRequestor.cpp
+++ b/security/certverifier/OCSPRequestor.cpp
@@ -69,18 +69,18 @@ AppendEscapedBase64Item(const SECItem* e
   base64Request.ReplaceSubstring("/", "%2F");
   base64Request.ReplaceSubstring("=", "%3D");
   path.Append(base64Request);
   return NS_OK;
 }
 
 Result
 DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
-              const SECItem* encodedRequest, PRIntervalTime timeout,
-              bool useGET,
+              const char* firstPartyDomain, const SECItem* encodedRequest,
+              PRIntervalTime timeout, bool useGET,
       /*out*/ SECItem*& encodedResponse)
 {
   MOZ_ASSERT(arena.get());
   MOZ_ASSERT(url);
   MOZ_ASSERT(encodedRequest);
   MOZ_ASSERT(encodedRequest->data);
   if (!arena.get() || !url || !encodedRequest || !encodedRequest->data) {
     return Result::FATAL_ERROR_INVALID_ARGS;
@@ -168,17 +168,18 @@ DoOCSPRequest(const UniquePLArenaPool& a
     nsresult nsrv = AppendEscapedBase64Item(encodedRequest, path);
     if (NS_WARN_IF(NS_FAILED(nsrv))) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
   }
 
   nsNSSHttpRequestSession* requestSessionPtr;
   rv = nsNSSHttpInterface::createFcn(serverSession.get(), "http", path.get(),
-                                     method.get(), timeout, &requestSessionPtr);
+                                     method.get(), firstPartyDomain, timeout,
+                                     &requestSessionPtr);
   if (rv != Success) {
     return rv;
   }
 
   UniqueHTTPRequestSession requestSession(requestSessionPtr);
 
   if (!useGET) {
     rv = nsNSSHttpInterface::setPostDataFcn(
--- a/security/certverifier/OCSPRequestor.h
+++ b/security/certverifier/OCSPRequestor.h
@@ -9,15 +9,16 @@
 
 #include "CertVerifier.h"
 #include "secmodt.h"
 
 namespace mozilla { namespace psm {
 
 // The memory returned via |encodedResponse| is owned by the given arena.
 Result DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
+                     const char* firstPartyDomain,
                      const SECItem* encodedRequest, PRIntervalTime timeout,
                      bool useGET,
              /*out*/ SECItem*& encodedResponse);
 
 } } // namespace mozilla::psm
 
 #endif // OCSPRequestor_h
--- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
+++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
@@ -167,17 +167,16 @@ CertDumpKeyCompromise=Key Compromise
 CertDumpCACompromise=CA Compromise
 CertDumpAffiliationChanged=Affiliation Changed
 CertDumpSuperseded=Superseded
 CertDumpCessation=Cessation of Operation
 CertDumpHold=Certificate Hold
 CertDumpOCSPResponder=OCSP
 CertDumpCAIssuers=CA Issuers
 CertDumpCPSPointer=Certification Practice Statement pointer
-CertDumpPolicyOidEV=Extended Validation (EV) SSL Server Certificate
 CertDumpUserNotice=User Notice
 CertDumpLogotype=Logotype
 CertDumpECPublicKey=Elliptic Curve Public Key
 CertDumpECDSAWithSHA1=X9.62 ECDSA Signature with SHA1
 CertDumpECprime192v1=ANSI X9.62 elliptic curve prime192v1 (aka secp192r1, NIST P-192)
 CertDumpECprime192v2=ANSI X9.62 elliptic curve prime192v2
 CertDumpECprime192v3=ANSI X9.62 elliptic curve prime192v3
 CertDumpECprime239v1=ANSI X9.62 elliptic curve prime239v1
--- a/security/manager/pki/nsNSSDialogs.cpp
+++ b/security/manager/pki/nsNSSDialogs.cpp
@@ -265,48 +265,46 @@ nsNSSDialogs::ChooseCertificate(nsIInter
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP 
-nsNSSDialogs::SetPKCS12FilePassword(nsIInterfaceRequestor *ctx, 
-                                    nsAString &_password,
-                                    bool *_retval)
+NS_IMETHODIMP
+nsNSSDialogs::SetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
+                            /*out*/ nsAString& password,
+                            /*out*/ bool* confirmedPassword)
 {
-  nsresult rv;
-  *_retval = true;
+  // |ctx| is allowed to be null.
+  NS_ENSURE_ARG(confirmedPassword);
+
   // Get the parent window for the dialog
   nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
-  nsCOMPtr<nsIDialogParamBlock> block =
-           do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
-  if (!block) return NS_ERROR_FAILURE;
-  // open up the window
-  rv = nsNSSDialogHelper::openDialog(parent,
+  nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
+  nsresult rv =
+    nsNSSDialogHelper::openDialog(parent,
                                   "chrome://pippki/content/setp12password.xul",
-                                  block);
-  if (NS_FAILED(rv)) return rv;
-  // see if user canceled
-  int32_t status;
-  rv = block->GetInt(1, &status);
-  if (NS_FAILED(rv)) return rv;
-  *_retval = (status == 0) ? false : true;
-  if (*_retval) {
-    // retrieve the password
-    char16_t *pw;
-    rv = block->GetString(2, &pw);
-    if (NS_SUCCEEDED(rv)) {
-      _password = pw;
-      free(pw);
-    }
+                                  retVals);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
-  return rv;
+
+  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("confirmedPassword"),
+                                  confirmedPassword);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!*confirmedPassword) {
+    return NS_OK;
+  }
+
+  return retVals->GetPropertyAsAString(NS_LITERAL_STRING("password"), password);
 }
 
 NS_IMETHODIMP
 nsNSSDialogs::GetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
                                     nsAString& _password,
                                     bool* _retval)
 {
   *_retval = false;
rename from security/manager/pki/resources/content/password.js
rename to security/manager/pki/resources/content/changepassword.js
--- a/security/manager/pki/resources/content/password.js
+++ b/security/manager/pki/resources/content/changepassword.js
@@ -119,25 +119,16 @@ function process()
   if (params) {
     // Return value 0 means "canceled"
     params.SetInt(1, 0);
   }
 
   checkPasswords();
 }
 
-function onP12Load(disableOkButton)
-{
-  document.documentElement.getButton("accept").disabled = disableOkButton;
-  pw1 = document.getElementById("pw1");
-  params = window.arguments[0].QueryInterface(nsIDialogParamBlock);
-  // Select first password field
-  document.getElementById('pw1').focus();
-}
-
 function setPassword()
 {
   var pk11db = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
   var token = pk11db.findTokenByName(tokenName);
 
   var oldpwbox = document.getElementById("oldpw");
   var initpw = oldpwbox.getAttribute("inited");
   var bundle = document.getElementById("pippki_bundle");
@@ -201,26 +192,16 @@ function setPassword()
     // Return value 1 means "successfully executed ok"
     params.SetInt(1, 1);
   }
 
   // Terminate dialog
   return success;
 }
 
-function setP12Password()
-{
-  // grab what was entered
-  params.SetString(2, pw1.value);
-  // Return value
-  params.SetInt(1, 1);
-  // Terminate dialog
-  return true;
-}
-
 function setPasswordStrength()
 {
   // We weigh the quality of the password by checking the number of:
   //  - Characters
   //  - Numbers
   //  - Non-alphanumeric chars
   //  - Upper and lower case characters
 
--- a/security/manager/pki/resources/content/changepassword.xul
+++ b/security/manager/pki/resources/content/changepassword.xul
@@ -10,17 +10,18 @@
 <dialog id="set_password" title="&setPassword.title;"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
   buttons="accept,cancel"
   ondialogaccept="return setPassword();"
   onload="onLoad();">
 
 <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
 
-<script type="application/javascript" src="chrome://pippki/content/password.js"/>
+<script type="application/javascript"
+        src="chrome://pippki/content/changepassword.js"/>
 
 <hbox align="center">
   <label value="&setPassword.tokenName.label;: "/>
   <label id="tokenName" />
   <menulist id="tokenMenu" oncommand="onMenuChange()">
      <menupopup/>
   </menulist>
 </hbox>
new file mode 100644
--- /dev/null
+++ b/security/manager/pki/resources/content/setp12password.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * @file Implements the functionality of setp12password.xul: a dialog that lets
+ *       the user confirm the password to set on a PKCS #12 file.
+ * @argument {nsISupports} window.arguments[0]
+ *           Object to set the return values of calling the dialog on, queryable
+ *           to the underlying type of SetP12PasswordReturnValues.
+ */
+
+/**
+ * @typedef SetP12PasswordReturnValues
+ * @type nsIWritablePropertyBag2
+ * @property {Boolean} confirmedPassword
+ *           Set to true if the user entered two matching passwords and
+ *           confirmed the dialog.
+ * @property {String} password
+ *           The password the user entered. Undefined value if
+ *           |confirmedPassword| is not true.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+  // Ensure the first password textbox has focus.
+  document.getElementById("pw1").focus();
+}
+
+/**
+ * ondialogaccept() handler.
+ *
+ * @returns {Boolean} true to make the dialog close, false otherwise.
+ */
+function onDialogAccept() {
+  let password = document.getElementById("pw1").value;
+
+  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+  retVals.setPropertyAsBool("confirmedPassword", true);
+  retVals.setPropertyAsAString("password", password);
+  return true;
+}
+
+/**
+ * ondialogcancel() handler.
+ *
+ * @returns {Boolean} true to make the dialog close, false otherwise.
+ */
+function onDialogCancel() {
+  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+  retVals.setPropertyAsBool("confirmedPassword", false);
+  return true;
+}
+
+/**
+ * Calculates the strength of the given password, suitable for use in updating
+ * a progress bar that represents said strength.
+ *
+ * The strength of the password is calculated by checking the number of:
+ *   - Characters
+ *   - Numbers
+ *   - Non-alphanumeric chars
+ *   - Upper case characters
+ *
+ * @param {String} password
+ *        The password to calculate the strength of.
+ * @returns {Number}
+ *          The strength of the password in the range [0, 100].
+ */
+function getPasswordStrength(password) {
+  let lengthStrength = password.length;
+  if (lengthStrength > 5) {
+    lengthStrength = 5;
+  }
+
+  let nonNumericChars = password.replace(/[0-9]/g, "");
+  let numericStrength = password.length - nonNumericChars.length;
+  if (numericStrength > 3) {
+    numericStrength = 3;
+  }
+
+  let nonSymbolChars = password.replace(/\W/g, "");
+  let symbolStrength = password.length - nonSymbolChars.length;
+  if (symbolStrength > 3) {
+    symbolStrength = 3;
+  }
+
+  let nonUpperAlphaChars = password.replace(/[A-Z]/g, "");
+  let upperAlphaStrength = password.length - nonUpperAlphaChars.length;
+  if (upperAlphaStrength > 3) {
+    upperAlphaStrength = 3;
+  }
+
+  let strength = (lengthStrength * 10) - 20 + (numericStrength * 10) +
+                 (symbolStrength * 15) + (upperAlphaStrength * 10);
+  if (strength < 0) {
+    strength = 0;
+  }
+  if (strength > 100) {
+    strength = 100;
+  }
+
+  return strength;
+}
+
+/**
+ * oninput() handler for both password textboxes.
+ *
+ * @param {Boolean} recalculatePasswordStrength
+ *                  Whether to recalculate the strength of the first password.
+ */
+function onPasswordInput(recalculatePasswordStrength) {
+  let pw1 = document.getElementById("pw1").value;
+
+  if (recalculatePasswordStrength) {
+    document.getElementById("pwmeter").value = getPasswordStrength(pw1);
+  }
+
+  // Disable the accept button if the two passwords don't match, and enable it
+  // if the passwords do match.
+  let pw2 = document.getElementById("pw2").value;
+  document.documentElement.getButton("accept").disabled = (pw1 != pw2);
+}
--- a/security/manager/pki/resources/content/setp12password.xul
+++ b/security/manager/pki/resources/content/setp12password.xul
@@ -2,50 +2,50 @@
 <!-- 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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd">
 
-<dialog id="setp12password" title="&pkcs12.setpassword.title;"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
-  style="width: 48em;"
-  buttons="accept,cancel"
-  ondialogaccept="return setP12Password();"
-  onload="onP12Load(true);">
+<dialog id="setp12password"
+        title="&pkcs12.setpassword.title;"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        style="width: 48em;"
+        buttons="accept,cancel"
+        ondialogaccept="return onDialogAccept();"
+        ondialogcancel="return onDialogCancel();"
+        onload="onLoad();">
 
-  <script type="application/javascript" src="chrome://pippki/content/password.js"/>
+  <script type="application/javascript"
+          src="chrome://pippki/content/setp12password.js"/>
 
   <description>&pkcs12.setpassword.message;</description>
   <separator />
   <grid>
     <columns> <column/> <column/> </columns>
     <rows>
       <row>
-        <label value="&pkcs12.setpassword.label1;"/> 
-        <textbox id="pw1" type="password" 
-                 oninput="setPasswordStrength(); checkPasswords();"/> 
+        <label value="&pkcs12.setpassword.label1;"/>
+        <textbox id="pw1" type="password" oninput="onPasswordInput(true);"/>
       </row>
       <row>
-        <label value="&pkcs12.setpassword.label2;"/> 
-        <textbox id="pw2" type="password" 
-                 oninput="checkPasswords();"/>  
+        <label value="&pkcs12.setpassword.label2;"/>
+        <textbox id="pw2" type="password" oninput="onPasswordInput(false);"/>
       </row>
     </rows>
   </grid>
   <separator/>
   <description>&pkcs12.setpassword.reminder;</description>
   <separator/>
   <label value="&setPassword.meter.label;"/>
   <grid style="margin: 4px;">
     <rows> <row/> </rows>
     <columns>
       <column style="margin: 5px;">
-        <progressmeter flex="1" id="pwmeter" mode="determined" value="0%"
+        <progressmeter flex="1" id="pwmeter" mode="determined" value="0"
                        orient="horizontal"
-                       style="width: 20em; foreground-color: red"/> 
+                       style="width: 20em; foreground-color: red"/>
       </column>
     </columns>
   </grid>
-  
 </dialog>
--- a/security/manager/pki/resources/jar.mn
+++ b/security/manager/pki/resources/jar.mn
@@ -1,42 +1,43 @@
 # 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/.
 
 pippki.jar:
 % content pippki %content/pippki/
-    content/pippki/changepassword.xul        (content/changepassword.xul)
-    content/pippki/password.js               (content/password.js)
-    content/pippki/resetpassword.xul         (content/resetpassword.xul)
-    content/pippki/resetpassword.js          (content/resetpassword.js)
-    content/pippki/downloadcert.js           (content/downloadcert.js)
-    content/pippki/downloadcert.xul          (content/downloadcert.xul)
-    content/pippki/certManager.js            (content/certManager.js)
-    content/pippki/certManager.xul           (content/certManager.xul)
     content/pippki/CAOverlay.xul             (content/CAOverlay.xul)
-    content/pippki/WebSitesOverlay.xul       (content/WebSitesOverlay.xul)
-    content/pippki/OthersOverlay.xul         (content/OthersOverlay.xul)
     content/pippki/MineOverlay.xul           (content/MineOverlay.xul)
     content/pippki/OrphanOverlay.xul         (content/OrphanOverlay.xul)
-    content/pippki/viewCertDetails.xul       (content/viewCertDetails.xul)
-    content/pippki/editcacert.xul            (content/editcacert.xul)
-    content/pippki/editcacert.js             (content/editcacert.js)
-*   content/pippki/exceptionDialog.xul       (content/exceptionDialog.xul)
-    content/pippki/exceptionDialog.js        (content/exceptionDialog.js)
-    content/pippki/deletecert.xul            (content/deletecert.xul)
-    content/pippki/deletecert.js             (content/deletecert.js)
-    content/pippki/setp12password.xul        (content/setp12password.xul)
-    content/pippki/pippki.js                 (content/pippki.js)
-    content/pippki/clientauthask.xul	     (content/clientauthask.xul)
-    content/pippki/clientauthask.js          (content/clientauthask.js)
+    content/pippki/OthersOverlay.xul         (content/OthersOverlay.xul)
+    content/pippki/WebSitesOverlay.xul       (content/WebSitesOverlay.xul)
+    content/pippki/certDump.xul              (content/certDump.xul)
+    content/pippki/certManager.js            (content/certManager.js)
+    content/pippki/certManager.xul           (content/certManager.xul)
+    content/pippki/certViewer.js             (content/certViewer.js)
     content/pippki/certViewer.xul            (content/certViewer.xul)
-    content/pippki/certViewer.js             (content/certViewer.js)
-    content/pippki/certDump.xul              (content/certDump.xul)
-    content/pippki/device_manager.xul        (content/device_manager.xul)
-    content/pippki/device_manager.js         (content/device_manager.js)
-    content/pippki/load_device.xul           (content/load_device.xul)
+    content/pippki/changepassword.js         (content/changepassword.js)
+    content/pippki/changepassword.xul        (content/changepassword.xul)
+    content/pippki/choosetoken.js            (content/choosetoken.js)
     content/pippki/choosetoken.xul           (content/choosetoken.xul)
-    content/pippki/choosetoken.js            (content/choosetoken.js)
-    content/pippki/createCertInfo.xul        (content/createCertInfo.xul)
+    content/pippki/clientauthask.js          (content/clientauthask.js)
+    content/pippki/clientauthask.xul         (content/clientauthask.xul)
     content/pippki/createCertInfo.js         (content/createCertInfo.js)
+    content/pippki/createCertInfo.xul        (content/createCertInfo.xul)
+    content/pippki/deletecert.js             (content/deletecert.js)
+    content/pippki/deletecert.xul            (content/deletecert.xul)
+    content/pippki/device_manager.js         (content/device_manager.js)
+    content/pippki/device_manager.xul        (content/device_manager.xul)
+    content/pippki/downloadcert.js           (content/downloadcert.js)
+    content/pippki/downloadcert.xul          (content/downloadcert.xul)
+    content/pippki/editcacert.js             (content/editcacert.js)
+    content/pippki/editcacert.xul            (content/editcacert.xul)
+    content/pippki/exceptionDialog.js        (content/exceptionDialog.js)
+*   content/pippki/exceptionDialog.xul       (content/exceptionDialog.xul)
+    content/pippki/load_device.xul           (content/load_device.xul)
+    content/pippki/pippki.js                 (content/pippki.js)
+    content/pippki/protectedAuth.js          (content/protectedAuth.js)
     content/pippki/protectedAuth.xul         (content/protectedAuth.xul)
-    content/pippki/protectedAuth.js          (content/protectedAuth.js)
+    content/pippki/resetpassword.js          (content/resetpassword.js)
+    content/pippki/resetpassword.xul         (content/resetpassword.xul)
+    content/pippki/setp12password.js         (content/setp12password.js)
+    content/pippki/setp12password.xul        (content/setp12password.xul)
+    content/pippki/viewCertDetails.xul       (content/viewCertDetails.xul)
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -99,35 +99,40 @@
 #include "BRNameMatchingPolicy.h"
 #include "CertVerifier.h"
 #include "CryptoTask.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "PSMRunnable.h"
 #include "RootCertificateTelemetryUtils.h"
 #include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
 #include "SharedSSLState.h"
+#include "TransportSecurityInfo.h" // For RememberCertErrorsTable
 #include "cert.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
 #include "mozilla/net/DNS.h"
-#include "mozilla/Unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsIBadCertListener2.h"
 #include "nsICertOverrideService.h"
 #include "nsISiteSecurityService.h"
 #include "nsISocketProvider.h"
 #include "nsIThreadPool.h"
+#include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "nsNSSIOLayer.h"
 #include "nsNSSShutDown.h"
+#include "nsSSLStatus.h"
 #include "nsServiceManagerUtils.h"
 #include "nsURLHelper.h"
 #include "nsXPCOMCIDInternal.h"
 #include "pkix/pkix.h"
 #include "pkix/pkixnss.h"
 #include "secerr.h"
 #include "secoidt.h"
 #include "secport.h"
@@ -1364,63 +1369,47 @@ AuthCertificate(CertVerifier& certVerifi
                           pinningTelemetryInfo.rootBucket);
   }
 
   if (pinningTelemetryInfo.accumulateResult) {
     Telemetry::Accumulate(pinningTelemetryInfo.certPinningResultHistogram,
                           pinningTelemetryInfo.certPinningResultBucket);
   }
 
-  RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
-  RefPtr<nsNSSCertificate> nsc;
-
-  if (!status || !status->HasServerCert()) {
-    if (rv == Success) {
-      nsc = nsNSSCertificate::Create(cert.get(), &evOidPolicy);
-    } else {
-      nsc = nsNSSCertificate::Create(cert.get());
-    }
-  }
-
   if (rv == Success) {
+    // Certificate verification succeeded. Delete any potential record of
+    // certificate error bits.
+    RememberCertErrorsTable::GetInstance().RememberCertHasError(infoObject,
+                                                                nullptr,
+                                                                SECSuccess);
     GatherSuccessfulValidationTelemetry(certList);
     GatherCertificateTransparencyTelemetry(certList,
                                            certificateTransparencyInfo);
 
     // The connection may get terminated, for example, if the server requires
     // a client cert. Let's provide a minimal SSLStatus
     // to the caller that contains at least the cert and its status.
+    RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
     if (!status) {
       status = new nsSSLStatus();
       infoObject->SetSSLStatus(status);
     }
 
-    if (rv == Success) {
-      // Certificate verification succeeded delete any potential record
-      // of certificate error bits.
-      RememberCertErrorsTable::GetInstance().RememberCertHasError(infoObject,
-                                                                  nullptr,
-                                                                  SECSuccess);
-    } else {
-      // Certificate verification failed, update the status' bits.
-      RememberCertErrorsTable::GetInstance().LookupCertErrorBits(
-        infoObject, status);
-    }
-
-    if (status && !status->HasServerCert()) {
-      nsNSSCertificate::EVStatus evStatus;
-      if (evOidPolicy == SEC_OID_UNKNOWN || rv != Success) {
-        evStatus = nsNSSCertificate::ev_status_invalid;
+    if (!status->HasServerCert()) {
+      EVStatus evStatus;
+      if (evOidPolicy == SEC_OID_UNKNOWN) {
+        evStatus = EVStatus::NotEV;
       } else {
-        evStatus = nsNSSCertificate::ev_status_valid;
+        evStatus = EVStatus::EV;
       }
 
+      RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert.get());
       status->SetServerCert(nsc, evStatus);
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-             ("AuthCertificate setting NEW cert %p\n", nsc.get()));
+              ("AuthCertificate setting NEW cert %p", nsc.get()));
     }
 
     status->SetCertificateTransparencyInfo(certificateTransparencyInfo);
   }
 
   if (rv != Success) {
     // Certificate validation failed; store the peer certificate chain on
     // infoObject so it can be used for error reporting.
--- a/security/manager/ssl/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/TransportSecurityInfo.cpp
@@ -1051,17 +1051,17 @@ TransportSecurityInfo::SetStatusErrorBit
                                           uint32_t collected_errors)
 {
   MutexAutoLock lock(mMutex);
 
   if (!mSSLStatus) {
     mSSLStatus = new nsSSLStatus();
   }
 
-  mSSLStatus->SetServerCert(cert, nsNSSCertificate::ev_status_invalid);
+  mSSLStatus->SetServerCert(cert, EVStatus::NotEV);
 
   mSSLStatus->mHaveCertErrorBits = true;
   mSSLStatus->mIsDomainMismatch = 
     collected_errors & nsICertOverrideService::ERROR_MISMATCH;
   mSSLStatus->mIsNotValidAtThisTime = 
     collected_errors & nsICertOverrideService::ERROR_TIME;
   mSSLStatus->mIsUntrusted = 
     collected_errors & nsICertOverrideService::ERROR_UNTRUSTED;
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1,39 +1,42 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNSSCallbacks.h"
 
+#include "PSMRunnable.h"
+#include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
+#include "SharedSSLState.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h"
 #include "nsICertOverrideService.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIPrompt.h"
 #include "nsISupportsPriority.h"
 #include "nsITokenDialogs.h"
 #include "nsIUploadChannel.h"
 #include "nsIWebProgressListener.h"
-#include "nsNetUtil.h"
+#include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "nsNSSIOLayer.h"
+#include "nsNetUtil.h"
 #include "nsProtectedAuthThread.h"
 #include "nsProxyRelease.h"
 #include "pkix/pkixtypes.h"
-#include "PSMRunnable.h"
-#include "ScopedNSSTypes.h"
-#include "SharedSSLState.h"
 #include "ssl.h"
 #include "sslproto.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 extern LazyLogModule gPIPNSSLog;
 
@@ -106,16 +109,28 @@ nsHTTPDownloadEvent::Run()
   // high priority to accommodate real time OCSP transactions.
   nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(chan);
   if (priorityChannel)
     priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
 
   chan->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS |
                      nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
 
+  if (!mRequestSession->mFirstPartyDomain.IsEmpty()) {
+    NeckoOriginAttributes attrs;
+    attrs.mFirstPartyDomain =
+      NS_ConvertUTF8toUTF16(mRequestSession->mFirstPartyDomain);
+
+    nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
+    if (loadInfo) {
+      rv = loadInfo->SetOriginAttributes(attrs);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
   // Create a loadgroup for this new channel.  This way if the channel
   // is redirected, we'll have a way to cancel the resulting channel.
   nsCOMPtr<nsILoadGroup> lg = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
   chan->SetLoadGroup(lg);
 
   if (mRequestSession->mHasPostData)
   {
     nsCOMPtr<nsIInputStream> uploadStream;
@@ -210,16 +225,17 @@ nsNSSHttpServerSession::createSessionFcn
   return Success;
 }
 
 mozilla::pkix::Result
 nsNSSHttpRequestSession::createFcn(const nsNSSHttpServerSession* session,
                                    const char* http_protocol_variant,
                                    const char* path_and_query_string,
                                    const char* http_request_method,
+                                   const char* first_party_domain,
                                    const PRIntervalTime timeout,
                            /*out*/ nsNSSHttpRequestSession** pRequest)
 {
   if (!session || !http_protocol_variant || !path_and_query_string ||
       !http_request_method || !pRequest) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
@@ -239,16 +255,18 @@ nsNSSHttpRequestSession::createFcn(const
 
   rs->mURL.Assign(http_protocol_variant);
   rs->mURL.AppendLiteral("://");
   rs->mURL.Append(session->mHost);
   rs->mURL.Append(':');
   rs->mURL.AppendInt(session->mPort);
   rs->mURL.Append(path_and_query_string);
 
+  rs->mFirstPartyDomain.Assign(first_party_domain);
+
   rs->mRequestMethod = http_request_method;
 
   *pRequest = rs;
   return Success;
 }
 
 mozilla::pkix::Result
 nsNSSHttpRequestSession::setPostDataFcn(const char* http_data,
@@ -1078,16 +1096,99 @@ AccumulateCipherSuite(Telemetry::ID prob
     default:
       value = 0;
       break;
   }
   MOZ_ASSERT(value != 0);
   Telemetry::Accumulate(probe, value);
 }
 
+// In the case of session resumption, the AuthCertificate hook has been bypassed
+// (because we've previously successfully connected to our peer). That being the
+// case, we unfortunately don't know if the peer's server certificate verified
+// as extended validation or not. To address this, we attempt to build a
+// verified EV certificate chain here using as much of the original context as
+// possible (e.g. stapled OCSP responses, SCTs, the hostname, the first party
+// domain, etc.). Note that because we are on the socket thread, this must not
+// cause any network requests, hence the use of FLAG_LOCAL_ONLY.
+static void
+DetermineEVStatusAndSetNewCert(RefPtr<nsSSLStatus> sslStatus, PRFileDesc* fd,
+                               nsNSSSocketInfo* infoObject)
+{
+  MOZ_ASSERT(sslStatus);
+  MOZ_ASSERT(fd);
+  MOZ_ASSERT(infoObject);
+
+  if (!sslStatus || !fd || !infoObject) {
+    return;
+  }
+
+  UniqueCERTCertificate cert(SSL_PeerCertificate(fd));
+  MOZ_ASSERT(cert, "SSL_PeerCertificate failed in TLS handshake callback?");
+  if (!cert) {
+    return;
+  }
+
+  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+  MOZ_ASSERT(certVerifier,
+             "Certificate verifier uninitialized in TLS handshake callback?");
+  if (!certVerifier) {
+    return;
+  }
+
+  // We don't own these pointers.
+  const SECItemArray* stapledOCSPResponses = SSL_PeerStapledOCSPResponses(fd);
+  const SECItem* stapledOCSPResponse = nullptr;
+  // we currently only support single stapled responses
+  if (stapledOCSPResponses && stapledOCSPResponses->len == 1) {
+    stapledOCSPResponse = &stapledOCSPResponses->items[0];
+  }
+  const SECItem* sctsFromTLSExtension = SSL_PeerSignedCertTimestamps(fd);
+  if (sctsFromTLSExtension && sctsFromTLSExtension->len == 0) {
+    // SSL_PeerSignedCertTimestamps returns null on error and empty item
+    // when no extension was returned by the server. We always use null when
+    // no extension was received (for whatever reason), ignoring errors.
+    sctsFromTLSExtension = nullptr;
+  }
+
+  int flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
+              mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
+  if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
+      !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
+    flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
+  }
+
+  SECOidTag evOidPolicy;
+  UniqueCERTCertList unusedBuiltChain;
+  const bool saveIntermediates = false;
+  mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
+    cert,
+    stapledOCSPResponse,
+    sctsFromTLSExtension,
+    mozilla::pkix::Now(),
+    infoObject,
+    infoObject->GetHostNameRaw(),
+    unusedBuiltChain,
+    saveIntermediates,
+    flags,
+    infoObject->GetFirstPartyDomainRaw(),
+    &evOidPolicy);
+
+  RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(cert.get()));
+  if (rv == Success && evOidPolicy != SEC_OID_UNKNOWN) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("HandshakeCallback using NEW cert %p (is EV)", nssc.get()));
+    sslStatus->SetServerCert(nssc, EVStatus::EV);
+  } else {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("HandshakeCallback using NEW cert %p (is not EV)", nssc.get()));
+    sslStatus->SetServerCert(nssc, EVStatus::NotEV);
+  }
+}
+
 void HandshakeCallback(PRFileDesc* fd, void* client_data) {
   nsNSSShutDownPreventionLock locker;
   SECStatus rv;
 
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
 
   // Do the bookkeeping that needs to be done after the
   // server's ServerHello...ServerHelloDone have been processed, but that doesn't
@@ -1231,21 +1332,17 @@ void HandshakeCallback(PRFileDesc* fd, v
       }
     }
   }
 
   if (status->HasServerCert()) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("HandshakeCallback KEEPING existing cert\n"));
   } else {
-    UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));
-    RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(serverCert.get()));
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-           ("HandshakeCallback using NEW cert %p\n", nssc.get()));
-    status->SetServerCert(nssc, nsNSSCertificate::ev_status_unknown);
+    DetermineEVStatusAndSetNewCert(status, fd, infoObject);
   }
 
   bool domainMismatch;
   bool untrusted;
   bool notValidAtThisTime;
   // These all return NS_OK, so don't even bother checking the return values.
   Unused << status->GetIsDomainMismatch(&domainMismatch);
   Unused << status->GetIsUntrusted(&untrusted);
--- a/security/manager/ssl/nsNSSCallbacks.h
+++ b/security/manager/ssl/nsNSSCallbacks.h
@@ -94,16 +94,17 @@ protected:
 
 public:
   typedef mozilla::pkix::Result Result;
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
+                          const char* firstPartyDomain,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest);
 
   Result setPostDataFcn(const char* httpData,
                         const uint32_t httpDataLen,
                         const char* httpContentType);
 
   Result trySendAndReceiveFcn(PRPollDesc** pPollDesc,
@@ -118,16 +119,18 @@ public:
 
   nsCString mURL;
   nsCString mRequestMethod;
 
   bool mHasPostData;
   nsCString mPostData;
   nsCString mPostContentType;
 
+  nsCString mFirstPartyDomain;
+
   PRIntervalTime mTimeoutInterval;
 
   RefPtr<nsHTTPListener> mListener;
 
 protected:
   nsNSSHttpRequestSession();
   ~nsNSSHttpRequestSession();
 
@@ -151,23 +154,24 @@ public:
   {
     return nsNSSHttpServerSession::createSessionFcn(host, portnum, pSession);
   }
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
+                          const char* firstPartyDomain,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest)
   {
     return nsNSSHttpRequestSession::createFcn(session, httpProtocolVariant,
                                               pathAndQueryString,
-                                              httpRequestMethod, timeout,
-                                              pRequest);
+                                              httpRequestMethod, firstPartyDomain,
+                                              timeout, pRequest);
   }
 
   static Result setPostDataFcn(nsNSSHttpRequestSession* request,
                                const char* httpData,
                                const uint32_t httpDataLen,
                                const char* httpContentType)
   {
     return request->setPostDataFcn(httpData, httpDataLen, httpContentType);
--- a/security/manager/ssl/nsNSSCertHelper.cpp
+++ b/security/manager/ssl/nsNSSCertHelper.cpp
@@ -1228,17 +1228,16 @@ ProcessUserNotice(SECItem* derNotice, ns
   }
 
   return NS_OK;
 }
 
 static nsresult
 ProcessCertificatePolicies(SECItem  *extData, 
 			   nsAString &text,
-                           SECOidTag ev_oid_tag, // SEC_OID_UNKNOWN means: not EV
 			   nsINSSComponent *nssComponent)
 {
   CERTPolicyInfo **policyInfos, *policyInfo;
   CERTPolicyQualifier **policyQualifiers, *policyQualifier;
   nsAutoString local;
   nsresult rv = NS_OK;
 
   UniqueCERTCertificatePolicies policies(
@@ -1255,36 +1254,20 @@ ProcessCertificatePolicies(SECItem  *ext
       nssComponent->GetPIPNSSBundleString("CertDumpVerisignNotices", local);
       text.Append(local);
       break;
     default:
       GetDefaultOIDFormat(&policyInfo->policyID, nssComponent, local, '.');
       text.Append(local);
     }
 
-    bool needColon = true;
-    if (ev_oid_tag != SEC_OID_UNKNOWN) {
-      // This is an EV cert. Let's see if this oid is the EV oid,
-      // because we want to display the EV information string
-      // next to the correct OID.
-
-      if (policyInfo->oid == ev_oid_tag) {
-        text.Append(':');
-        text.AppendLiteral(SEPARATOR);
-        needColon = false;
-        nssComponent->GetPIPNSSBundleString("CertDumpPolicyOidEV", local);
-        text.Append(local);
-      }
-    }
-
     if (policyInfo->policyQualifiers) {
       /* Add all qualifiers on separate lines, indented */
       policyQualifiers = policyInfo->policyQualifiers;
-      if (needColon)
-        text.Append(':');
+      text.Append(':');
       text.AppendLiteral(SEPARATOR);
       while (*policyQualifiers) {
 	text.AppendLiteral("  ");
 	policyQualifier = *policyQualifiers++;
 	switch(policyQualifier->oid) {
 	case SEC_OID_PKIX_CPS_POINTER_QUALIFIER:
 	  nssComponent->GetPIPNSSBundleString("CertDumpCPSPointer", local);
 	  text.Append(local);
@@ -1495,17 +1478,16 @@ ProcessMSCAVersion(SECItem  *extData,
 
   text.AppendASCII(buf);
   return NS_OK;
 }
 
 static nsresult
 ProcessExtensionData(SECOidTag oidTag, SECItem *extData, 
                      nsAString &text, 
-                     SECOidTag ev_oid_tag, // SEC_OID_UNKNOWN means: not EV
                      nsINSSComponent *nssComponent)
 {
   nsresult rv;
   switch (oidTag) {
   case SEC_OID_X509_KEY_USAGE:
     rv = ProcessKeyUsageExtension(extData, text, nssComponent);
     break;
   case SEC_OID_X509_BASIC_CONSTRAINTS:
@@ -1520,17 +1502,17 @@ ProcessExtensionData(SECOidTag oidTag, S
     break;
   case SEC_OID_X509_SUBJECT_KEY_ID:
     rv = ProcessSubjectKeyId(extData, text, nssComponent);
     break;
   case SEC_OID_X509_AUTH_KEY_ID:
     rv = ProcessAuthKeyId(extData, text, nssComponent);
     break;
   case SEC_OID_X509_CERTIFICATE_POLICIES:
-    rv = ProcessCertificatePolicies(extData, text, ev_oid_tag, nssComponent);
+    rv = ProcessCertificatePolicies(extData, text, nssComponent);
     break;
   case SEC_OID_X509_CRL_DIST_POINTS:
     rv = ProcessCrlDistPoints(extData, text, nssComponent);
     break;
   case SEC_OID_X509_AUTH_INFO_ACCESS:
     rv = ProcessAuthInfoAccess(extData, text, nssComponent);
     break;
   default:
@@ -1545,17 +1527,16 @@ ProcessExtensionData(SECOidTag oidTag, S
     rv = ProcessRawBytes(nssComponent, extData, text);
     break; 
   }
   return rv;
 }
 
 static nsresult
 ProcessSingleExtension(CERTCertExtension *extension,
-                       SECOidTag ev_oid_tag, // SEC_OID_UNKNOWN means: not EV
                        nsINSSComponent *nssComponent,
                        nsIASN1PrintableItem **retExtension)
 {
   nsAutoString text, extvalue;
   GetOIDText(&extension->id, nssComponent, text);
   nsCOMPtr<nsIASN1PrintableItem>extensionItem = new nsNSSASN1PrintableItem();
 
   extensionItem->SetDisplayName(text);
@@ -1567,17 +1548,17 @@ ProcessSingleExtension(CERTCertExtension
     } else {
       nssComponent->GetPIPNSSBundleString("CertDumpNonCritical", text);
     }
   } else {
     nssComponent->GetPIPNSSBundleString("CertDumpNonCritical", text);
   }
   text.AppendLiteral(SEPARATOR);
   nsresult rv = ProcessExtensionData(oidTag, &extension->value, extvalue, 
-                                     ev_oid_tag, nssComponent);
+                                     nssComponent);
   if (NS_FAILED(rv)) {
     extvalue.Truncate();
     rv = ProcessRawBytes(nssComponent, &extension->value, extvalue, false);
   }
   text.Append(extvalue);
 
   extensionItem->SetDisplayValue(text);
   extensionItem.forget(retExtension);
@@ -1763,32 +1744,30 @@ ProcessSubjectPublicKeyInfo(CERTSubjectP
   parentSequence->GetASN1Objects(getter_AddRefs(asn1Objects));
   asn1Objects->AppendElement(spkiSequence, false);
   return NS_OK;
 }
 
 static nsresult
 ProcessExtensions(CERTCertExtension **extensions, 
                   nsIASN1Sequence *parentSequence,
-                  SECOidTag ev_oid_tag, // SEC_OID_UNKNOWN means: not EV
                   nsINSSComponent *nssComponent)
 {
   nsCOMPtr<nsIASN1Sequence> extensionSequence = new nsNSSASN1Sequence;
 
   nsString text;
   nssComponent->GetPIPNSSBundleString("CertDumpExtensions", text);
   extensionSequence->SetDisplayName(text);
   int32_t i;
   nsresult rv;
   nsCOMPtr<nsIASN1PrintableItem> newExtension;
   nsCOMPtr<nsIMutableArray> asn1Objects;
   extensionSequence->GetASN1Objects(getter_AddRefs(asn1Objects));
   for (i=0; extensions[i] != nullptr; i++) {
     rv = ProcessSingleExtension(extensions[i], 
-                                ev_oid_tag,
                                 nssComponent,
                                 getter_AddRefs(newExtension));
     if (NS_FAILED(rv))
       return rv;
 
     asn1Objects->AppendElement(newExtension, false);
   }
   parentSequence->GetASN1Objects(getter_AddRefs(asn1Objects));
@@ -1961,29 +1940,17 @@ nsNSSCertificate::CreateTBSCertificateAS
 
     printableItem->SetDisplayValue(text);
     nssComponent->GetPIPNSSBundleString("CertDumpSubjectUniqueID", text);
     printableItem->SetDisplayName(text);
     asn1Objects->AppendElement(printableItem, false);
 
   }
   if (mCert->extensions) {
-    SECOidTag ev_oid_tag = SEC_OID_UNKNOWN;
-
-    bool validEV;
-    rv = hasValidEVOidTag(ev_oid_tag, validEV);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    if (!validEV) {
-      ev_oid_tag = SEC_OID_UNKNOWN;
-    }
-
-    rv = ProcessExtensions(mCert->extensions, sequence, ev_oid_tag, nssComponent);
+    rv = ProcessExtensions(mCert->extensions, sequence, nssComponent);
     if (NS_FAILED(rv))
       return rv;
   }
   sequence.forget(retSequence);
   return NS_OK;
 }
 
 nsresult
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -65,24 +65,24 @@ extern LazyLogModule gPIPNSSLog;
 NS_IMPL_ISUPPORTS(nsNSSCertificate,
                   nsIX509Cert,
                   nsISerializable,
                   nsIClassInfo)
 
 static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
 
 /*static*/ nsNSSCertificate*
-nsNSSCertificate::Create(CERTCertificate* cert, SECOidTag* evOidPolicy)
+nsNSSCertificate::Create(CERTCertificate* cert)
 {
   if (GeckoProcessType_Default != XRE_GetProcessType()) {
     NS_ERROR("Trying to initialize nsNSSCertificate in a non-chrome process!");
     return nullptr;
   }
   if (cert)
-    return new nsNSSCertificate(cert, evOidPolicy);
+    return new nsNSSCertificate(cert);
   else
     return new nsNSSCertificate();
 }
 
 nsNSSCertificate*
 nsNSSCertificate::ConstructFromDER(char* certDER, int derLen)
 {
   // On non-chrome process prevent instantiation
@@ -117,51 +117,39 @@ nsNSSCertificate::InitFromDER(char* cert
   {
     aCert->dbhandle = CERT_GetDefaultCertDB();
   }
 
   mCert.reset(aCert);
   return true;
 }
 
-nsNSSCertificate::nsNSSCertificate(CERTCertificate* cert,
-                                   SECOidTag* evOidPolicy)
+nsNSSCertificate::nsNSSCertificate(CERTCertificate* cert)
   : mCert(nullptr)
   , mPermDelete(false)
   , mCertType(CERT_TYPE_NOT_YET_INITIALIZED)
-  , mCachedEVStatus(ev_status_unknown)
 {
 #if defined(DEBUG)
   if (GeckoProcessType_Default != XRE_GetProcessType())
     NS_ERROR("Trying to initialize nsNSSCertificate in a non-chrome process!");
 #endif
 
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return;
 
   if (cert) {
     mCert.reset(CERT_DupCertificate(cert));
-    if (evOidPolicy) {
-      if (*evOidPolicy == SEC_OID_UNKNOWN) {
-        mCachedEVStatus =  ev_status_invalid;
-      }
-      else {
-        mCachedEVStatus = ev_status_valid;
-      }
-      mCachedEVOidTag = *evOidPolicy;
-    }
   }
 }
 
-nsNSSCertificate::nsNSSCertificate() :
-  mCert(nullptr),
-  mPermDelete(false),
-  mCertType(CERT_TYPE_NOT_YET_INITIALIZED),
-  mCachedEVStatus(ev_status_unknown)
+nsNSSCertificate::nsNSSCertificate()
+  : mCert(nullptr)
+  , mPermDelete(false)
+  , mCertType(CERT_TYPE_NOT_YET_INITIALIZED)
 {
   if (GeckoProcessType_Default != XRE_GetProcessType())
     NS_ERROR("Trying to initialize nsNSSCertificate in a non-chrome process!");
 }
 
 nsNSSCertificate::~nsNSSCertificate()
 {
   nsNSSShutDownPreventionLock locker;
@@ -1129,95 +1117,16 @@ nsNSSCertificate::Equals(nsIX509Cert* ot
   NS_ENSURE_ARG(other);
   NS_ENSURE_ARG(result);
 
   UniqueCERTCertificate cert(other->GetCert());
   *result = (mCert.get() == cert.get());
   return NS_OK;
 }
 
-nsresult
-nsNSSCertificate::hasValidEVOidTag(SECOidTag& resultOidTag, bool& validEV)
-{
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  RefPtr<mozilla::psm::SharedCertVerifier>
-    certVerifier(mozilla::psm::GetDefaultCertVerifier());
-  NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
-
-  validEV = false;
-  resultOidTag = SEC_OID_UNKNOWN;
-
-  uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
-    mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
-  UniqueCERTCertList unusedBuiltChain;
-  mozilla::pkix::Result result = certVerifier->VerifyCert(mCert.get(),
-    certificateUsageSSLServer, mozilla::pkix::Now(),
-    nullptr /* XXX pinarg */,
-    nullptr /* hostname */,
-    unusedBuiltChain,
-    flags,
-    nullptr /* stapledOCSPResponse */,
-    nullptr /* sctsFromTLSExtension */,
-    nullptr /* firstPartyDomain */,
-    &resultOidTag);
-
-  if (result != mozilla::pkix::Success) {
-    resultOidTag = SEC_OID_UNKNOWN;
-  }
-  if (resultOidTag != SEC_OID_UNKNOWN) {
-    validEV = true;
-  }
-  return NS_OK;
-}
-
-nsresult
-nsNSSCertificate::getValidEVOidTag(SECOidTag& resultOidTag, bool& validEV)
-{
-  if (mCachedEVStatus != ev_status_unknown) {
-    validEV = (mCachedEVStatus == ev_status_valid);
-    if (validEV) {
-      resultOidTag = mCachedEVOidTag;
-    }
-    return NS_OK;
-  }
-
-  nsresult rv = hasValidEVOidTag(resultOidTag, validEV);
-  if (NS_SUCCEEDED(rv)) {
-    if (validEV) {
-      mCachedEVOidTag = resultOidTag;
-    }
-    mCachedEVStatus = validEV ? ev_status_valid : ev_status_invalid;
-  }
-  return rv;
-}
-
-nsresult
-nsNSSCertificate::GetIsExtendedValidation(bool* aIsEV)
-{
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  NS_ENSURE_ARG(aIsEV);
-  *aIsEV = false;
-
-  if (mCachedEVStatus != ev_status_unknown) {
-    *aIsEV = (mCachedEVStatus == ev_status_valid);
-    return NS_OK;
-  }
-
-  SECOidTag oid_tag;
-  return getValidEVOidTag(oid_tag, *aIsEV);
-}
-
 namespace mozilla {
 
 // TODO(bug 1036065): It seems like we only construct CERTCertLists for the
 // purpose of constructing nsNSSCertLists, so maybe we should change this
 // function to output an nsNSSCertList instead.
 SECStatus
 ConstructCERTCertListFromReversedDERArray(
   const mozilla::pkix::DERArray& certArray,
@@ -1623,46 +1532,39 @@ nsNSSCertListEnumerator::GetNext(nsISupp
   return NS_OK;
 }
 
 // NB: This serialization must match that of nsNSSCertificateFakeTransport.
 NS_IMETHODIMP
 nsNSSCertificate::Write(nsIObjectOutputStream* aStream)
 {
   NS_ENSURE_STATE(mCert);
-  nsresult rv = aStream->Write32(static_cast<uint32_t>(mCachedEVStatus));
+  // This field used to be the cached EV status, but it is no longer necessary.
+  nsresult rv = aStream->Write32(0);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = aStream->Write32(mCert->derCert.len);
   if (NS_FAILED(rv)) {
     return rv;
   }
   return aStream->WriteByteArray(mCert->derCert.data, mCert->derCert.len);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::Read(nsIObjectInputStream* aStream)
 {
   NS_ENSURE_STATE(!mCert);
 
-  uint32_t cachedEVStatus;
-  nsresult rv = aStream->Read32(&cachedEVStatus);
+  // This field is no longer used.
+  uint32_t unusedCachedEVStatus;
+  nsresult rv = aStream->Read32(&unusedCachedEVStatus);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  if (cachedEVStatus == static_cast<uint32_t>(ev_status_unknown)) {
-    mCachedEVStatus = ev_status_unknown;
-  } else if (cachedEVStatus == static_cast<uint32_t>(ev_status_valid)) {
-    mCachedEVStatus = ev_status_valid;
-  } else if (cachedEVStatus == static_cast<uint32_t>(ev_status_invalid)) {
-    mCachedEVStatus = ev_status_invalid;
-  } else {
-    return NS_ERROR_UNEXPECTED;
-  }
 
   uint32_t len;
   rv = aStream->Read32(&len);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsXPIDLCString str;
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -32,28 +32,20 @@ class nsNSSCertificate final : public ns
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIX509CERT
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
 
   friend class nsNSSCertificateFakeTransport;
 
-  explicit nsNSSCertificate(CERTCertificate* cert, SECOidTag* evOidPolicy = nullptr);
+  explicit nsNSSCertificate(CERTCertificate* cert);
   nsNSSCertificate();
-  static nsNSSCertificate* Create(CERTCertificate*cert = nullptr,
-                                  SECOidTag* evOidPolicy = nullptr);
+  static nsNSSCertificate* Create(CERTCertificate* cert = nullptr);
   static nsNSSCertificate* ConstructFromDER(char* certDER, int derLen);
-  nsresult GetIsExtendedValidation(bool* aIsEV);
-
-  enum EVStatus {
-    ev_status_invalid = 0,
-    ev_status_valid = 1,
-    ev_status_unknown = 2
-  };
 
   // This is a separate static method so nsNSSComponent can use it during NSS
   // initialization. Other code should probably not use it.
   static nsresult GetDbKey(const mozilla::UniqueCERTCertificate& cert,
                            nsACString& aDbKey);
 
 private:
   virtual ~nsNSSCertificate();
@@ -65,21 +57,16 @@ private:
   nsresult CreateTBSCertificateASN1Struct(nsIASN1Sequence** retSequence,
                                           nsINSSComponent* nssComponent);
   nsresult GetSortableDate(PRTime aTime, nsAString& _aSortableDate);
   virtual void virtualDestroyNSSReference() override;
   void destructorSafeDestroyNSSReference();
   bool InitFromDER(char* certDER, int derLen);  // return false on failure
 
   nsresult GetCertificateHash(nsAString& aFingerprint, SECOidTag aHashAlg);
-
-  EVStatus mCachedEVStatus;
-  SECOidTag mCachedEVOidTag;
-  nsresult hasValidEVOidTag(SECOidTag& resultOidTag, bool& validEV);
-  nsresult getValidEVOidTag(SECOidTag& resultOidTag, bool& validEV);
 };
 
 namespace mozilla {
 
 SECStatus ConstructCERTCertListFromReversedDERArray(
             const mozilla::pkix::DERArray& certArray,
             /*out*/ mozilla::UniqueCERTCertList& certList);
 
--- a/security/manager/ssl/nsNSSCertificateFakeTransport.cpp
+++ b/security/manager/ssl/nsNSSCertificateFakeTransport.cpp
@@ -213,40 +213,39 @@ nsNSSCertificateFakeTransport::GetSha256
 // NB: This serialization must match that of nsNSSCertificate.
 NS_IMETHODIMP
 nsNSSCertificateFakeTransport::Write(nsIObjectOutputStream* aStream)
 {
   // On a non-chrome process we don't have mCert because we lack
   // nsNSSComponent. nsNSSCertificateFakeTransport object is used only to
   // carry the certificate serialization.
 
-  // This serialization has to match that of nsNSSCertificate,
-  // so write a fake cached EV Status.
-  uint32_t status = static_cast<uint32_t>(nsNSSCertificate::ev_status_unknown);
-  nsresult rv = aStream->Write32(status);
+  // This serialization has to match that of nsNSSCertificate, so include this
+  // now-unused field.
+  nsresult rv = aStream->Write32(0);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   rv = aStream->Write32(mCertSerialization->len);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return aStream->WriteByteArray(mCertSerialization->data,
                                  mCertSerialization->len);
 }
 
 NS_IMETHODIMP
 nsNSSCertificateFakeTransport::Read(nsIObjectInputStream* aStream)
 {
-  // This serialization has to match that of nsNSSCertificate,
-  // so read the cachedEVStatus but don't actually use it.
-  uint32_t cachedEVStatus;
-  nsresult rv = aStream->Read32(&cachedEVStatus);
+  // This serialization has to match that of nsNSSCertificate, so read the (now
+  // unused) cachedEVStatus.
+  uint32_t unusedCachedEVStatus;
+  nsresult rv = aStream->Read32(&unusedCachedEVStatus);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   uint32_t len;
   rv = aStream->Read32(&len);
   if (NS_FAILED(rv)) {
     return rv;
--- a/security/manager/ssl/nsSSLStatus.cpp
+++ b/security/manager/ssl/nsSSLStatus.cpp
@@ -1,20 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Casting.h"
 #include "nsSSLStatus.h"
-#include "plstr.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObjectInputStream.h"
+#include "nsNSSCertificate.h"
 #include "SignedCertificateTimestamp.h"
 #include "ssl.h"
 
 NS_IMETHODIMP
 nsSSLStatus::GetServerCert(nsIX509Cert** aServerCert)
 {
   NS_ENSURE_ARG_POINTER(aServerCert);
 
@@ -312,34 +312,23 @@ nsSSLStatus::nsSSLStatus()
 
 NS_IMPL_ISUPPORTS(nsSSLStatus, nsISSLStatus, nsISerializable, nsIClassInfo)
 
 nsSSLStatus::~nsSSLStatus()
 {
 }
 
 void
-nsSSLStatus::SetServerCert(nsNSSCertificate* aServerCert,
-                           nsNSSCertificate::EVStatus aEVStatus)
+nsSSLStatus::SetServerCert(nsNSSCertificate* aServerCert, EVStatus aEVStatus)
 {
-  mServerCert = aServerCert;
+  MOZ_ASSERT(aServerCert);
 
-  if (aEVStatus != nsNSSCertificate::ev_status_unknown) {
-    mIsEV = (aEVStatus == nsNSSCertificate::ev_status_valid);
-    mHasIsEVStatus = true;
-    return;
-  }
-
-  if (aServerCert) {
-    nsresult rv = aServerCert->GetIsExtendedValidation(&mIsEV);
-    if (NS_FAILED(rv)) {
-      return;
-    }
-    mHasIsEVStatus = true;
-  }
+  mServerCert = aServerCert;
+  mIsEV = (aEVStatus == EVStatus::EV);
+  mHasIsEVStatus = true;
 }
 
 void
 nsSSLStatus::SetCertificateTransparencyInfo(
   const mozilla::psm::CertificateTransparencyInfo& info)
 {
   using mozilla::ct::SignedCertificateTimestamp;
 
--- a/security/manager/ssl/nsSSLStatus.h
+++ b/security/manager/ssl/nsSSLStatus.h
@@ -9,35 +9,40 @@
 
 #include "CertVerifier.h" // For CertificateTransparencyInfo
 #include "nsISSLStatus.h"
 #include "nsCOMPtr.h"
 #include "nsXPIDLString.h"
 #include "nsIX509Cert.h"
 #include "nsISerializable.h"
 #include "nsIClassInfo.h"
-#include "nsNSSCertificate.h" // For EVStatus
+
+class nsNSSCertificate;
+
+enum class EVStatus {
+  NotEV = 0,
+  EV = 1,
+};
 
 class nsSSLStatus final
   : public nsISSLStatus
   , public nsISerializable
   , public nsIClassInfo
 {
 protected:
   virtual ~nsSSLStatus();
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISSLSTATUS
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
 
   nsSSLStatus();
 
-  void SetServerCert(nsNSSCertificate* aServerCert,
-                     nsNSSCertificate::EVStatus aEVStatus);
+  void SetServerCert(nsNSSCertificate* aServerCert, EVStatus aEVStatus);
 
   bool HasServerCert() {
     return mServerCert != nullptr;
   }
 
   void SetCertificateTransparencyInfo(
     const mozilla::psm::CertificateTransparencyInfo& info);
 
--- a/security/manager/ssl/tests/mochitest/browser/browser.ini
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -7,11 +7,12 @@ support-files =
 [browser_bug627234_perwindowpb.js]
 [browser_certificateManagerLeak.js]
 [browser_certViewer.js]
 [browser_clientAuth_connection.js]
 [browser_clientAuth_ui.js]
 [browser_deleteCert_ui.js]
 [browser_downloadCert_ui.js]
 [browser_editCACertTrust.js]
-# An earlier attempting at landing this test resulted in frequent intermittent
-# failures, almost entirely on Linux.
+# An earlier attempt at landing this test resulted in frequent intermittent
+# failures, almost entirely on Linux. See Bug 1309519.
 skip-if = os == "linux"
+[browser_exportP12_passwordUI.js]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_exportP12_passwordUI.js
@@ -0,0 +1,142 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the UI for setting the password on a to be exported PKCS #12 file:
+//   1. Correctly requires the password to be typed in twice as confirmation.
+//   2. Calculates and displays the strength of said password.
+
+/**
+ * @typedef {TestCase}
+ * @type Object
+ * @property {String} name
+ *           The name of the test case for display purposes.
+ * @property {String} password1
+ *           The password to enter into the first password textbox.
+ * @property {String} password2
+ *           The password to enter into the second password textbox.
+ * @property {String} strength
+ *           The expected strength of the password in the range [0, 100].
+ */
+
+/**
+ * A list of test cases representing various inputs to the password textboxes.
+ * @type TestCase[]
+ */
+const TEST_CASES = [
+  { name: "empty",
+    password1: "",
+    password2: "",
+    strength: "0" },
+  { name: "match-weak",
+    password1: "foo",
+    password2: "foo",
+    strength: "10" },
+  { name: "match-medium",
+    password1: "foo123",
+    password2: "foo123",
+    strength: "60" },
+  { name: "match-strong",
+    password1: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+    password2: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+    strength: "100" },
+  { name: "mismatch-weak",
+    password1: "foo",
+    password2: "bar",
+    strength: "10" },
+  { name: "mismatch-medium",
+    password1: "foo123",
+    password2: "bar",
+    strength: "60" },
+  { name: "mismatch-strong",
+    password1: "fooBARBAZ 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三",
+    password2: "bar",
+    strength: "100" },
+];
+
+/**
+ * Opens the dialog shown to set the password on a PKCS #12 file being exported.
+ *
+ * @returns {Promise}
+ *          A promise that resolves when the dialog has finished loading, with
+ *          an array consisting of:
+ *            1. The window of the opened dialog.
+ *            2. The return value nsIWritablePropertyBag2 passed to the dialog.
+ */
+function openSetP12PasswordDialog() {
+  let returnVals = Cc["@mozilla.org/hash-property-bag;1"]
+                     .createInstance(Ci.nsIWritablePropertyBag2);
+  let win = window.openDialog("chrome://pippki/content/setp12password.xul", "",
+                              "", returnVals);
+  return new Promise((resolve, reject) => {
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad);
+      resolve([win, returnVals]);
+    });
+  });
+}
+
+// Tests that the first password textbox is the element that is initially
+// focused.
+add_task(function* testFocus() {
+  let [win, retVals] = yield openSetP12PasswordDialog();
+  Assert.equal(win.document.activeElement,
+               win.document.getElementById("pw1").inputField,
+               "First password textbox should have focus");
+  yield BrowserTestUtils.closeWindow(win);
+});
+
+// Tests that the password strength algorithm used is reasonable, and that the
+// Accept button is only enabled if the two passwords match.
+add_task(function* testPasswordStrengthAndEquality() {
+  let [win, retVals] = yield openSetP12PasswordDialog();
+  let password1Textbox = win.document.getElementById("pw1");
+  let password2Textbox = win.document.getElementById("pw2");
+  let strengthProgressBar = win.document.getElementById("pwmeter");
+
+  for (let testCase of TEST_CASES) {
+    password1Textbox.value = testCase.password1;
+    password2Textbox.value = testCase.password2;
+    // Setting the value of the password textboxes via |.value| apparently
+    // doesn't cause the oninput handlers to be called, so we do it here.
+    password1Textbox.oninput();
+    password2Textbox.oninput();
+
+    Assert.equal(win.document.documentElement.getButton("accept").disabled,
+                 password1Textbox.value != password2Textbox.value,
+                 "Actual and expected accept button disable state should " +
+                 `match for ${testCase.name}`);
+    Assert.equal(strengthProgressBar.value, testCase.strength,
+                 "Actual and expected strength value should match for" +
+                 `${testCase.name}`);
+  }
+
+  yield BrowserTestUtils.closeWindow(win);
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(function* testAcceptDialogReturnValues() {
+  let [win, retVals] = yield openSetP12PasswordDialog();
+  const password = "fooBAR 1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?一二三";
+  win.document.getElementById("pw1").value = password;
+  win.document.getElementById("pw2").value = password;
+  info("Accepting dialog");
+  win.document.getElementById("setp12password").acceptDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.ok(retVals.get("confirmedPassword"),
+            "Return value should signal user confirmed a password");
+  Assert.equal(retVals.get("password"), password,
+               "Actual and expected password should match");
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(function* testCancelDialogReturnValues() {
+  let [win, retVals] = yield openSetP12PasswordDialog();
+  info("Canceling dialog");
+  win.document.getElementById("setp12password").cancelDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.ok(!retVals.get("confirmedPassword"),
+            "Return value should signal user didn't confirm a password");
+});
copy from security/manager/ssl/tests/unit/test_ev_certs/anyPolicy-int-path-int.pem
copy to security/manager/ssl/tests/unit/bad_certs/ev-test-intermediate.pem
--- a/security/manager/ssl/tests/unit/test_ev_certs/anyPolicy-int-path-int.pem
+++ b/security/manager/ssl/tests/unit/bad_certs/ev-test-intermediate.pem
@@ -1,20 +1,20 @@
 -----BEGIN CERTIFICATE-----
-MIIDQjCCAiygAwIBAgIUI4h7bIgXBroqPq3r8qcqzWTPiTwwCwYJKoZIhvcNAQEL
+MIIDNzCCAiGgAwIBAgIUdakyQxHEOz8eq2oddSdoUYT0DP0wCwYJKoZIhvcNAQEL
 MBExDzANBgNVBAMMBmV2cm9vdDAiGA8yMDE0MTEyNzAwMDAwMFoYDzIwMTcwMjA0
-MDAwMDAwWjAhMR8wHQYDVQQDDBZhbnlQb2xpY3ktaW50LXBhdGgtaW50MIIBIjAN
-BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq
-5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SSc
-An7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39
-ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYk
-zBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3u
-JtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQAB
-o4GBMH8wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwTwYIKwYBBQUHAQEEQzBB
-MD8GCCsGAQUFBzABhjNodHRwOi8vd3d3LmV4YW1wbGUuY29tOjg4ODgvYW55UG9s
-aWN5LWludC1wYXRoLWludC8wEQYDVR0gBAowCDAGBgRVHSAAMAsGCSqGSIb3DQEB
-CwOCAQEAaar6+lvsKAL6fuKS9b8HOSI1Q6c+7/PDAo+YPVsDyzg4OYpFHfrJqveK
-vmwWSnUngX/V702znW4woDu1ZjXLWpTG4xx87FU7b0BIrL7r1N1twAohOYFUMnjl
-TW7RMjTgMGIgxybQc3N0snwf2SJedUu78xekdLW1/jTiMuIEys/+44tqGzVsFu9j
-XrFxPxNBHVzR8UFGICREeE2nFeOnqj3uQPh1JJszKUlfXbYtjgPFKfbbsPzzGLJ3
-tLmzPZLSeEed/AYvegq00CybA5f6UDY1uMnECekHAWFzv/yhZZsL+hMSGXTctE7+
-C+WTNlFX41Gi6uvck6N8T3ABNVTk8A==
+MDAwMDAwWjAfMR0wGwYDVQQDDBRldi10ZXN0LWludGVybWVkaWF0ZTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1
+SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+
+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYL
+K7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwc
+bJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibW
+JZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaN5
+MHcwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwRwYIKwYBBQUHAQEEOzA5MDcG
+CCsGAQUFBzABhitodHRwOi8vbG9jYWxob3N0Ojg4ODgvZXYtdGVzdC1pbnRlcm1l
+ZGlhdGUvMBEGA1UdIAQKMAgwBgYEVR0gADALBgkqhkiG9w0BAQsDggEBAEnoeMK3
+VQ18/OA9LzFSlkr8YFLnz/0iL8l2LnftDtcoTckr3Zhyo6HdQDYvWf7Ox1sN3BLB
+PFgQ0bEWSLRUSCTuUjLM+gKR8Dzo5LWY3ZyHh851NRP4o/mwXujr4qlMiCpKMlJi
+itjIIPEID2/oFdf8uujH+q6/Mk038v+Bq0FcfLmpcfmsptCHza1Ryw2lxc3WVvOv
+J+5t6qA1H6xJIVcb0dQwF5doTMV27YDmuLyg2VTKnoF4Fux/glH1v/YXdfxi6WKC
+8L7jeeVMurd3huWLRIoBimOn26e/wMQJAJMOXfwnYU1RULgbwdFCVZUeSNYW4pDY
+4ga6LzbJdLBvb6k=
 -----END CERTIFICATE-----
\ No newline at end of file
copy from security/manager/ssl/tests/unit/test_ev_certs/anyPolicy-int-path-int.pem.certspec
copy to security/manager/ssl/tests/unit/bad_certs/ev-test-intermediate.pem.certspec
--- a/security/manager/ssl/tests/unit/test_ev_certs/anyPolicy-int-path-int.pem.certspec
+++ b/security/manager/ssl/tests/unit/bad_certs/ev-test-intermediate.pem.certspec
@@ -1,7 +1,7 @@
 issuer:evroot
-subject:anyPolicy-int-path-int
+subject:ev-test-intermediate
 issuerKey:ev
 extension:basicConstraints:cA,
 extension:keyUsage:cRLSign,keyCertSign
-extension:authorityInformationAccess:http://www.example.com:8888/anyPolicy-int-path-int/
+extension:authorityInformationAccess:http://localhost:8888/ev-test-intermediate/
 extension:certificatePolicies:any
copy from security/manager/ssl/tests/unit/test_ev_certs/test-oid-path-ee.pem
copy to security/manager/ssl/tests/unit/bad_certs/ev-test.pem
--- a/security/manager/ssl/tests/unit/test_ev_certs/test-oid-path-ee.pem
+++ b/security/manager/ssl/tests/unit/bad_certs/ev-test.pem
@@ -1,20 +1,20 @@
 -----BEGIN CERTIFICATE-----
-MIIDVTCCAj+gAwIBAgIULwfe1XYxIxI1GOvu3ZnTqxvVOYYwCwYJKoZIhvcNAQEL
-MBwxGjAYBgNVBAMMEXRlc3Qtb2lkLXBhdGgtaW50MCIYDzIwMTQxMTI3MDAwMDAw
-WhgPMjAxNzAyMDQwMDAwMDBaMBsxGTAXBgNVBAMMEHRlc3Qtb2lkLXBhdGgtZWUw
-ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQ
-PTwT2erkNUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH
-9xzVJJwCfs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw
-4A8Njf1mCyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86
-exCABiTMHGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0
-ipVhHe4m1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2N
-AgMBAAGjgY8wgYwwSQYIKwYBBQUHAQEEPTA7MDkGCCsGAQUFBzABhi1odHRwOi8v
-d3d3LmV4YW1wbGUuY29tOjg4ODgvdGVzdC1vaWQtcGF0aC1lZS8wHwYDVR0gBBgw
-FjAUBhIrBgEEAetJhRqFGoUaAYN0CQEwHgYDVR0RBBcwFYITZXYtdGVzdC5leGFt
-cGxlLmNvbTALBgkqhkiG9w0BAQsDggEBAGBM93ylo+yXjVAr7GHY2/Suvddfd47X
-i+0qQc5Aif2f5okWm7k8BaLdhQYMcLo/D/AZzKcPvO5wUFdiInHPF069ebu8s6qL
-qZ7ybJK7AR/UfkS4Yn+gTdvPUxasFCtorT3tx8aws3Y9NBK0YV2IImgC+wS2Qe37
-XBUF+526UjJ/ooInFnW6Ukf8rdhxMpSOAXzblJCfHMnnkg36m5zSWNH83oTWEGwe
-tWolqulTICNpRA4rqwO7i2BRHkgQrq9lhQS3/rCyGYgeqware7QPSj5S4WXBLM3p
-a7je/NteBTOUVsfngQSz5ETVu3Bj7mgJYmtkCC5ZRVfQmjWsfPyqslE=
+MIIDPjCCAiigAwIBAgIUD91RYDYNkAEZj914Ztj5ISzYywEwCwYJKoZIhvcNAQEL
+MB8xHTAbBgNVBAMMFGV2LXRlc3QtaW50ZXJtZWRpYXRlMCIYDzIwMTQxMTI3MDAw
+MDAwWhgPMjAxNzAyMDQwMDAwMDBaMBIxEDAOBgNVBAMMB2V2LXRlc3QwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erk
+NUq07PVoV2wke8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwC
+fs1D/B5p0DggKZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1m
+CyuwJJKkfbmIYXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTM
+HGyXrZZhW7filhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m
+1iWdq5EITjbLHCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAGj
+fzB9MDoGCCsGAQUFBwEBBC4wLDAqBggrBgEFBQcwAYYeaHR0cDovL2xvY2FsaG9z
+dDo4ODg4L2V2LXRlc3QvMB8GA1UdIAQYMBYwFAYSKwYBBAHrSYUahRqFGgGDdAkB
+MB4GA1UdEQQXMBWCE2V2LXRlc3QuZXhhbXBsZS5jb20wCwYJKoZIhvcNAQELA4IB
+AQCGr7tHhNrdziIH9DlTc6KOtwgHAC/9oq2t5r+nTw2PAzGnXlVDhechXaXHYJJ0
+6PAEh+VkN+QGluG2qUfQsKDoMMNTOdJekZWlnhPuAwQPfjzLa06yLbmzMwUSHJZU
+XdeAbf1o79xyMHfJMcmA5pduAv0RAsAZl6SyiR5Fm6nORHVHWu0Zw9WqPDBCb+BW
+Mx2Lgl3M95jAbUeR2HfDOHTh3Crpf0V8FL2n9jFgpQ+niO3JFZOBg3dVQ+BMIov/
+pldY1jGnSAaf1xGh3vL5ildKQVH2an69KjIliwofEW0qq6q4OTZgZXYBuARzGvPl
+Pbf+6Wnb09vLKtJ4QXy9cBHO
 -----END CERTIFICATE-----
\ No newline at end of file
copy from security/manager/ssl/tests/unit/test_ev_certs/test-oid-path-ee.pem.certspec
copy to security/manager/ssl/tests/unit/bad_certs/ev-test.pem.certspec
--- a/security/manager/ssl/tests/unit/test_ev_certs/test-oid-path-ee.pem.certspec
+++ b/security/manager/ssl/tests/unit/bad_certs/ev-test.pem.certspec
@@ -1,5 +1,5 @@
-issuer:test-oid-path-int
-subject:test-oid-path-ee
-extension:authorityInformationAccess:http://www.example.com:8888/test-oid-path-ee/
+issuer:ev-test-intermediate
+subject:ev-test
+extension:authorityInformationAccess:http://localhost:8888/ev-test/
 extension:certificatePolicies:1.3.6.1.4.1.13769.666.666.666.1.500.9.1
 extension:subjectAlternativeName:ev-test.example.com
copy from security/manager/ssl/tests/unit/test_ev_certs/evroot.key
copy to security/manager/ssl/tests/unit/bad_certs/evroot.key
copy from security/manager/ssl/tests/unit/test_ev_certs/evroot.key.keyspec
copy to security/manager/ssl/tests/unit/bad_certs/evroot.key.keyspec
copy from security/manager/ssl/tests/unit/test_ev_certs/evroot.pem
copy to security/manager/ssl/tests/unit/bad_certs/evroot.pem
copy from security/manager/ssl/tests/unit/test_ev_certs/evroot.pem.certspec
copy to security/manager/ssl/tests/unit/bad_certs/evroot.pem.certspec
--- a/security/manager/ssl/tests/unit/bad_certs/moz.build
+++ b/security/manager/ssl/tests/unit/bad_certs/moz.build
@@ -11,16 +11,19 @@
 #    'beforeEpochINT.pem',
 #    'beforeEpochIssuer.pem',
 #    'ca-used-as-end-entity.pem',
 #    'default-ee.pem',
 #    'eeIssuedByNonCA.pem',
 #    'eeIssuedByV1Cert.pem',
 #    'emptyIssuerName.pem',
 #    'emptyNameCA.pem',
+#    'ev-test-intermediate.pem',
+#    'ev-test.pem',
+#    'evroot.pem',
 #    'expired-ee.pem',
 #    'expiredINT.pem',
 #    'expiredissuer.pem',
 #    'idn-certificate.pem',
 #    'inadequateKeySizeEE.pem',
 #    'inadequatekeyusage-ee.pem',
 #    'ipAddressAsDNSNameInSAN.pem',
 #    'md5signature-expired.pem',
@@ -51,14 +54,15 @@
 #    'v1Cert.pem',
 #)
 #
 #for test_certificate in test_certificates:
 #    GeneratedTestCertificate(test_certificate)
 #
 #test_keys = (
 #    'default-ee.key',
+#    'evroot.key',
 #    'inadequateKeySizeEE.key',
 #    'other-test-ca.key',
 #)
 #
 #for test_key in test_keys:
 #    GeneratedTestKey(test_key)
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -218,30 +218,81 @@ function add_tests() {
 
   //---------------------------------------------------------------------------
 
   // Reset state
   add_test(function() { clearOCSPCache(); run_next_test(); });
 
   // This test makes sure that OCSP cache are isolated by firstPartyDomain.
 
+  let gObservedCnt = 0;
+  let protocolProxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+                               .getService(Ci.nsIProtocolProxyService);
+
+  // Observe all channels and make sure the firstPartyDomain in their loadInfo's
+  // origin attributes are aFirstPartyDomain.
+  function startObservingChannels(aFirstPartyDomain) {
+    // We use a dummy proxy filter to catch all channels, even those that do not
+    // generate an "http-on-modify-request" notification.
+    let proxyFilter = {
+      applyFilter: function (aProxyService, aChannel, aProxy) {
+        // We have the channel; provide it to the callback.
+        if (aChannel.originalURI.spec == "http://localhost:8888/") {
+          gObservedCnt++;
+          equal(aChannel.loadInfo.originAttributes.firstPartyDomain,
+                aFirstPartyDomain, "firstPartyDomain should match");
+        }
+        // Pass on aProxy unmodified.
+        return aProxy;
+      }
+    };
+    protocolProxyService.registerChannelFilter(proxyFilter, 0);
+    // Return the stop() function:
+    return () => protocolProxyService.unregisterChannelFilter(proxyFilter);
+  }
+
+  let stopObservingChannels;
+  add_test(function() {
+    stopObservingChannels = startObservingChannels("foo.com");
+    run_next_test();
+  });
+
   // A good OCSP response will be cached.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = foo.com) -> a fetch " +
                 "should have been attempted", "foo.com");
 
   // The cache will prevent a fetch from happening.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess, [],
                 "Noted OCSP server failure (firstPartyDomain = foo.com) -> a " +
                 "fetch should not have been attempted", "foo.com");
 
+  add_test(function() {
+    stopObservingChannels();
+    equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
+    gObservedCnt = 0;
+    run_next_test();
+  });
+
+  add_test(function() {
+    stopObservingChannels = startObservingChannels("bar.com");
+    run_next_test();
+  });
+
   // But using a different firstPartyDomain should result in a fetch.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = bar.com) -> a fetch " +
                 "should have been attempted", "bar.com");
 
+  add_test(function() {
+    stopObservingChannels();
+    equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
+    gObservedCnt = 0;
+    run_next_test();
+  });
+
   //---------------------------------------------------------------------------
 
   // Reset state
   add_test(function() { clearOCSPCache(); run_next_test(); });
 }
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_session_resumption.js
@@ -0,0 +1,117 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that PSM makes the correct determination of the security status of
+// loads involving session resumption (i.e. when a TLS handshake bypasses the
+// AuthCertificate callback).
+
+do_get_profile();
+const certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+
+do_register_cleanup(() => {
+  Services.prefs.clearUserPref("security.OCSP.enabled");
+});
+
+Services.prefs.setIntPref("security.OCSP.enabled", 1);
+
+addCertFromFile(certdb, "bad_certs/evroot.pem", "CTu,,");
+addCertFromFile(certdb, "bad_certs/ev-test-intermediate.pem", ",,");
+
+// For expired.example.com, the platform will make a connection that will fail.
+// Using information gathered at that point, an override will be added and
+// another connection will be made. This connection will succeed. At that point,
+// as long as the session cache isn't cleared, subsequent new connections should
+// use session resumption, thereby bypassing the AuthCertificate hook. We need
+// to ensure that the correct security state is propagated to the new connection
+// information object.
+function add_resume_non_ev_with_override_test() {
+  // This adds the override and makes one successful connection.
+  add_cert_override_test("expired.example.com",
+                         Ci.nsICertOverrideService.ERROR_TIME,
+                         SEC_ERROR_EXPIRED_CERTIFICATE);
+
+  // This connects again, using session resumption. Note that we don't clear
+  // the TLS session cache between these operations (that would defeat the
+  // purpose).
+  add_connection_test("expired.example.com", PRErrorCodeSuccess, null,
+    (transportSecurityInfo) => {
+      ok(transportSecurityInfo.securityState &
+         Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN,
+         "expired.example.com should have STATE_CERT_USER_OVERRIDDEN flag");
+      let sslStatus = transportSecurityInfo
+                        .QueryInterface(Ci.nsISSLStatusProvider)
+                        .SSLStatus;
+      ok(!sslStatus.isDomainMismatch,
+         "expired.example.com should not have isDomainMismatch set");
+      ok(sslStatus.isNotValidAtThisTime,
+         "expired.example.com should have isNotValidAtThisTime set");
+      ok(!sslStatus.isUntrusted,
+         "expired.example.com should not have isUntrusted set");
+      ok(!sslStatus.isExtendedValidation,
+         "expired.example.com should not have isExtendedValidation set");
+    }
+  );
+}
+
+// Helper function that adds a test that connects to ev-test.example.com and
+// verifies that it validates as EV (or not, if we're running a non-debug
+// build). This assumes that an appropriate OCSP responder is running or that
+// good responses are cached.
+function add_one_ev_test() {
+  add_connection_test("ev-test.example.com", PRErrorCodeSuccess, null,
+    (transportSecurityInfo) => {
+      ok(!(transportSecurityInfo.securityState &
+           Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN),
+         "ev-test.example.com should not have STATE_CERT_USER_OVERRIDDEN flag");
+      let sslStatus = transportSecurityInfo
+                        .QueryInterface(Ci.nsISSLStatusProvider)
+                        .SSLStatus;
+      ok(!sslStatus.isDomainMismatch,
+         "ev-test.example.com should not have isDomainMismatch set");
+      ok(!sslStatus.isNotValidAtThisTime,
+         "ev-test.example.com should not have isNotValidAtThisTime set");
+      ok(!sslStatus.isUntrusted,
+         "ev-test.example.com should not have isUntrusted set");
+      ok(!gEVExpected || sslStatus.isExtendedValidation,
+         "ev-test.example.com should have isExtendedValidation set " +
+         "(or this is a non-debug build)");
+    }
+  );
+}
+
+// This test is similar, except with extended validation. We should connect
+// successfully, and the certificate should be EV in debug builds. Without
+// clearing the session cache, we should connect successfully again, this time
+// with session resumption. The certificate should again be EV in debug builds.
+function add_resume_ev_test() {
+  const SERVER_PORT = 8888;
+  let expectedRequestPaths = gEVExpected ? [ "ev-test-intermediate", "ev-test" ]
+                                         : [ "ev-test" ];
+  let responseTypes = gEVExpected ? [ "good", "good" ] : [ "good" ];
+  // Since we cache OCSP responses, we only ever actually serve one set.
+  let ocspResponder = startOCSPResponder(SERVER_PORT, "localhost", "bad_certs",
+                                         expectedRequestPaths,
+                                         expectedRequestPaths.slice(),
+                                         null, responseTypes);
+  // We should be able to connect and verify the certificate as EV (in debug
+  // builds).
+  add_one_ev_test();
+  // We should be able to connect again (using session resumption). In debug
+  // builds, the certificate should be noted as EV. Again, it's important that
+  // nothing clears the TLS cache in between these two operations.
+  add_one_ev_test();
+
+  add_test(() => {
+    ocspResponder.stop(run_next_test);
+  });
+}
+
+function run_test() {
+  add_tls_server_setup("BadCertServer", "bad_certs");
+  add_resume_non_ev_with_override_test();
+  add_resume_ev_test();
+  run_next_test();
+}
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp
@@ -71,16 +71,17 @@ const BadCertHost sBadCertHosts[] =
   { "end-entity-issued-by-v1-cert.example.com", "eeIssuedByV1Cert" },
   { "end-entity-issued-by-non-CA.example.com", "eeIssuedByNonCA" },
   { "inadequate-key-size-ee.example.com", "inadequateKeySizeEE" },
   { "badSubjectAltNames.example.com", "badSubjectAltNames" },
   { "ipAddressAsDNSNameInSAN.example.com", "ipAddressAsDNSNameInSAN" },
   { "noValidNames.example.com", "noValidNames" },
   { "bug413909.xn--hxajbheg2az3al.xn--jxalpdlp", "idn-certificate" },
   { "emptyissuername.example.com", "emptyIssuerName" },
+  { "ev-test.example.com", "ev-test" },
   { nullptr, nullptr }
 };
 
 int32_t
 DoSNISocketConfigBySubjectCN(PRFileDesc* aFd, const SECItem* aSrvNameArr,
                              uint32_t aSrvNameArrSize)
 {
   for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
--- a/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/TLSServer.cpp
@@ -484,16 +484,19 @@ ConfigSecureServerWithNamedCert(PRFileDe
   if (certOut) {
     *certOut = Move(cert);
   }
 
   if (keaOut) {
     *keaOut = certKEA;
   }
 
+  SSL_OptionSet(fd, SSL_NO_CACHE, false);
+  SSL_OptionSet(fd, SSL_ENABLE_SESSION_TICKETS, true);
+
   return SECSuccess;
 }
 
 int
 StartServer(const char *nssCertDBDir, SSLSNISocketConfig sniSocketConfig,
             void *sniSocketConfigArg)
 {
   const char *debugLevel = PR_GetEnv("MOZ_TLS_SERVER_DEBUG_LEVEL");
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -112,16 +112,18 @@ run-sequentially = hardcoded ports
 [test_pinning.js]
 run-sequentially = hardcoded ports
 # This test can take longer than 300 seconds on B2G emulator debug builds, so
 # give it enough time to finish. See bug 1081128.
 requesttimeoutfactor = 2
 [test_pinning_dynamic.js]
 [test_pinning_header_parsing.js]
 [test_sdr.js]
+[test_session_resumption.js]
+run-sequentially = hardcoded ports
 [test_signed_apps.js]
 [test_signed_apps-marketplace.js]
 [test_signed_dir.js]
 tags = addons psm
 [test_sss_eviction.js]
 [test_sss_readstate.js]
 [test_sss_readstate_child.js]
 support-files = sss_readstate_child_worker.js
--- a/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
+++ b/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
@@ -7,24 +7,33 @@ import pprint
 from datetime import datetime
 
 import mozfile
 
 from marionette import MarionetteTestCase
 from marionette_driver import Wait
 from marionette_driver.errors import NoSuchWindowException
 
+from firefox_puppeteer import PuppeteerMixin
 from firefox_puppeteer.api.prefs import Preferences
 from firefox_puppeteer.api.software_update import SoftwareUpdate
-from firefox_puppeteer.testcases import BaseFirefoxTestCase
 from firefox_puppeteer.ui.update_wizard import UpdateWizardDialog
 
 
-class FirefoxTestCase(BaseFirefoxTestCase, MarionetteTestCase):
-    """ Integrate MarionetteTestCase with BaseFirefoxTestCase by reordering MRO """
+class FirefoxTestCase(PuppeteerMixin, MarionetteTestCase):
+    """Base TestCase class for Firefox Desktop tests.
+
+    This class enhances the MarionetteTestCase class with PuppeteerMixin on top
+    of MarionetteTestCase by reordering the MRO.
+
+    If you're extending the inheritance tree further to make specialized
+    TestCases, favour the use of super() as opposed to explicit calls to a
+    parent class.
+
+    """
     pass
 
 
 class UpdateTestCase(FirefoxTestCase):
 
     TIMEOUT_UPDATE_APPLY = 300
     TIMEOUT_UPDATE_CHECK = 30
     TIMEOUT_UPDATE_DOWNLOAD = 360
@@ -50,17 +59,17 @@ class UpdateTestCase(FirefoxTestCase):
         self.update_mar_channels = set(kwargs.pop('update_mar_channels'))
         self.default_mar_channels = None
 
         self.updates = []
 
     def setUp(self, is_fallback=False):
         super(UpdateTestCase, self).setUp()
 
-        self.software_update = SoftwareUpdate(lambda: self.marionette)
+        self.software_update = SoftwareUpdate(self.marionette)
         self.download_duration = None
 
         # Bug 604364 - Preparation to test multiple update steps
         self.current_update_index = 0
 
         # Ensure that there exists no already partially downloaded update
         self.remove_downloaded_update()
 
@@ -217,29 +226,30 @@ class UpdateTestCase(FirefoxTestCase):
         :param timeout: How long to wait for the download to finish. Optional, default to 360s.
         """
 
         def download_via_update_wizard(dialog):
             """ Download the update via the old update wizard dialog.
 
             :param dialog: Instance of :class:`UpdateWizardDialog`.
             """
-            prefs = Preferences(lambda: self.marionette)
+            prefs = Preferences(self.marionette)
             prefs.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, dialog.window_type)
 
             try:
                 # If updates have already been found, proceed to download
                 if dialog.wizard.selected_panel in [dialog.wizard.updates_found_basic,
                                                     dialog.wizard.error_patching,
                                                     ]:
                     dialog.select_next_page()
 
                 # If incompatible add-on are installed, skip over the wizard page
                 # TODO: Remove once we no longer support version Firefox 45.0ESR
-                if self.utils.compare_version(self.appinfo.version, '49.0a1') == -1:
+                if self.puppeteer.utils.compare_version(self.puppeteer.appinfo.version,
+                                                        '49.0a1') == -1:
                     if dialog.wizard.selected_panel == dialog.wizard.incompatible_list:
                         dialog.select_next_page()
 
                 # Updates were stored in the cache, so no download is necessary
                 if dialog.wizard.selected_panel in [dialog.wizard.finished,
                                                     dialog.wizard.finished_background,
                                                     ]:
                     pass
@@ -309,17 +319,17 @@ class UpdateTestCase(FirefoxTestCase):
             self.software_update.force_fallback()
 
         # Restart Firefox to apply the downloaded update
         self.restart(callback=lambda: about_window.deck.apply.button.click())
 
     def download_and_apply_forced_update(self):
         # The update wizard dialog opens automatically after the restart but with a short delay
         dialog = Wait(self.marionette, ignored_exceptions=[NoSuchWindowException]).until(
-            lambda _: self.windows.switch_to(lambda win: type(win) is UpdateWizardDialog)
+            lambda _: self.puppeteer.windows.switch_to(lambda win: type(win) is UpdateWizardDialog)
         )
 
         # In case of a broken complete update the about window has to be used
         if self.updates[self.current_update_index]['patch']['is_complete']:
             about_window = None
             try:
                 self.assertEqual(dialog.wizard.selected_panel,
                                  dialog.wizard.error)
--- a/testing/firefox-ui/harness/requirements.txt
+++ b/testing/firefox-ui/harness/requirements.txt
@@ -1,5 +1,5 @@
-firefox-puppeteer >= 52.0.0, <53.0.0
+firefox-puppeteer >= 52.1.0, <53.0.0
 marionette-client >= 2.3.0
 mozfile >= 1.2
 mozinfo >= 0.8
 mozinstall >= 1.12
--- a/testing/firefox-ui/tests/functional/keyboard_shortcuts/test_browser_window.py
+++ b/testing/firefox-ui/tests/functional/keyboard_shortcuts/test_browser_window.py
@@ -32,17 +32,17 @@ class TestBrowserWindowShortcuts(Firefox
         current_name = self.marionette.execute_script("""
             return window.document.activeElement.localName;
         """)
 
         # This doesn't test anything if we're already at input.
         self.assertNotEqual(current_name, "input")
 
         # TODO: To be moved to the upcoming search library
-        if self.platform == 'linux':
+        if self.puppeteer.platform == 'linux':
             key = 'searchFocusUnix.commandkey'
         else:
             key = 'searchFocus.commandkey'
         self.browser.send_shortcut(self.browser.get_entity(key), accel=True)
 
         # TODO: Check that the right input box is focused
         # Located below searchbar as class="autocomplete-textbox textbox-input"
         # Anon locator has not been released yet (bug 1080764)
--- a/testing/firefox-ui/tests/functional/locationbar/test_access_locationbar.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_access_locationbar.py
@@ -8,17 +8,17 @@ from firefox_ui_harness.testcases import
 
 
 class TestAccessLocationBar(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         # Clear complete history so there's no interference from previous entries.
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
 
         self.test_urls = [
             'layout/mozilla_projects.html',
             'layout/mozilla.html',
             'layout/mozilla_mission.html'
         ]
         self.test_urls = [self.marionette.absolute_url(t)
                           for t in self.test_urls]
@@ -29,32 +29,32 @@ class TestAccessLocationBar(FirefoxTestC
 
     def test_access_locationbar_history(self):
 
         # Open some local pages, then about:blank
         def load_urls():
             with self.marionette.using_context('content'):
                 for url in self.test_urls:
                     self.marionette.navigate(url)
-        self.places.wait_for_visited(self.test_urls, load_urls)
+        self.puppeteer.places.wait_for_visited(self.test_urls, load_urls)
         with self.marionette.using_context('content'):
             self.marionette.navigate('about:blank')
 
         # Need to blur url bar or autocomplete won't load - bug 1038614
         self.marionette.execute_script("""arguments[0].blur();""", script_args=[self.urlbar])
 
         # Clear contents of url bar to focus, then arrow down for list of visited sites
         # Verify that autocomplete is open and results are displayed
         self.locationbar.clear()
-        self.urlbar.send_keys(self.keys.ARROW_DOWN)
+        self.urlbar.send_keys(self.puppeteer.keys.ARROW_DOWN)
         Wait(self.marionette).until(lambda _: self.autocomplete_results.is_open)
         Wait(self.marionette).until(lambda _: len(self.autocomplete_results.visible_results) > 1)
 
         # Arrow down again to select first item in list, appearing in reversed order, as loaded.
         # Verify first item.
-        self.urlbar.send_keys(self.keys.ARROW_DOWN)
+        self.urlbar.send_keys(self.puppeteer.keys.ARROW_DOWN)
         Wait(self.marionette).until(lambda _: self.autocomplete_results.selected_index == '0')
         self.assertIn('mission', self.locationbar.value)
 
         # Navigate to the currently selected url
         # Verify it loads by comparing the page url to the test url
-        self.urlbar.send_keys(self.keys.ENTER)
+        self.urlbar.send_keys(self.puppeteer.keys.ENTER)
         self.assertEqual(self.locationbar.value, self.test_urls[-1])
--- a/testing/firefox-ui/tests/functional/locationbar/test_escape_autocomplete.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_escape_autocomplete.py
@@ -8,17 +8,17 @@ from firefox_ui_harness.testcases import
 
 
 class TestEscapeAutocomplete(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         # Clear complete history so there's no interference from previous entries.
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
 
         self.test_urls = [
             'layout/mozilla.html',
             'layout/mozilla_community.html',
         ]
         self.test_urls = [self.marionette.absolute_url(t)
                           for t in self.test_urls]
 
@@ -32,24 +32,24 @@ class TestEscapeAutocomplete(FirefoxTest
         FirefoxTestCase.tearDown(self)
 
     def test_escape_autocomplete(self):
         # Open some local pages
         def load_urls():
             with self.marionette.using_context('content'):
                 for url in self.test_urls:
                     self.marionette.navigate(url)
-        self.places.wait_for_visited(self.test_urls, load_urls)
+        self.puppeteer.places.wait_for_visited(self.test_urls, load_urls)
 
         # Clear the location bar, type the test string, check that autocomplete list opens
         self.locationbar.clear()
         self.locationbar.urlbar.send_keys(self.test_string)
         self.assertEqual(self.locationbar.value, self.test_string)
         Wait(self.marionette).until(lambda _: self.autocomplete_results.is_open)
 
         # Press escape, check location bar value, check autocomplete list closed
-        self.locationbar.urlbar.send_keys(self.keys.ESCAPE)
+        self.locationbar.urlbar.send_keys(self.puppeteer.keys.ESCAPE)
         self.assertEqual(self.locationbar.value, self.test_string)
         Wait(self.marionette).until(lambda _: not self.autocomplete_results.is_open)
 
         # Press escape again and check that locationbar returns to the page url
-        self.locationbar.urlbar.send_keys(self.keys.ESCAPE)
+        self.locationbar.urlbar.send_keys(self.puppeteer.keys.ESCAPE)
         self.assertEqual(self.locationbar.value, self.test_urls[-1])
--- a/testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
@@ -11,20 +11,20 @@ class TestFaviconInAutocomplete(FirefoxT
 
     PREF_SUGGEST_SEARCHES = 'browser.urlbar.suggest.searches'
     PREF_SUGGEST_BOOKMARK = 'browser.urlbar.suggest.bookmark'
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         # Disable suggestions for searches and bookmarks to get results only for history
-        self.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
-        self.prefs.set_pref(self.PREF_SUGGEST_BOOKMARK, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_BOOKMARK, False)
 
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
 
         self.test_urls = [self.marionette.absolute_url('layout/mozilla.html')]
 
         self.test_string = 'mozilla'
         self.test_favicon = 'mozilla_favicon.ico'
 
         self.autocomplete_results = self.browser.navbar.locationbar.autocomplete_results
 
@@ -36,17 +36,17 @@ class TestFaviconInAutocomplete(FirefoxT
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_favicon_in_autocomplete(self):
         # Open the test page
         def load_urls():
             with self.marionette.using_context('content'):
                 self.marionette.navigate(self.test_urls[0])
-        self.places.wait_for_visited(self.test_urls, load_urls)
+        self.puppeteer.places.wait_for_visited(self.test_urls, load_urls)
 
         locationbar = self.browser.navbar.locationbar
 
         # Clear the location bar, type the test string, check that autocomplete list opens
         locationbar.clear()
         locationbar.urlbar.send_keys(self.test_string)
         self.assertEqual(locationbar.value, self.test_string)
         Wait(self.marionette).until(lambda _: self.autocomplete_results.is_complete)
--- a/testing/firefox-ui/tests/functional/locationbar/test_suggest_bookmarks.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_suggest_bookmarks.py
@@ -17,48 +17,48 @@ class TestStarInAutocomplete(FirefoxTest
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         self.bookmark_panel = None
         self.test_urls = [self.marionette.absolute_url('layout/mozilla_grants.html')]
 
         # Disable search suggestions to only get results for history and bookmarks
-        self.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
 
         with self.marionette.using_context('content'):
             self.marionette.navigate('about:blank')
 
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
 
     def tearDown(self):
         # Close the autocomplete results
         try:
             if self.bookmark_panel:
                 self.marionette.execute_script("""
                   arguments[0].hidePopup();
                 """, script_args=[self.bookmark_panel])
 
             self.browser.navbar.locationbar.autocomplete_results.close()
-            self.places.restore_default_bookmarks()
+            self.puppeteer.places.restore_default_bookmarks()
             self.marionette.clear_pref(self.PREF_SUGGEST_SEARCHES)
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_star_in_autocomplete(self):
         search_string = 'grants'
 
         def visit_urls():
             with self.marionette.using_context('content'):
                 for url in self.test_urls:
                     self.marionette.navigate(url)
 
         # Navigate to all the urls specified in self.test_urls and wait for them to
         # be registered as visited
-        self.places.wait_for_visited(self.test_urls, visit_urls)
+        self.puppeteer.places.wait_for_visited(self.test_urls, visit_urls)
 
         # Bookmark the current page using the bookmark menu
         self.browser.menubar.select_by_id('bookmarksMenu',
                                           'menu_bookmarkThisPage')
 
         # TODO: Replace hard-coded selector with library method when one is available
         self.bookmark_panel = self.marionette.find_element(By.ID, 'editBookmarkPanel')
         done_button = self.marionette.find_element(By.ID, 'editBookmarkPanelDoneButton')
@@ -66,17 +66,17 @@ class TestStarInAutocomplete(FirefoxTest
         Wait(self.marionette).until(
             lambda mn: self.bookmark_panel.get_attribute('panelopen') == 'true')
         done_button.click()
 
         # We must open the blank page so the autocomplete result isn't "Switch to tab"
         with self.marionette.using_context('content'):
             self.marionette.navigate('about:blank')
 
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
 
         # Focus the locationbar, delete any contents there, and type the search string
         locationbar = self.browser.navbar.locationbar
         locationbar.clear()
         locationbar.urlbar.send_keys(search_string)
         autocomplete_results = locationbar.autocomplete_results
 
         # Wait for the search string to be present, for the autocomplete results to appear
--- a/testing/firefox-ui/tests/functional/private_browsing/test_about_private_browsing.py
+++ b/testing/firefox-ui/tests/functional/private_browsing/test_about_private_browsing.py
@@ -11,17 +11,17 @@ from firefox_puppeteer.ui.browser.window
 
 class TestAboutPrivateBrowsing(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         # Use a fake local support URL
         support_url = 'about:blank?'
-        self.prefs.set_pref('app.support.baseURL', support_url)
+        self.puppeteer.prefs.set_pref('app.support.baseURL', support_url)
 
         self.pb_url = support_url + 'private-browsing'
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('app.support.baseURL')
         finally:
             FirefoxTestCase.tearDown(self)
--- a/testing/firefox-ui/tests/functional/security/test_dv_certificate.py
+++ b/testing/firefox-ui/tests/functional/security/test_dv_certificate.py
@@ -16,17 +16,17 @@ class TestDVCertificate(FirefoxTestCase)
         self.identity_popup = self.browser.navbar.locationbar.identity_popup
 
         self.url = 'https://ssl-dv.mozqa.com'
 
     def tearDown(self):
         try:
             self.browser.switch_to()
             self.identity_popup.close(force=True)
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_dv_cert(self):
         with self.marionette.using_context('content'):
             self.marionette.navigate(self.url)
 
         # The lock icon should be shown
--- a/testing/firefox-ui/tests/functional/security/test_ev_certificate.py
+++ b/testing/firefox-ui/tests/functional/security/test_ev_certificate.py
@@ -16,17 +16,17 @@ class TestEVCertificate(FirefoxTestCase)
         self.identity_popup = self.locationbar.identity_popup
 
         self.url = 'https://ssl-ev.mozqa.com/'
 
     def tearDown(self):
         try:
             self.browser.switch_to()
             self.identity_popup.close(force=True)
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_ev_certificate(self):
         with self.marionette.using_context('content'):
             self.marionette.navigate(self.url)
 
         # The lock icon should be shown
@@ -34,17 +34,17 @@ class TestEVCertificate(FirefoxTestCase)
                       self.locationbar.connection_icon.value_of_css_property('list-style-image'))
 
         # Check the identity box
         self.assertEqual(self.locationbar.identity_box.get_attribute('className'),
                          'verifiedIdentity')
 
         # Get the information from the certificate
         cert = self.browser.tabbar.selected_tab.certificate
-        address = self.security.get_address_from_certificate(cert)
+        address = self.puppeteer.security.get_address_from_certificate(cert)
 
         # Check the identity popup label displays
         self.assertEqual(self.locationbar.identity_organization_label.get_attribute('value'),
                          cert['organization'])
         self.assertEqual(self.locationbar.identity_country_label.get_attribute('value'),
                          '(' + address['country'] + ')')
 
         # Open the identity popup
--- a/testing/firefox-ui/tests/functional/security/test_no_certificate.py
+++ b/testing/firefox-ui/tests/functional/security/test_no_certificate.py
@@ -18,17 +18,17 @@ class TestNoCertificate(FirefoxTestCase)
         self.identity_popup = self.locationbar.identity_popup
 
         self.url = self.marionette.absolute_url('layout/mozilla.html')
 
     def tearDown(self):
         try:
             self.browser.switch_to()
             self.identity_popup.close(force=True)
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_no_certificate(self):
         with self.marionette.using_context('content'):
             self.marionette.navigate(self.url)
 
         # Check the favicon
--- a/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
+++ b/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
@@ -32,31 +32,31 @@ class TestSafeBrowsingNotificationBar(Fi
             # Malware URL object
             {
                 'button_property': 'safebrowsing.notAnAttackButton.label',
                 'report_page': 'stopbadware.org',
                 'unsafe_page': 'https://www.itisatrap.org/firefox/its-an-attack.html'
             }
         ]
 
-        self.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
-        self.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
 
         # Give the browser a little time, because SafeBrowsing.jsm takes a while
         # between start up and adding the example urls to the db.
         # hg.mozilla.org/mozilla-central/file/46aebcd9481e/browser/base/content/browser.js#l1194
         time.sleep(3)
 
         # TODO: Bug 1139544: While we don't have a reliable way to close the safe browsing
         # notification bar when a test fails, run this test in a new tab.
         self.browser.tabbar.open_tab()
 
     def tearDown(self):
         try:
-            self.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
+            self.puppeteer.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
             self.marionette.clear_pref('browser.safebrowsing.phishing.enabled')
             self.marionette.clear_pref('browser.safebrowsing.malware.enabled')
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_notification_bar(self):
         with self.marionette.using_context('content'):
@@ -96,17 +96,17 @@ class TestSafeBrowsingNotificationBar(Fi
 
         Wait(self.marionette, timeout=self.browser.timeout_page_load).until(
             expected.element_present(By.ID, 'main-feature'),
             message='Expected target element "#main-feature" has not been found',
         )
         self.assertEquals(self.marionette.get_url(), self.browser.get_final_url(unsafe_page))
 
         # Clean up here since the permission gets set in this function
-        self.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
+        self.puppeteer.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
 
     # Check the not a forgery or attack button in the notification bar
     def check_not_badware_button(self, button_property, report_page):
         with self.marionette.using_context('chrome'):
             # TODO: update to use safe browsing notification bar class when bug 1139544 lands
             label = self.browser.get_property(button_property)
             button = (self.marionette.find_element(By.ID, 'content')
                       .find_element('anon attribute', {'label': label}))
--- a/testing/firefox-ui/tests/functional/security/test_safe_browsing_warning_pages.py
+++ b/testing/firefox-ui/tests/functional/security/test_safe_browsing_warning_pages.py
@@ -18,31 +18,31 @@ class TestSafeBrowsingWarningPages(Firef
             # Unwanted software URL
             'https://www.itisatrap.org/firefox/unwanted.html',
             # Phishing URL
             'https://www.itisatrap.org/firefox/its-a-trap.html',
             # Malware URL
             'https://www.itisatrap.org/firefox/its-an-attack.html'
         ]
 
-        self.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
-        self.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
 
         # Give the browser a little time, because SafeBrowsing.jsm takes a
         # while between start up and adding the example urls to the db.
         # hg.mozilla.org/mozilla-central/file/46aebcd9481e/browser/base/content/browser.js#l1194
         time.sleep(3)
 
         # TODO: Bug 1139544: While we don't have a reliable way to close the safe browsing
         # notification bar when a test fails, run this test in a new tab.
         self.browser.tabbar.open_tab()
 
     def tearDown(self):
         try:
-            self.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
+            self.puppeteer.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
             self.marionette.clear_pref('browser.safebrowsing.malware.enabled')
             self.marionette.clear_pref('browser.safebrowsing.phishing.enabled')
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_warning_pages(self):
         with self.marionette.using_context("content"):
@@ -103,9 +103,9 @@ class TestSafeBrowsingWarningPages(Firef
         button = self.marionette.find_element(By.ID, 'ignoreWarningButton')
         button.click()
 
         Wait(self.marionette, timeout=self.browser.timeout_page_load).until(
             expected.element_present(By.ID, 'main-feature'))
         self.assertEquals(self.marionette.get_url(), self.browser.get_final_url(unsafe_page))
 
         # Clean up by removing safe browsing permission for unsafe page
-        self.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
+        self.puppeteer.utils.permissions.remove('https://www.itisatrap.org', 'safe-browsing')
--- a/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
+++ b/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
@@ -12,23 +12,23 @@ from firefox_ui_harness.testcases import
 
 class TestSSLDisabledErrorPage(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         self.url = 'https://tlsv1-0.mozqa.com'
 
-        self.utils.sanitize({"sessions": True})
+        self.puppeteer.utils.sanitize({"sessions": True})
 
         # Disable SSL 3.0, TLS 1.0 and TLS 1.1 for secure connections
         # by forcing the use of TLS 1.2
         # see: http://kb.mozillazine.org/Security.tls.version.*#Possible_values_and_their_effects
-        self.prefs.set_pref('security.tls.version.min', 3)
-        self.prefs.set_pref('security.tls.version.max', 3)
+        self.puppeteer.prefs.set_pref('security.tls.version.min', 3)
+        self.puppeteer.prefs.set_pref('security.tls.version.max', 3)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('security.tls.version.min')
             self.marionette.clear_pref('security.tls.version.max')
         finally:
             FirefoxTestCase.tearDown(self)
 
--- a/testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
+++ b/testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
@@ -27,24 +27,24 @@ class TestSSLStatusAfterRestart(FirefoxT
             {
                 'url': 'https://ssl-ov.mozqa.com/',
                 'identity': '',
                 'type': 'secure'
             }
         )
 
         # Set browser to restore previous session
-        self.prefs.set_pref('browser.startup.page', 3)
+        self.puppeteer.prefs.set_pref('browser.startup.page', 3)
 
         self.locationbar = self.browser.navbar.locationbar
         self.identity_popup = self.locationbar.identity_popup
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
             self.browser.switch_to()
             self.identity_popup.close(force=True)
             self.marionette.clear_pref('browser.startup.page')
         finally:
             FirefoxTestCase.tearDown(self)
 
     @skip_if_e10s
@@ -96,17 +96,17 @@ class TestSSLStatusAfterRestart(FirefoxT
             lambda _: self.identity_popup.view.security.more_info_button.click())
 
         # Verify that the current panel is the security panel
         self.assertEqual(page_info.deck.selected_panel, page_info.deck.security)
 
         # Verify the domain listed on the security panel
         # If this is a wildcard cert, check only the domain
         if cert['commonName'].startswith('*'):
-            self.assertIn(self.security.get_domain_from_common_name(cert['commonName']),
+            self.assertIn(self.puppeteer.security.get_domain_from_common_name(cert['commonName']),
                           page_info.deck.security.domain.get_attribute('value'),
                           'Expected domain found in certificate for ' + url)
         else:
             self.assertEqual(page_info.deck.security.domain.get_attribute('value'),
                              cert['commonName'],
                              'Domain value matches certificate common name.')
 
         # Verify the owner listed on the security panel
--- a/testing/firefox-ui/tests/functional/security/test_submit_unencrypted_info_warning.py
+++ b/testing/firefox-ui/tests/functional/security/test_submit_unencrypted_info_warning.py
@@ -13,17 +13,17 @@ from firefox_ui_harness.testcases import
 class TestSubmitUnencryptedInfoWarning(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         self.url = 'https://ssl-dv.mozqa.com/data/firefox/security/unencryptedsearch.html'
         self.test_string = 'mozilla'
 
-        self.prefs.set_pref('security.warn_submit_insecure', True)
+        self.puppeteer.prefs.set_pref('security.warn_submit_insecure', True)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('security.warn_submit_insecure')
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_submit_unencrypted_info_warning(self):
--- a/testing/firefox-ui/tests/functional/sessionstore/test_restore_windows_after_restart.py
+++ b/testing/firefox-ui/tests/functional/sessionstore/test_restore_windows_after_restart.py
@@ -59,17 +59,17 @@ class TestRestoreWindowsAfterRestart(Fir
         the browser, and then ensures that the standard tabs have been
         restored, and that the private ones have not.
         """
         self.open_windows(self.test_windows)
         self.open_windows(self.private_windows, is_private=True)
 
         self.restart()
 
-        windows = self.windows.all
+        windows = self.puppeteer.windows.all
 
         # There's no guarantee that Marionette will return us an
         # iterator for the opened windows that will match the
         # order within our window list. Instead, we'll convert
         # the list of URLs within each open window to a set of
         # tuples that will allow us to do a direct comparison
         # while allowing the windows to be in any order.
         opened_windows = set()
--- a/testing/firefox-ui/tests/puppeteer/test_about_window.py
+++ b/testing/firefox-ui/tests/puppeteer/test_about_window.py
@@ -10,17 +10,17 @@ class TestAboutWindow(FirefoxTestCase):
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         self.about_window = self.browser.open_about_window()
         self.deck = self.about_window.deck
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_basic(self):
         self.assertEqual(self.about_window.window_type, 'Browser:About')
 
     def test_elements(self):
         """Test correct retrieval of elements."""
@@ -59,10 +59,10 @@ class TestAboutWindow(FirefoxTestCase):
 
         open_strategies = ('menu',
                            opener,
                            )
 
         self.about_window.close()
         for trigger in open_strategies:
             about_window = self.browser.open_about_window(trigger=trigger)
-            self.assertEquals(about_window, self.windows.current)
+            self.assertEquals(about_window, self.puppeteer.windows.current)
             about_window.close()
--- a/testing/firefox-ui/tests/puppeteer/test_appinfo.py
+++ b/testing/firefox-ui/tests/puppeteer/test_appinfo.py
@@ -7,22 +7,23 @@ from firefox_ui_harness.testcases import
 
 
 class TestAppInfo(FirefoxTestCase):
 
     def test_valid_properties(self):
         binary = self.marionette.bin
         version_info = mozversion.get_version(binary=binary)
 
-        self.assertEqual(self.appinfo.ID, version_info['application_id'])
-        self.assertEqual(self.appinfo.name, version_info['application_name'])
-        self.assertEqual(self.appinfo.vendor, version_info['application_vendor'])
-        self.assertEqual(self.appinfo.version, version_info['application_version'])
+        self.assertEqual(self.puppeteer.appinfo.ID, version_info['application_id'])
+        self.assertEqual(self.puppeteer.appinfo.name, version_info['application_name'])
+        self.assertEqual(self.puppeteer.appinfo.vendor, version_info['application_vendor'])
+        self.assertEqual(self.puppeteer.appinfo.version, version_info['application_version'])
         # Bug 1298328 - Platform buildid mismatch due to incremental builds
-        # self.assertEqual(self.appinfo.platformBuildID, version_info['platform_buildid'])
-        self.assertEqual(self.appinfo.platformVersion, version_info['platform_version'])
-        self.assertIsNotNone(self.appinfo.locale)
-        self.assertIsNotNone(self.appinfo.user_agent)
-        self.assertIsNotNone(self.appinfo.XPCOMABI)
+        # self.assertEqual(self.puppeteer.appinfo.platformBuildID,
+        #                  version_info['platform_buildid'])
+        self.assertEqual(self.puppeteer.appinfo.platformVersion, version_info['platform_version'])
+        self.assertIsNotNone(self.puppeteer.appinfo.locale)
+        self.assertIsNotNone(self.puppeteer.appinfo.user_agent)
+        self.assertIsNotNone(self.puppeteer.appinfo.XPCOMABI)
 
     def test_invalid_properties(self):
         with self.assertRaises(AttributeError):
-            self.appinfo.unknown
+            self.puppeteer.appinfo.unknown
--- a/testing/firefox-ui/tests/puppeteer/test_l10n.py
+++ b/testing/firefox-ui/tests/puppeteer/test_l10n.py
@@ -8,17 +8,17 @@ from marionette_driver.errors import Mar
 from firefox_puppeteer.api.l10n import L10n
 from firefox_ui_harness.testcases import FirefoxTestCase
 
 
 class TestL10n(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
-        self.l10n = L10n(lambda: self.marionette)
+        self.l10n = L10n(self.marionette)
 
     def tearDown(self):
         FirefoxTestCase.tearDown(self)
 
     def test_dtd_entity_chrome(self):
         dtds = ['chrome://global/locale/about.dtd',
                 'chrome://browser/locale/baseMenuOverlay.dtd']
 
--- a/testing/firefox-ui/tests/puppeteer/test_notifications.py
+++ b/testing/firefox-ui/tests/puppeteer/test_notifications.py
@@ -12,27 +12,27 @@ from firefox_puppeteer.ui.browser.notifi
 )
 
 
 class TestNotifications(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
-        self.prefs.set_pref('extensions.install.requireSecureOrigin', False)
+        self.puppeteer.prefs.set_pref('extensions.install.requireSecureOrigin', False)
 
         self.addons_url = self.marionette.absolute_url('addons/extensions/')
-        self.utils.permissions.add(self.marionette.baseurl, 'install')
+        self.puppeteer.utils.permissions.add(self.marionette.baseurl, 'install')
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('extensions.install.requireSecureOrigin')
             self.marionette.clear_pref('xpinstall.signatures.required')
 
-            self.utils.permissions.remove(self.addons_url, 'install')
+            self.puppeteer.utils.permissions.remove(self.addons_url, 'install')
 
             if self.browser.notification:
                 self.browser.notification.close(force=True)
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_open_close_notification(self):
         """Trigger and dismiss a notification"""
@@ -64,17 +64,17 @@ class TestNotifications(FirefoxTestCase)
         """Trigger a notification with an origin"""
         self.trigger_addon_notification('restartless_addon_signed.xpi')
         self.assertIn(self.browser.notification.origin, self.marionette.baseurl)
         self.assertIsNotNone(self.browser.notification.label)
 
     def test_addon_install_failed_notification(self):
         """Trigger add-on blocked notification using an unsigned add-on"""
         # Ensure that installing unsigned extensions will fail
-        self.prefs.set_pref('xpinstall.signatures.required', True)
+        self.puppeteer.prefs.set_pref('xpinstall.signatures.required', True)
 
         self.trigger_addon_notification(
             'restartless_addon_unsigned.xpi',
             notification=AddOnInstallFailedNotification)
 
     def trigger_addon_notification(self, addon, notification=AddOnInstallConfirmationNotification):
         with self.marionette.using_context('content'):
             self.marionette.navigate(self.addons_url)
--- a/testing/firefox-ui/tests/puppeteer/test_page_info_window.py
+++ b/testing/firefox-ui/tests/puppeteer/test_page_info_window.py
@@ -4,17 +4,17 @@
 
 from firefox_ui_harness.testcases import FirefoxTestCase
 
 
 class TestPageInfoWindow(FirefoxTestCase):
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_elements(self):
         """Test correct retrieval of elements."""
         page_info = self.browser.open_page_info_window()
 
         self.assertNotEqual(page_info.dtds, [])
@@ -63,36 +63,36 @@ class TestPageInfoWindow(FirefoxTestCase
             self.browser.menubar.select_by_id('tools-menu', 'menu_pageInfo')
 
         open_strategies = ('menu',
                            'shortcut',
                            opener,
                            )
 
         for trigger in open_strategies:
-            if trigger == 'shortcut' and self.platform == 'windows_nt':
+            if trigger == 'shortcut' and self.puppeteer.platform == 'windows_nt':
                 # The shortcut for page info window does not exist on windows.
                 self.assertRaises(ValueError, self.browser.open_page_info_window,
                                   trigger=trigger)
                 continue
 
             page_info = self.browser.open_page_info_window(trigger=trigger)
-            self.assertEquals(page_info, self.windows.current)
+            self.assertEquals(page_info, self.puppeteer.windows.current)
             page_info.close()
 
     def test_close_window(self):
         """Test various closing strategies."""
         def closer(win):
             win.send_shortcut(win.get_entity('closeWindow.key'), accel=True)
 
         # Close a tab by each trigger method
         close_strategies = ('menu',
                             'shortcut',
                             closer,
                             )
         for trigger in close_strategies:
             # menu only works on OS X
-            if trigger == 'menu' and self.platform != 'darwin':
+            if trigger == 'menu' and self.puppeteer.platform != 'darwin':
                 continue
 
             page_info = self.browser.open_page_info_window()
             page_info.close(trigger=trigger)
             self.assertTrue(page_info.closed)
--- a/testing/firefox-ui/tests/puppeteer/test_places.py
+++ b/testing/firefox-ui/tests/puppeteer/test_places.py
@@ -13,18 +13,18 @@ class TestPlaces(FirefoxTestCase):
         FirefoxTestCase.setUp(self)
 
         self.urls = [self.marionette.absolute_url('layout/mozilla_governance.html'),
                      self.marionette.absolute_url('layout/mozilla_grants.html'),
                      ]
 
     def tearDown(self):
         try:
-            self.places.restore_default_bookmarks()
-            self.places.remove_all_history()
+            self.puppeteer.places.restore_default_bookmarks()
+            self.puppeteer.places.remove_all_history()
         finally:
             FirefoxTestCase.tearDown(self)
 
     def get_all_urls_in_history(self):
         return self.marionette.execute_script("""
           let hs = Components.classes["@mozilla.org/browser/nav-history-service;1"]
                    .getService(Components.interfaces.nsINavHistoryService);
           let urls = [];
@@ -39,46 +39,47 @@ class TestPlaces(FirefoxTestCase):
           }
           root.containerOpen = false;
 
           return urls;
         """)
 
     def test_plugins(self):
         # TODO: Once we use a plugin, add a test case to verify that the data will be removed
-        self.places.clear_plugin_data()
+        self.puppeteer.places.clear_plugin_data()
 
     def test_bookmarks(self):
         star_button = self.marionette.find_element(By.ID, 'bookmarks-menu-button')
 
         # Visit URLs and bookmark them all
         for url in self.urls:
             with self.marionette.using_context('content'):
                 self.marionette.navigate(url)
 
-            Wait(self.marionette).until(lambda _: self.places.is_bookmark_star_button_ready())
+            Wait(self.marionette).until(
+                lambda _: self.puppeteer.places.is_bookmark_star_button_ready())
             star_button.click()
-            Wait(self.marionette).until(lambda _: self.places.is_bookmarked(url))
+            Wait(self.marionette).until(lambda _: self.puppeteer.places.is_bookmarked(url))
 
-            ids = self.places.get_folder_ids_for_url(url)
+            ids = self.puppeteer.places.get_folder_ids_for_url(url)
             self.assertEqual(len(ids), 1)
-            self.assertEqual(ids[0], self.places.bookmark_folders.unfiled)
+            self.assertEqual(ids[0], self.puppeteer.places.bookmark_folders.unfiled)
 
         # Restore default bookmarks, so the added URLs are gone
-        self.places.restore_default_bookmarks()
+        self.puppeteer.places.restore_default_bookmarks()
         for url in self.urls:
-            self.assertFalse(self.places.is_bookmarked(url))
+            self.assertFalse(self.puppeteer.places.is_bookmarked(url))
 
     def test_history(self):
         self.assertEqual(len(self.get_all_urls_in_history()), 0)
 
         # Visit pages and check that they are all present
         def load_urls():
             with self.marionette.using_context('content'):
                 for url in self.urls:
                     self.marionette.navigate(url)
-        self.places.wait_for_visited(self.urls, load_urls)
+        self.puppeteer.places.wait_for_visited(self.urls, load_urls)
 
         self.assertEqual(self.get_all_urls_in_history(), self.urls)
 
         # Check that both pages are no longer in the remove_all_history
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
         self.assertEqual(len(self.get_all_urls_in_history()), 0)
--- a/testing/firefox-ui/tests/puppeteer/test_prefs.py
+++ b/testing/firefox-ui/tests/puppeteer/test_prefs.py
@@ -26,121 +26,121 @@ class testPreferences(FirefoxTestCase):
             self.marionette.clear_pref('browser.tabs.loadBookmarksInBackground')
             self.marionette.clear_pref('browser.tabs.maxOpenBeforeWarn')
             self.marionette.clear_pref('browser.startup.homepage')
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_get_pref(self):
         # check correct types
-        self.assertTrue(isinstance(self.prefs.get_pref(self.bool_pref),
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.bool_pref),
                                    bool))
-        self.assertTrue(isinstance(self.prefs.get_pref(self.int_pref),
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.int_pref),
                                    int))
-        self.assertTrue(isinstance(self.prefs.get_pref(self.string_pref),
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.string_pref),
                                    basestring))
 
         # unknown
-        self.assertIsNone(self.prefs.get_pref(self.unknown_pref))
+        self.assertIsNone(self.puppeteer.prefs.get_pref(self.unknown_pref))
 
         # default branch
-        orig_value = self.prefs.get_pref(self.int_pref)
-        self.prefs.set_pref(self.int_pref, 99999)
-        self.assertEqual(self.prefs.get_pref(self.int_pref), 99999)
-        self.assertEqual(self.prefs.get_pref(self.int_pref, True), orig_value)
+        orig_value = self.puppeteer.prefs.get_pref(self.int_pref)
+        self.puppeteer.prefs.set_pref(self.int_pref, 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref, True), orig_value)
 
         # complex value
         properties_file = 'chrome://branding/locale/browserconfig.properties'
-        self.assertEqual(self.prefs.get_pref('browser.startup.homepage'),
+        self.assertEqual(self.puppeteer.prefs.get_pref('browser.startup.homepage'),
                          properties_file)
 
-        value = self.prefs.get_pref('browser.startup.homepage',
-                                    interface='nsIPrefLocalizedString')
+        value = self.puppeteer.prefs.get_pref('browser.startup.homepage',
+                                              interface='nsIPrefLocalizedString')
         self.assertNotEqual(value, properties_file)
 
     def test_set_pref_casted_values(self):
         # basestring as boolean
-        self.prefs.set_pref(self.bool_pref, '')
-        self.assertFalse(self.prefs.get_pref(self.bool_pref))
+        self.puppeteer.prefs.set_pref(self.bool_pref, '')
+        self.assertFalse(self.puppeteer.prefs.get_pref(self.bool_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.bool_pref)
 
-        self.prefs.set_pref(self.bool_pref, 'unittest')
-        self.assertTrue(self.prefs.get_pref(self.bool_pref))
+        self.puppeteer.prefs.set_pref(self.bool_pref, 'unittest')
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.bool_pref)
 
         # int as boolean
-        self.prefs.set_pref(self.bool_pref, 0)
-        self.assertFalse(self.prefs.get_pref(self.bool_pref))
+        self.puppeteer.prefs.set_pref(self.bool_pref, 0)
+        self.assertFalse(self.puppeteer.prefs.get_pref(self.bool_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.bool_pref)
 
-        self.prefs.set_pref(self.bool_pref, 5)
-        self.assertTrue(self.prefs.get_pref(self.bool_pref))
+        self.puppeteer.prefs.set_pref(self.bool_pref, 5)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.bool_pref)
 
         # boolean as int
-        self.prefs.set_pref(self.int_pref, False)
-        self.assertEqual(self.prefs.get_pref(self.int_pref), 0)
+        self.puppeteer.prefs.set_pref(self.int_pref, False)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 0)
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.int_pref)
 
-        self.prefs.set_pref(self.int_pref, True)
-        self.assertEqual(self.prefs.get_pref(self.int_pref), 1)
+        self.puppeteer.prefs.set_pref(self.int_pref, True)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 1)
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.int_pref)
 
         # int as string
-        self.prefs.set_pref(self.string_pref, 54)
-        self.assertEqual(self.prefs.get_pref(self.string_pref), '54')
+        self.puppeteer.prefs.set_pref(self.string_pref, 54)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), '54')
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.string_pref)
 
     def test_set_pref_invalid(self):
         self.assertRaises(AssertionError,
-                          self.prefs.set_pref, self.new_pref, None)
+                          self.puppeteer.prefs.set_pref, self.new_pref, None)
 
     def test_set_pref_new_preference(self):
-        self.prefs.set_pref(self.new_pref, True)
-        self.assertTrue(self.prefs.get_pref(self.new_pref))
+        self.puppeteer.prefs.set_pref(self.new_pref, True)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.new_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.new_pref)
 
-        self.prefs.set_pref(self.new_pref, 5)
-        self.assertEqual(self.prefs.get_pref(self.new_pref), 5)
+        self.puppeteer.prefs.set_pref(self.new_pref, 5)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.new_pref), 5)
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.new_pref)
 
-        self.prefs.set_pref(self.new_pref, 'test')
-        self.assertEqual(self.prefs.get_pref(self.new_pref), 'test')
+        self.puppeteer.prefs.set_pref(self.new_pref, 'test')
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.new_pref), 'test')
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.new_pref)
 
     def test_set_pref_new_values(self):
-        self.prefs.set_pref(self.bool_pref, True)
-        self.assertTrue(self.prefs.get_pref(self.bool_pref))
+        self.puppeteer.prefs.set_pref(self.bool_pref, True)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.bool_pref)
 
-        self.prefs.set_pref(self.int_pref, 99999)
-        self.assertEqual(self.prefs.get_pref(self.int_pref), 99999)
+        self.puppeteer.prefs.set_pref(self.int_pref, 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 99999)
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.int_pref)
 
-        self.prefs.set_pref(self.string_pref, 'test_string')
-        self.assertEqual(self.prefs.get_pref(self.string_pref), 'test_string')
+        self.puppeteer.prefs.set_pref(self.string_pref, 'test_string')
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), 'test_string')
         # Remove when all self.marionette methods are implemented
         # Please see Bug 1293588
         self.marionette.clear_pref(self.string_pref)
--- a/testing/firefox-ui/tests/puppeteer/test_security.py
+++ b/testing/firefox-ui/tests/puppeteer/test_security.py
@@ -15,17 +15,17 @@ class TestSecurity(FirefoxTestCase):
         with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
             self.marionette.navigate(url)
 
         cert = self.browser.tabbar.tabs[0].certificate
         self.assertIn(cert['commonName'], url)
         self.assertEqual(cert['organization'], 'Mozilla Corporation')
         self.assertEqual(cert['issuerOrganization'], 'DigiCert Inc')
 
-        address = self.security.get_address_from_certificate(cert)
+        address = self.puppeteer.security.get_address_from_certificate(cert)
         self.assertIsNotNone(address)
         self.assertIsNotNone(address['city'])
         self.assertIsNotNone(address['country'])
         self.assertIsNotNone(address['postal_code'])
         self.assertIsNotNone(address['state'])
         self.assertIsNotNone(address['street'])
 
     def test_get_certificate(self):
--- a/testing/firefox-ui/tests/puppeteer/test_software_update.py
+++ b/testing/firefox-ui/tests/puppeteer/test_software_update.py
@@ -8,17 +8,17 @@ from firefox_ui_harness.testcases import
 
 from firefox_puppeteer.api.software_update import SoftwareUpdate
 
 
 class TestSoftwareUpdate(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
-        self.software_update = SoftwareUpdate(lambda: self.marionette)
+        self.software_update = SoftwareUpdate(self.marionette)
 
         self.saved_mar_channels = self.software_update.mar_channels.channels
         self.software_update.mar_channels.channels = set(['expected', 'channels'])
 
     def tearDown(self):
         try:
             self.software_update.mar_channels.channels = self.saved_mar_channels
         finally:
@@ -67,17 +67,17 @@ class TestSoftwareUpdate(FirefoxTestCase
     def test_staging_directory(self):
         self.assertTrue(self.software_update.staging_directory)
 
 
 class TestUpdateChannel(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
-        self.software_update = SoftwareUpdate(lambda: self.marionette)
+        self.software_update = SoftwareUpdate(self.marionette)
 
         self.saved_channel = self.software_update.update_channel.default_channel
         self.software_update.update_channel.default_channel = 'expected_channel'
 
     def tearDown(self):
         try:
             self.software_update.update_channel.default_channel = self.saved_channel
         finally:
@@ -93,17 +93,17 @@ class TestUpdateChannel(FirefoxTestCase)
         self.software_update.update_channel.default_channel = 'new_channel'
         self.assertEqual(self.software_update.update_channel.default_channel, 'new_channel')
 
 
 class TestMARChannels(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
-        self.software_update = SoftwareUpdate(lambda: self.marionette)
+        self.software_update = SoftwareUpdate(self.marionette)
 
         self.saved_mar_channels = self.software_update.mar_channels.channels
         self.software_update.mar_channels.channels = set(['expected', 'channels'])
 
     def tearDown(self):
         try:
             self.software_update.mar_channels.channels = self.saved_mar_channels
         finally:
--- a/testing/firefox-ui/tests/puppeteer/test_toolbars.py
+++ b/testing/firefox-ui/tests/puppeteer/test_toolbars.py
@@ -14,17 +14,17 @@ class TestNavBar(FirefoxTestCase):
         FirefoxTestCase.setUp(self)
 
         self.navbar = self.browser.navbar
         self.url = self.marionette.absolute_url('layout/mozilla.html')
 
         with self.marionette.using_context('content'):
             self.marionette.navigate('about:blank')
 
-        # TODO: check why self.places.remove_all_history() does not work here
+        # TODO: check why self.puppeteer.places.remove_all_history() does not work here
         self.marionette.execute_script("""
             let count = gBrowser.sessionHistory.count;
             gBrowser.sessionHistory.PurgeHistory(count);
         """)
 
     def test_elements(self):
         self.assertEqual(self.navbar.back_button.get_attribute('localName'), 'toolbarbutton')
         self.assertEqual(self.navbar.forward_button.get_attribute('localName'), 'toolbarbutton')
--- a/testing/firefox-ui/tests/puppeteer/test_update_wizard.py
+++ b/testing/firefox-ui/tests/puppeteer/test_update_wizard.py
@@ -19,17 +19,17 @@ class TestUpdateWizard(FirefoxTestCase):
             """)
 
         self.dialog = self.browser.open_window(callback=opener,
                                                expected_window_class=UpdateWizardDialog)
         self.wizard = self.dialog.wizard
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             FirefoxTestCase.tearDown(self)
 
     def test_basic(self):
         self.assertEqual(self.dialog.window_type, 'Update:Wizard')
         self.assertNotEqual(self.dialog.dtds, [])
         self.assertNotEqual(self.dialog.properties, [])
 
--- a/testing/firefox-ui/tests/puppeteer/test_utils.py
+++ b/testing/firefox-ui/tests/puppeteer/test_utils.py
@@ -6,40 +6,40 @@ from firefox_ui_harness.testcases import
 
 
 class TestSanitize(FirefoxTestCase):
 
     def setUp(self):
         FirefoxTestCase.setUp(self)
 
         # Clear all previous history and cookies.
-        self.places.remove_all_history()
+        self.puppeteer.places.remove_all_history()
         self.marionette.delete_all_cookies()
 
         self.urls = [
             'layout/mozilla_projects.html',
             'layout/mozilla.html',
             'layout/mozilla_mission.html',
             'cookies/cookie_single.html'
         ]
         self.urls = [self.marionette.absolute_url(url) for url in self.urls]
 
         # Open the test urls, including the single cookie setting page.
         def load_urls():
             with self.marionette.using_context('content'):
                 for url in self.urls:
                     self.marionette.navigate(url)
-        self.places.wait_for_visited(self.urls, load_urls)
+        self.puppeteer.places.wait_for_visited(self.urls, load_urls)
 
     def tearDown(self):
         FirefoxTestCase.tearDown(self)
 
     def test_sanitize_history(self):
         """ Clears history. """
-        self.assertEqual(self.places.get_all_urls_in_history(), self.urls)
-        self.utils.sanitize(data_type={"history": True})
-        self.assertEqual(self.places.get_all_urls_in_history(), [])
+        self.assertEqual(self.puppeteer.places.get_all_urls_in_history(), self.urls)
+        self.puppeteer.utils.sanitize(data_type={"history": True})
+        self.assertEqual(self.puppeteer.places.get_all_urls_in_history(), [])
 
     def test_sanitize_cookies(self):
         """ Clears cookies. """
         self.assertIsNotNone(self.marionette.get_cookie('litmus_1'))
-        self.utils.sanitize(data_type={"cookies": True})
+        self.puppeteer.utils.sanitize(data_type={"cookies": True})
         self.assertIsNone(self.marionette.get_cookie('litmus_1'))
--- a/testing/firefox-ui/tests/puppeteer/test_windows.py
+++ b/testing/firefox-ui/tests/puppeteer/test_windows.py
@@ -22,75 +22,75 @@ class BaseWindowTestCase(FirefoxTestCase
         doesn't have enough time to get itself all sorted before
         the timeout gets hit, which results in the parent killing
         the content process manually, which generates a crash report,
         which causes these tests to orange. We side-step this by
         setting dom.ipc.tabs.shutdownTimeoutSecs to 0, which disables
         the shutdown timer.
         """
         FirefoxTestCase.setUp(self)
-        self.prefs.set_pref('dom.ipc.tabs.shutdownTimeoutSecs', 0)
+        self.puppeteer.prefs.set_pref('dom.ipc.tabs.shutdownTimeoutSecs', 0)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('dom.ipc.tabs.shutdownTimeoutSecs')
         finally:
             FirefoxTestCase.tearDown(self)
 
 
 class TestWindows(BaseWindowTestCase):
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             BaseWindowTestCase.tearDown(self)
 
     def test_switch_to(self):
         url = self.marionette.absolute_url('layout/mozilla.html')
 
         # Open two more windows
         for index in range(0, 2):
             self.marionette.execute_script(""" window.open(); """)
 
-        windows = self.windows.all
+        windows = self.puppeteer.windows.all
         self.assertEquals(len(windows), 3)
 
         # Switch to the 2nd window
-        self.windows.switch_to(windows[1].handle)
+        self.puppeteer.windows.switch_to(windows[1].handle)
         self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)
 
         # TODO: Needs updated tabs module for improved navigation
         with self.marionette.using_context('content'):
             self.marionette.navigate(url)
 
         # Switch to the last window and find 2nd window by URL
-        self.windows.switch_to(windows[2].handle)
+        self.puppeteer.windows.switch_to(windows[2].handle)
 
         # TODO: A window can have multiple tabs, so this may need an update
         # when the tabs module gets implemented
         def find_by_url(win):
             with win.marionette.using_context('content'):
                 return win.marionette.get_url() == url
 
-        self.windows.switch_to(find_by_url)
+        self.puppeteer.windows.switch_to(find_by_url)
         self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)
 
-        self.windows.switch_to(find_by_url)
+        self.puppeteer.windows.switch_to(find_by_url)
 
         # Switching to an unknown handles has to fail
         self.assertRaises(NoSuchWindowException,
-                          self.windows.switch_to, "humbug")
+                          self.puppeteer.windows.switch_to, "humbug")
         self.assertRaises(NoSuchWindowException,
-                          self.windows.switch_to, lambda win: False)
+                          self.puppeteer.windows.switch_to, lambda win: False)
 
-        self.windows.close_all([self.browser])
+        self.puppeteer.windows.close_all([self.browser])
         self.browser.switch_to()
 
-        self.assertEqual(len(self.windows.all), 1)
+        self.assertEqual(len(self.puppeteer.windows.all), 1)
 
     def test_switch_to_unknown_window_type(self):
         def open_by_js(_):
             with self.marionette.using_context('chrome'):
                 self.marionette.execute_script("""
                   window.open('chrome://browser/content/safeMode.xul', '_blank',
                               'chrome,centerscreen,resizable=no');
                 """)
@@ -99,50 +99,48 @@ class TestWindows(BaseWindowTestCase):
         win.close()
         self.browser.switch_to()
 
 
 class TestBaseWindow(BaseWindowTestCase):
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             BaseWindowTestCase.tearDown(self)
 
     def test_basics(self):
         # force BaseWindow instance
-        win1 = BaseWindow(lambda: self.marionette, self.browser.handle)
+        win1 = BaseWindow(self.marionette, self.browser.handle)
 
         self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
         self.assertEquals(win1.window_element,
                           self.marionette.find_element(By.CSS_SELECTOR, ':root'))
         self.assertEquals(win1.window_element.get_attribute('windowtype'),
                           self.marionette.get_window_type())
         self.assertFalse(win1.closed)
 
         # Test invalid parameters for BaseWindow constructor
-        self.assertRaises(TypeError,
-                          BaseWindow, self.marionette, self.browser.handle)
         self.assertRaises(errors.UnknownWindowError,
-                          BaseWindow, lambda: self.marionette, 10)
+                          BaseWindow, self.marionette, 10)
 
         # Test invalid shortcuts
         self.assertRaises(KeyError,
                           win1.send_shortcut, 'l', acel=True)
 
     def test_open_close(self):
         # force BaseWindow instance
-        win1 = BaseWindow(lambda: self.marionette, self.browser.handle)
+        win1 = BaseWindow(self.marionette, self.browser.handle)
 
         # Open a new window (will be focused), and check states
         win2 = win1.open_window()
 
         # force BaseWindow instance
-        win2 = BaseWindow(lambda: self.marionette, win2.handle)
+        win2 = BaseWindow(self.marionette, win2.handle)
 
         self.assertEquals(len(self.marionette.chrome_window_handles), 2)
         self.assertNotEquals(win1.handle, win2.handle)
         self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
 
         win2.close()
 
         self.assertTrue(win2.closed)
@@ -157,103 +155,103 @@ class TestBaseWindow(BaseWindowTestCase)
             window.marionette.execute_script(""" window.open(); """)
 
         def closer(window):
             window.marionette.execute_script(""" window.close(); """)
 
         win2 = win1.open_window(callback=opener)
 
         # force BaseWindow instance
-        win2 = BaseWindow(lambda: self.marionette, win2.handle)
+        win2 = BaseWindow(self.marionette, win2.handle)
 
         self.assertEquals(len(self.marionette.chrome_window_handles), 2)
         win2.close(callback=closer)
 
         win1.focus()
 
         # Check for an unexpected window class
         self.assertRaises(errors.UnexpectedWindowTypeError,
                           win1.open_window, expected_window_class=BaseWindow)
-        self.windows.close_all([win1])
+        self.puppeteer.windows.close_all([win1])
 
     def test_switch_to_and_focus(self):
         # force BaseWindow instance
-        win1 = BaseWindow(lambda: self.marionette, self.browser.handle)
+        win1 = BaseWindow(self.marionette, self.browser.handle)
 
         # Open a new window (will be focused), and check states
         win2 = win1.open_window()
 
         # force BaseWindow instance
-        win2 = BaseWindow(lambda: self.marionette, win2.handle)
+        win2 = BaseWindow(self.marionette, win2.handle)
 
         self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
-        self.assertEquals(win2.handle, self.windows.focused_chrome_window_handle)
+        self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
         self.assertFalse(win1.focused)
         self.assertTrue(win2.focused)
 
         # Switch back to win1 without moving the focus, but focus separately
         win1.switch_to()
         self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
         self.assertTrue(win2.focused)
 
         win1.focus()
         self.assertTrue(win1.focused)
 
         # Switch back to win2 by focusing it directly
         win2.focus()
         self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
-        self.assertEquals(win2.handle, self.windows.focused_chrome_window_handle)
+        self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
         self.assertTrue(win2.focused)
 
         # Close win2, and check that it keeps active but looses focus
         win2.switch_to()
         win2.close()
 
         win1.switch_to()
 
 
 class TestBrowserWindow(BaseWindowTestCase):
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             BaseWindowTestCase.tearDown(self)
 
     def test_basic(self):
         self.assertNotEqual(self.browser.dtds, [])
         self.assertNotEqual(self.browser.properties, [])
 
         self.assertFalse(self.browser.is_private)
 
         self.assertIsNotNone(self.browser.menubar)
         self.assertIsNotNone(self.browser.navbar)
         self.assertIsNotNone(self.browser.tabbar)
 
     def test_open_close(self):
         # open and close a new browser windows by menu
         win2 = self.browser.open_browser(trigger='menu')
-        self.assertEquals(win2, self.windows.current)
+        self.assertEquals(win2, self.puppeteer.windows.current)
         self.assertFalse(self.browser.is_private)
         win2.close(trigger='menu')
 
         # open and close a new browser window by shortcut
         win2 = self.browser.open_browser(trigger='shortcut')
-        self.assertEquals(win2, self.windows.current)
+        self.assertEquals(win2, self.puppeteer.windows.current)
         self.assertFalse(self.browser.is_private)
         win2.close(trigger='shortcut')
 
         # open and close a new private browsing window
         win2 = self.browser.open_browser(is_private=True)
-        self.assertEquals(win2, self.windows.current)
+        self.assertEquals(win2, self.puppeteer.windows.current)
         self.assertTrue(win2.is_private)
         win2.close()
 
         # open and close a new private browsing window
         win2 = self.browser.open_browser(trigger='shortcut', is_private=True)
-        self.assertEquals(win2, self.windows.current)
+        self.assertEquals(win2, self.puppeteer.windows.current)
         self.assertTrue(win2.is_private)
         win2.close()
 
         # force closing a window
         win2 = self.browser.open_browser()
-        self.assertEquals(win2, self.windows.current)
+        self.assertEquals(win2, self.puppeteer.windows.current)
         win2.close(force=True)
--- a/testing/firefox-ui/tests/update/direct/test_direct_update.py
+++ b/testing/firefox-ui/tests/update/direct/test_direct_update.py
@@ -7,15 +7,15 @@ from firefox_ui_harness.testcases import
 
 class TestDirectUpdate(UpdateTestCase):
 
     def setUp(self):
         UpdateTestCase.setUp(self, is_fallback=False)
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             UpdateTestCase.tearDown(self)
 
     def test_update(self):
         self.download_and_apply_available_update(force_fallback=False)
         self.check_update_applied()
--- a/testing/firefox-ui/tests/update/fallback/test_fallback_update.py
+++ b/testing/firefox-ui/tests/update/fallback/test_fallback_update.py
@@ -7,16 +7,16 @@ from firefox_ui_harness.testcases import
 
 class TestFallbackUpdate(UpdateTestCase):
 
     def setUp(self):
         UpdateTestCase.setUp(self, is_fallback=True)
 
     def tearDown(self):
         try:
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
         finally:
             UpdateTestCase.tearDown(self)
 
     def test_update(self):
         self.download_and_apply_available_update(force_fallback=True)
         self.download_and_apply_forced_update()
         self.check_update_applied()
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -1198,17 +1198,17 @@ or run without that action (ie: --no-{ac
             if changes:
                 comments = changes[0].get('comments', '')
                 self.set_buildbot_property('comments',
                                            comments,
                                            write_to_file=True)
             else:
                 self.warning(ERROR_MSGS['comments_undetermined'])
             self.set_buildbot_property('got_revision',
-                                       rev[:12],
+                                       rev,
                                        write_to_file=True)
 
     def _count_ctors(self):
         """count num of ctors and set testresults."""
         dirs = self.query_abs_dirs()
         python_path = os.path.join(dirs['abs_work_dir'], 'venv', 'bin',
                                    'python')
         abs_count_ctors_path = os.path.join(dirs['abs_src_dir'],
--- a/testing/puppeteer/firefox/firefox_puppeteer/__init__.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/__init__.py
@@ -1,112 +1,9 @@
 # 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/.
 
-from marionette_driver.marionette import HTMLElement
-
-from decorators import use_class_as_property
-
-
-__version__ = '52.0.0'
+from firefox_puppeteer.mixins import PuppeteerMixin
+from firefox_puppeteer.puppeteer import Puppeteer
 
 
-class Puppeteer(object):
-    """The puppeteer class is used to expose libraries to test cases.
-
-    example:
-    `class MyTestCase(MarionetteTestCase, Puppeteer)`
-
-    Each library can be referenced by its puppeteer name as a member of a
-    the TestCase instance. For example, from within a test method, the
-    "current_window" member of the "Browser" class can be accessed via
-    "self.browser.current_window".
-    """
-
-    def __init__(self):
-        self.marionette = None
-
-    def get_marionette(self):
-        return self.marionette
-
-    @use_class_as_property('api.appinfo.AppInfo')
-    def appinfo(self):
-        """
-        Provides access to members of the appinfo  api.
-
-        See the :class:`~api.appinfo.AppInfo` reference.
-        """
-
-    @use_class_as_property('api.keys.Keys')
-    def keys(self):
-        """
-        Provides a definition of control keys to use with keyboard shortcuts.
-        For example, keys.CONTROL or keys.ALT.
-        See the :class:`~api.keys.Keys` reference.
-        """
-
-    @use_class_as_property('api.places.Places')
-    def places(self):
-        """Provides low-level access to several bookmark and history related actions.
-
-        See the :class:`~api.places.Places` reference.
-        """
-
-    @use_class_as_property('api.utils.Utils')
-    def utils(self):
-        """Provides an api for interacting with utility actions.
-
-        See the :class:`~api.utils.Utils` reference.
-        """
-
-    @property
-    def platform(self):
-        """Returns the lowercased platform name.
-
-        :returns: Platform name
-        """
-        return self.marionette.session_capabilities['platformName']
-
-    @use_class_as_property('api.prefs.Preferences')
-    def prefs(self):
-        """
-        Provides an api for setting and inspecting preferences, as see in
-        about:config.
-
-        See the :class:`~api.prefs.Preferences` reference.
-        """
-
-    @use_class_as_property('api.security.Security')
-    def security(self):
-        """
-        Provides an api for accessing security related properties and helpers.
-
-        See the :class:`~api.security.Security` reference.
-        """
-
-    @use_class_as_property('ui.windows.Windows')
-    def windows(self):
-        """
-        Provides shortcuts to the top-level windows.
-
-        See the :class:`~ui.window.Windows` reference.
-        """
-
-
-class DOMElement(HTMLElement):
-    """
-    Class that inherits from HTMLElement and provides a way for subclasses to
-    expose new api's.
-    """
-
-    def __new__(cls, element):
-        instance = object.__new__(cls)
-        instance.__dict__ = element.__dict__.copy()
-        setattr(instance, 'inner', element)
-
-        return instance
-
-    def __init__(self, element):
-        pass
-
-    def get_marionette(self):
-        return self.marionette
+__version__ = '52.1.0'
--- a/testing/puppeteer/firefox/firefox_puppeteer/api/keys.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/api/keys.py
@@ -4,17 +4,14 @@
 
 import marionette_driver
 
 
 class Keys(marionette_driver.keys.Keys):
     """Proxy to marionette's keys with an "accel" provided for convenience
     testing across platforms."""
 
-    def __init__(self, marionette_getter):
-        self.marionette_getter = marionette_getter
-
-        caps = self.marionette_getter().session_capabilities
-        self.isDarwin = caps['platformName'] == 'darwin'
+    def __init__(self, marionette):
+        self.isDarwin = marionette.session_capabilities['platformName'] == 'darwin'
 
     @property
     def ACCEL(self):
         return self.META if self.isDarwin else self.CONTROL
--- a/testing/puppeteer/firefox/firefox_puppeteer/api/software_update.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/api/software_update.py
@@ -77,18 +77,18 @@ class ActiveUpdate(BaseLib):
     """)
 
 
 class MARChannels(BaseLib):
     """Class to handle the allowed MAR channels as listed in update-settings.ini."""
     INI_SECTION = 'Settings'
     INI_OPTION = 'ACCEPTED_MAR_CHANNEL_IDS'
 
-    def __init__(self, marionette_getter):
-        BaseLib.__init__(self, marionette_getter)
+    def __init__(self, marionette):
+        BaseLib.__init__(self, marionette)
 
         self._ini_file_path = self.marionette.execute_script("""
           Components.utils.import('resource://gre/modules/Services.jsm');
 
           let file = Services.dirsvc.get('GreD', Components.interfaces.nsIFile);
           file.append('update-settings.ini');
 
           return file.path;
@@ -163,25 +163,25 @@ class MARChannels(BaseLib):
 class SoftwareUpdate(BaseLib):
     """The SoftwareUpdate API adds support for an easy access to the update process."""
     PREF_APP_DISTRIBUTION = 'distribution.id'
     PREF_APP_DISTRIBUTION_VERSION = 'distribution.version'
     PREF_APP_UPDATE_URL = 'app.update.url'
     PREF_APP_UPDATE_URL_OVERRIDE = 'app.update.url.override'
     PREF_DISABLED_ADDONS = 'extensions.disabledAddons'
 
-    def __init__(self, marionette_getter):
-        BaseLib.__init__(self, marionette_getter)
+    def __init__(self, marionette):
+        BaseLib.__init__(self, marionette)
 
-        self.app_info = AppInfo(marionette_getter)
-        self.prefs = Preferences(marionette_getter)
+        self.app_info = AppInfo(marionette)
+        self.prefs = Preferences(marionette)
 
-        self._update_channel = UpdateChannel(marionette_getter)
-        self._mar_channels = MARChannels(marionette_getter)
-        self._active_update = ActiveUpdate(marionette_getter)
+        self._update_channel = UpdateChannel(marionette)
+        self._mar_channels = MARChannels(marionette)
+        self._active_update = ActiveUpdate(marionette)
 
     @property
     def ABI(self):
         """Get the customized ABI for the update service.
 
         :returns: ABI version
         """
         abi = self.app_info.XPCOMABI
@@ -373,20 +373,20 @@ class SoftwareUpdate(BaseLib):
 
         return url
 
 
 class UpdateChannel(BaseLib):
     """Class to handle the update channel as listed in channel-prefs.js"""
     REGEX_UPDATE_CHANNEL = re.compile(r'("app\.update\.channel", ")([^"].*)(?=")')
 
-    def __init__(self, marionette_getter):
-        BaseLib.__init__(self, marionette_getter)
+    def __init__(self, marionette):
+        BaseLib.__init__(self, marionette)
 
-        self.prefs = Preferences(marionette_getter)
+        self.prefs = Preferences(marionette)
 
         self.file_path = self.marionette.execute_script("""
           Components.utils.import('resource://gre/modules/Services.jsm');
 
           let file = Services.dirsvc.get('PrfDef', Components.interfaces.nsIFile);
           file.append('channel-prefs.js');
 
           return file.path;
--- a/testing/puppeteer/firefox/firefox_puppeteer/api/utils.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/api/utils.py
@@ -8,17 +8,17 @@ from firefox_puppeteer.base import BaseL
 
 
 class Utils(BaseLib):
     """Low-level access to utility actions."""
 
     def __init__(self, *args, **kwargs):
         BaseLib.__init__(self, *args, **kwargs)
 
-        self._permissions = Permissions(lambda: self.marionette)
+        self._permissions = Permissions(self.marionette)
 
     @property
     def permissions(self):
         """Handling of various permissions for hosts.
 
         :returns: Instance of the Permissions class.
         """
         return self._permissions
--- a/testing/puppeteer/firefox/firefox_puppeteer/base.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/base.py
@@ -1,23 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 class BaseLib(object):
     """A base class that handles lazily setting the "client" class attribute."""
 
-    def __init__(self, marionette_getter):
-        if not callable(marionette_getter):
-            raise TypeError('Invalid callback for "marionette_getter": %s' % marionette_getter)
-
-        self._marionette = None
-        self._marionette_getter = marionette_getter
+    def __init__(self, marionette):
+        self._marionette = marionette
 
     @property
     def marionette(self):
-        if self._marionette is None:
-            self._marionette = self._marionette_getter()
         return self._marionette
-
-    def get_marionette(self):
-        return self.marionette
--- a/testing/puppeteer/firefox/firefox_puppeteer/decorators.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/decorators.py
@@ -23,13 +23,13 @@ class use_class_as_property(object):
         @wraps(func)
         def _(cls, *args, **kwargs):
             tag = '_{}_{}'.format(self.mod_name, self.cls_name)
             prop = getattr(cls, tag, None)
 
             if not prop:
                 module = import_module('.{}'.format(self.mod_name),
                                        'firefox_puppeteer')
-                prop = getattr(module, self.cls_name)(cls.get_marionette)
+                prop = getattr(module, self.cls_name)(cls.marionette)
                 setattr(cls, tag, prop)
             func(cls, *args, **kwargs)
             return prop
         return _
rename from testing/puppeteer/firefox/firefox_puppeteer/testcases/base.py
rename to testing/puppeteer/firefox/firefox_puppeteer/mixins.py
--- a/testing/puppeteer/firefox/firefox_puppeteer/testcases/base.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/mixins.py
@@ -1,44 +1,37 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import unittest
-
-from firefox_puppeteer import Puppeteer
+from firefox_puppeteer.puppeteer import Puppeteer
 from firefox_puppeteer.ui.browser.window import BrowserWindow
 
 
-class BaseFirefoxTestCase(unittest.TestCase, Puppeteer):
-    """Base TestCase class for Firefox Desktop tests.
+class PuppeteerMixin(object):
+    """Mix-in class for Firefox specific API modules exposed to test scope.
 
-    This is designed to enhance MarionetteTestCase by inserting the Puppeteer
-    mixin class (so Firefox specific API modules are exposed to test scope) and
-    providing common set-up and tear-down code for Firefox tests.
+    It also provides common set-up and tear-down code for Firefox tests.
 
-    Child classes are expected to also subclass MarionetteTestCase such that
-    MarionetteTestCase is inserted into the MRO after FirefoxTestCase but before
-    unittest.TestCase.
+    Child test case classes are expected to also subclass MarionetteTestCase such
+    that PuppeteerMixin is followed by MarionetteTestCase. This will insert the
+    Puppeteer mixin before the MarionetteTestCase into the MRO.
 
     example:
-    `class AwesomeTestCase(FirefoxTestCase, MarionetteTestCase)`
+    `class MyTestCase(PuppeteerMixin, MarionetteTestCase)`
 
     The key role of MarionetteTestCase is to set self.marionette appropriately
-    in `__init__`. Any TestCase class that satisfies this requirement is
+    in `setUp()`. Any TestCase class that satisfies this requirement is
     compatible with this class.
 
     If you're extending the inheritance tree further to make specialized
     TestCases, favour the use of super() as opposed to explicit calls to a
     parent class.
 
     """
-    def __init__(self, *args, **kwargs):
-        super(BaseFirefoxTestCase, self).__init__(*args, **kwargs)
-
     def _check_and_fix_leaked_handles(self):
         handle_count = len(self.marionette.window_handles)
         url = []
 
         try:
             # Verify the existence of leaked tabs and print their URLs.
             if self._start_handle_count < handle_count:
                 message = ('A test must not leak window handles. This test started with '
@@ -50,52 +43,54 @@ class BaseFirefoxTestCase(unittest.TestC
                             url.append(' %s' % self.marionette.get_url())
                 self.assertListEqual(self._init_tab_handles, self.marionette.window_handles,
                                      message + ','.join(url))
         finally:
             # For clean-up make sure we work on a proper browser window
             if not self.browser or self.browser.closed:
                 # Find a proper replacement browser window
                 # TODO: We have to make this less error prone in case no browser is open.
-                self.browser = self.windows.switch_to(lambda win: type(win) is BrowserWindow)
+                self.browser = self.puppeteer.windows.switch_to(
+                    lambda win: type(win) is BrowserWindow)
 
             # Ensure to close all the remaining chrome windows to give following
             # tests a proper start condition and make them not fail.
-            self.windows.close_all([self.browser])
+            self.puppeteer.windows.close_all([self.browser])
             self.browser.focus()
 
             # Also close all remaining tabs
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
             self.browser.tabbar.tabs[0].switch_to()
 
     def restart(self, **kwargs):
         """Restart Firefox and re-initialize data.
 
         :param flags: Specific restart flags for Firefox
         """
         self.marionette.restart(in_app=not kwargs.get('clean'), **kwargs)
 
         # Ensure that we always have a valid browser instance available
-        self.browser = self.windows.switch_to(lambda win: type(win) is BrowserWindow)
+        self.browser = self.puppeteer.windows.switch_to(lambda win: type(win) is BrowserWindow)
 
     def setUp(self, *args, **kwargs):
-        super(BaseFirefoxTestCase, self).setUp(*args, **kwargs)
+        super(PuppeteerMixin, self).setUp(*args, **kwargs)
 
         self._start_handle_count = len(self.marionette.window_handles)
         self._init_tab_handles = self.marionette.window_handles
         self.marionette.set_context('chrome')
 
-        self.browser = self.windows.current
+        self.puppeteer = Puppeteer(self.marionette)
+        self.browser = self.puppeteer.windows.current
         self.browser.focus()
         with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
             # Ensure that we have a default page opened
-            self.marionette.navigate(self.prefs.get_pref('browser.newtab.url'))
+            self.marionette.navigate(self.puppeteer.prefs.get_pref('browser.newtab.url'))
 
     def tearDown(self, *args, **kwargs):
         self.marionette.set_context('chrome')
 
         try:
             # This code should be run after all other tearDown code
             # so that in case of a failure, further tests will not run
             # in a state that is more inconsistent than necessary.
             self._check_and_fix_leaked_handles()
         finally:
-            super(BaseFirefoxTestCase, self).tearDown(*args, **kwargs)
+            super(PuppeteerMixin, self).tearDown(*args, **kwargs)
new file mode 100644
--- /dev/null
+++ b/testing/puppeteer/firefox/firefox_puppeteer/puppeteer.py
@@ -0,0 +1,84 @@
+# 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/.
+
+from decorators import use_class_as_property
+
+
+class Puppeteer(object):
+    """The puppeteer class is used to expose additional API and UI libraries.
+
+    Each library can be referenced by its puppeteer name as a member of a
+    Puppeteer instance. For example, the `current_window` member of the
+    `Browser` class can be accessed via `puppeteer.browser.current_window`.
+    """
+
+    def __init__(self, marionette):
+        self._marionette = marionette
+
+    @property
+    def marionette(self):
+        return self._marionette
+
+    @use_class_as_property('api.appinfo.AppInfo')
+    def appinfo(self):
+        """
+        Provides access to members of the appinfo  api.
+
+        See the :class:`~api.appinfo.AppInfo` reference.
+        """
+
+    @use_class_as_property('api.keys.Keys')
+    def keys(self):
+        """
+        Provides a definition of control keys to use with keyboard shortcuts.
+        For example, keys.CONTROL or keys.ALT.
+        See the :class:`~api.keys.Keys` reference.
+        """
+
+    @use_class_as_property('api.places.Places')
+    def places(self):
+        """Provides low-level access to several bookmark and history related actions.
+
+        See the :class:`~api.places.Places` reference.
+        """
+
+    @use_class_as_property('api.utils.Utils')
+    def utils(self):
+        """Provides an api for interacting with utility actions.
+
+        See the :class:`~api.utils.Utils` reference.
+        """
+
+    @property
+    def platform(self):
+        """Returns the lowercased platform name.
+
+        :returns: Platform name
+        """
+        return self.marionette.session_capabilities['platformName']
+
+    @use_class_as_property('api.prefs.Preferences')
+    def prefs(self):
+        """
+        Provides an api for setting and inspecting preferences, as see in
+        about:config.
+
+        See the :class:`~api.prefs.Preferences` reference.
+        """
+
+    @use_class_as_property('api.security.Security')
+    def security(self):
+        """
+        Provides an api for accessing security related properties and helpers.
+
+        See the :class:`~api.security.Security` reference.
+        """
+
+    @use_class_as_property('ui.windows.Windows')
+    def windows(self):
+        """
+        Provides shortcuts to the top-level windows.
+
+        See the :class:`~ui.window.Windows` reference.
+        """
deleted file mode 100644
--- a/testing/puppeteer/firefox/firefox_puppeteer/testcases/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# 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/.
-
-from firefox_puppeteer.testcases.base import BaseFirefoxTestCase
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/about_window/deck.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/about_window/deck.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_driver import By
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 from firefox_puppeteer.ui.deck import Panel
 
 
 class Deck(UIBaseLib):
 
     def _create_panel_for_id(self, panel_id):
         """Creates an instance of :class:`Panel` for the specified panel id.
 
@@ -22,17 +22,17 @@ class Deck(UIBaseLib):
                    'checkingForUpdates': CheckingForUpdatesPanel,
                    'downloadAndInstall': DownloadAndInstallPanel,
                    'downloadFailed': DownloadFailedPanel,
                    'downloading': DownloadingPanel,
                    'noUpdatesFound': NoUpdatesFoundPanel,
                    }
 
         panel = self.element.find_element(By.ID, panel_id)
-        return mapping.get(panel_id, Panel)(lambda: self.marionette, self.window, panel)
+        return mapping.get(panel_id, Panel)(self.marionette, self.window, panel)
 
     # Properties for visual elements of the deck #
 
     @property
     def apply(self):
         """The :class:`ApplyPanel` instance for the apply panel.
 
         :returns: :class:`ApplyPanel` instance.
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/about_window/window.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/about_window/window.py
@@ -24,12 +24,12 @@ class AboutWindow(BaseWindow):
     def deck(self):
         """The :class:`Deck` instance which represents the deck.
 
         :returns: Reference to the deck.
         """
         self.switch_to()
 
         deck = self.window_element.find_element(By.ID, 'updateDeck')
-        return Deck(lambda: self.marionette, self, deck)
+        return Deck(self.marionette, self, deck)
 
 
 Windows.register_window(AboutWindow.window_type, AboutWindow)
rename from testing/puppeteer/firefox/firefox_puppeteer/ui_base_lib.py
rename to testing/puppeteer/firefox/firefox_puppeteer/ui/base.py
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui_base_lib.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/base.py
@@ -6,22 +6,22 @@ from marionette_driver.marionette import
 
 from firefox_puppeteer.base import BaseLib
 from firefox_puppeteer.ui.windows import BaseWindow
 
 
 class UIBaseLib(BaseLib):
     """A base class for all UI element wrapper classes inside a chrome window."""
 
-    def __init__(self, marionette_getter, window, element):
+    def __init__(self, marionette, window, element):
 
         assert isinstance(window, BaseWindow)
         assert isinstance(element, HTMLElement)
 
-        BaseLib.__init__(self, marionette_getter)
+        BaseLib.__init__(self, marionette)
         self._window = window
         self._element = element
 
     @property
     def element(self):
         """Returns the reference to the underlying DOM element.
 
         :returns: Reference to the DOM element
@@ -30,8 +30,28 @@ class UIBaseLib(BaseLib):
 
     @property
     def window(self):
         """Returns the reference to the chrome window.
 
         :returns: :class:`BaseWindow` instance of the chrome window.
         """
         return self._window
+
+
+class DOMElement(HTMLElement):
+    """
+    Class that inherits from HTMLElement and provides a way for subclasses to
+    expose new api's.
+    """
+
+    def __new__(cls, element):
+        instance = object.__new__(cls)
+        instance.__dict__ = element.__dict__.copy()
+        setattr(instance, 'inner', element)
+
+        return instance
+
+    def __init__(self, element):
+        pass
+
+    def get_marionette(self):
+        return self.marionette
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/notifications.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/notifications.py
@@ -1,17 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from abc import ABCMeta
 
 from marionette_driver import By
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 
 
 class BaseNotification(UIBaseLib):
     """Abstract base class for any kind of notification."""
 
     __metaclass__ = ABCMeta
 
     @property
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
@@ -5,34 +5,33 @@
 from marionette_driver import (
     By, Wait
 )
 
 from marionette_driver.errors import NoSuchElementException
 
 import firefox_puppeteer.errors as errors
 
-from firefox_puppeteer import DOMElement
 from firefox_puppeteer.api.security import Security
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib, DOMElement
 
 
 class TabBar(UIBaseLib):
     """Wraps the tabs toolbar DOM element inside a browser window."""
 
     # Properties for visual elements of the tabs toolbar #
 
     @property
     def menupanel(self):
         """A :class:`MenuPanel` instance which represents the menu panel
         at the far right side of the tabs toolbar.
 
         :returns: :class:`MenuPanel` instance.
         """
-        return MenuPanel(lambda: self.marionette, self.window)
+        return MenuPanel(self.marionette, self.window)
 
     @property
     def newtab_button(self):
         """The DOM element which represents the new tab button.
 
         :returns: Reference to the new tab button.
         """
         return self.toolbar.find_element(By.ANON_ATTRIBUTE, {'anonid': 'tabs-newtab-button'})
@@ -40,17 +39,17 @@ class TabBar(UIBaseLib):
     @property
     def tabs(self):
         """List of all the :class:`Tab` instances of the current browser window.
 
         :returns: List of :class:`Tab` instances.
         """
         tabs = self.toolbar.find_elements(By.TAG_NAME, 'tab')
 
-        return [Tab(lambda: self.marionette, self.window, tab) for tab in tabs]
+        return [Tab(self.marionette, self.window, tab) for tab in tabs]
 
     @property
     def toolbar(self):
         """The DOM element which represents the tab toolbar.
 
         :returns: Reference to the tabs toolbar.
         """
         return self.element
@@ -202,20 +201,20 @@ class TabBar(UIBaseLib):
         """, script_args=[tab_element])
 
         return handle
 
 
 class Tab(UIBaseLib):
     """Wraps a tab DOM element."""
 
-    def __init__(self, marionette_getter, window, element):
-        UIBaseLib.__init__(self, marionette_getter, window, element)
+    def __init__(self, marionette, window, element):
+        UIBaseLib.__init__(self, marionette, window, element)
 
-        self._security = Security(lambda: self.marionette)
+        self._security = Security(self.marionette)
         self._handle = None
 
     # Properties for visual elements of tabs #
 
     @property
     def close_button(self):
         """The DOM element which represents the tab close button.
 
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_driver import By, keys, Wait
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 
 
 class NavBar(UIBaseLib):
     """Provides access to the DOM elements contained in the
     navigation bar as well as the location bar."""
 
     def __init__(self, *args, **kwargs):
         UIBaseLib.__init__(self, *args, **kwargs)
@@ -44,17 +44,17 @@ class NavBar(UIBaseLib):
     def locationbar(self):
         """Provides access to the DOM elements contained in the
         locationbar.
 
         See the :class:`LocationBar` reference.
         """
         if not self._locationbar:
             urlbar = self.marionette.find_element(By.ID, 'urlbar')
-            self._locationbar = LocationBar(lambda: self.marionette, self.window, urlbar)
+            self._locationbar = LocationBar(self.marionette, self.window, urlbar)
 
         return self._locationbar
 
     @property
     def menu_button(self):
         """Provides access to the DOM element menu button in the navbar.
 
         :returns: Reference to the menu button element.
@@ -83,17 +83,17 @@ class LocationBar(UIBaseLib):
     @property
     def autocomplete_results(self):
         """Provides access to and methods for the location bar
         autocomplete results.
 
         See the :class:`AutocompleteResults` reference."""
         if not self._autocomplete_results:
             popup = self.marionette.find_element(By.ID, 'PopupAutoCompleteRichResult')
-            self._autocomplete_results = AutocompleteResults(lambda: self.marionette,
+            self._autocomplete_results = AutocompleteResults(self.marionette,
                                                              self.window, popup)
 
         return self._autocomplete_results
 
     def clear(self):
         """Clears the contents of the url bar (via the DELETE shortcut)."""
         self.focus('shortcut')
         self.urlbar.send_keys(keys.Keys.DELETE)
@@ -207,17 +207,17 @@ class LocationBar(UIBaseLib):
     def identity_popup(self):
         """Provides utility members for accessing and manipulating the
         identity popup.
 
         See the :class:`IdentityPopup` reference.
         """
         if not self._identity_popup:
             popup = self.marionette.find_element(By.ID, 'identity-popup')
-            self._identity_popup = IdentityPopup(lambda: self.marionette,
+            self._identity_popup = IdentityPopup(self.marionette,
                                                  self.window, popup)
 
         return self._identity_popup
 
     def load_url(self, url):
         """Load the specified url in the location bar by synthesized
         keystrokes.
 
@@ -447,17 +447,17 @@ class IdentityPopup(UIBaseLib):
     def view(self):
         """Provides utility members for accessing and manipulating the
         identity popup's multi view.
 
         See the :class:`IdentityPopupMultiView` reference.
         """
         if not self._view:
             view = self.marionette.find_element(By.ID, 'identity-popup-multiView')
-            self._view = IdentityPopupMultiView(lambda: self.marionette, self.window, view)
+            self._view = IdentityPopupMultiView(self.marionette, self.window, view)
 
         return self._view
 
 
 class IdentityPopupMultiView(UIBaseLib):
 
     def _create_view_for_id(self, view_id):
         """Creates an instance of :class:`IdentityPopupView` for the specified view id.
@@ -466,17 +466,17 @@ class IdentityPopupMultiView(UIBaseLib):
 
         :returns: :class:`IdentityPopupView` instance
         """
         mapping = {'identity-popup-mainView': IdentityPopupMainView,
                    'identity-popup-securityView': IdentityPopupSecurityView,
                    }
 
         view = self.marionette.find_element(By.ID, view_id)
-        return mapping.get(view_id, IdentityPopupView)(lambda: self.marionette, self.window, view)
+        return mapping.get(view_id, IdentityPopupView)(self.marionette, self.window, view)
 
     @property
     def main(self):
         """The DOM element which represents the main view.
 
         :returns: Reference to the main view.
         """
         return self._create_view_for_id('identity-popup-mainView')
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from marionette_driver import By, Wait
 from marionette_driver.errors import NoSuchElementException
+
 from firefox_puppeteer.ui.about_window.window import AboutWindow
 from firefox_puppeteer.ui.browser.notifications import (
     AddOnInstallBlockedNotification,
     AddOnInstallConfirmationNotification,
     AddOnInstallCompleteNotification,
     AddOnInstallFailedNotification,
     AddOnProgressNotification,
     BaseNotification)
@@ -74,17 +75,17 @@ class BrowserWindow(BaseWindow):
         the back, forward and home buttons. It also contains the location bar.
 
         See the :class:`~ui.browser.toolbars.NavBar` reference.
         """
         self.switch_to()
 
         if not self._navbar:
             navbar = self.window_element.find_element(By.ID, 'nav-bar')
-            self._navbar = NavBar(lambda: self.marionette, self, navbar)
+            self._navbar = NavBar(self.marionette, self, navbar)
 
         return self._navbar
 
     @property
     def notification(self):
         """Provides access to the currently displayed notification."""
 
         notifications_map = {
@@ -96,17 +97,17 @@ class BrowserWindow(BaseWindow):
         }
 
         try:
             notification = self.window_element.find_element(
                 By.CSS_SELECTOR, '#notification-popup popupnotification')
 
             notification_id = notification.get_attribute('id')
             return notifications_map.get(notification_id, BaseNotification)(
-                lambda: self.marionette, self, notification)
+                self.marionette, self, notification)
 
         except NoSuchElementException:
             return None  # no notification is displayed
 
     def wait_for_notification(self, notification_class=BaseNotification,
                               timeout=5):
         """Waits for the specified notification to be displayed.
 
@@ -137,17 +138,17 @@ class BrowserWindow(BaseWindow):
         """Provides access to the tab bar.
 
         See the :class:`~ui.browser.tabbar.TabBar` reference.
         """
         self.switch_to()
 
         if not self._tabbar:
             tabbrowser = self.window_element.find_element(By.ID, 'tabbrowser-tabs')
-            self._tabbar = TabBar(lambda: self.marionette, self, tabbrowser)
+            self._tabbar = TabBar(self.marionette, self, tabbrowser)
 
         return self._tabbar
 
     def close(self, trigger='menu', force=False):
         """Closes the current browser window by using the specified trigger.
 
         :param trigger: Optional, method to close the current browser window. This can
          be a string with one of `menu` or `shortcut`, or a callback which gets triggered
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/deck.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/deck.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 
 
 class Panel(UIBaseLib):
 
     def __eq__(self, other):
         return self.element.get_attribute('id') == other.element.get_attribute('id')
 
     def __ne__(self, other):
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/menu.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/menu.py
@@ -1,17 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from marionette_driver import By
 from marionette_driver.errors import NoSuchElementException
 
-from firefox_puppeteer import DOMElement
 from firefox_puppeteer.base import BaseLib
+from firefox_puppeteer.ui.base import DOMElement
 
 
 class MenuBar(BaseLib):
     """Wraps the menubar DOM element inside a browser window."""
 
     @property
     def menus(self):
         """A list of :class:`MenuElement` instances corresponding to
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_driver import By, Wait
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 from firefox_puppeteer.ui.deck import Panel
 
 
 class Deck(UIBaseLib):
 
     def _create_panel_for_id(self, panel_id):
         """Creates an instance of :class:`Panel` for the specified panel id.
 
@@ -20,17 +20,17 @@ class Deck(UIBaseLib):
         mapping = {'feedPanel': FeedPanel,
                    'generalPanel': GeneralPanel,
                    'mediaPanel': MediaPanel,
                    'permPanel': PermissionsPanel,
                    'securityPanel': SecurityPanel
                    }
 
         panel = self.element.find_element(By.ID, panel_id)
-        return mapping.get(panel_id, Panel)(lambda: self.marionette, self.window, panel)
+        return mapping.get(panel_id, Panel)(self.marionette, self.window, panel)
 
     # Properties for visual elements of the deck #
 
     @property
     def feed(self):
         """The :class:`FeedPanel` instance for the feed panel.
 
         :returns: :class:`FeedPanel` instance.
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/window.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/window.py
@@ -28,17 +28,17 @@ class PageInfoWindow(BaseWindow):
 
     @property
     def deck(self):
         """The :class:`Deck` instance which represents the deck.
 
         :returns: Reference to the deck.
         """
         deck = self.window_element.find_element(By.ID, 'mainDeck')
-        return Deck(lambda: self.marionette, self, deck)
+        return Deck(self.marionette, self, deck)
 
     def close(self, trigger='shortcut', force=False):
         """Closes the current page info window by using the specified trigger.
 
         :param trigger: Optional, method to close the current window. This can
          be a string with one of `menu` (OS X only) or `shortcut`, or a callback
          which gets triggered with the current :class:`PageInfoWindow` as parameter.
          Defaults to `shortcut`.
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/dialog.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/dialog.py
@@ -29,17 +29,17 @@ class UpdateWizardDialog(BaseWindow):
     @property
     def wizard(self):
         """The :class:`Wizard` instance which represents the wizard.
 
         :returns: Reference to the wizard.
         """
         # The deck is also the root element
         wizard = self.marionette.find_element(By.ID, 'updates')
-        return Wizard(lambda: self.marionette, self, wizard)
+        return Wizard(self.marionette, self, wizard)
 
     def select_next_page(self):
         """Clicks on "Next" button, and waits for the next page to show up."""
         current_panel = self.wizard.selected_panel
 
         self.wizard.next_button.click()
         Wait(self.marionette).until(
             lambda _: self.wizard.selected_panel != current_panel,
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_driver import By, Wait
 
-from firefox_puppeteer.ui_base_lib import UIBaseLib
+from firefox_puppeteer.ui.base import UIBaseLib
 from firefox_puppeteer.ui.deck import Panel
 
 
 class Wizard(UIBaseLib):
 
     def __init__(self, *args, **kwargs):
         UIBaseLib.__init__(self, *args, **kwargs)
 
@@ -37,17 +37,17 @@ class Wizard(UIBaseLib):
                    'updatesfoundbasic': UpdatesFoundBasicPanel,
 
                    # TODO: Remove once we no longer support version Firefox 45.0ESR
                    'incompatibleCheck': IncompatibleCheckPanel,
                    'incompatibleList': IncompatibleListPanel,
                    }
 
         panel = self.element.find_element(By.ID, panel_id)
-        return mapping.get(panel_id, Panel)(lambda: self.marionette, self.window, panel)
+        return mapping.get(panel_id, Panel)(self.marionette, self.window, panel)
 
     # Properties for visual buttons of the wizard #
 
     @property
     def _buttons(self):
         return self.element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'Buttons'})
 
     @property
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/windows.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/windows.py
@@ -108,19 +108,19 @@ class Windows(BaseLib):
                     self.switch_to(handle)
                 window_type = self.marionette.get_window_type()
             finally:
                 # Ensure to switch back to the original window
                 if handle != current_handle:
                     self.switch_to(current_handle)
 
             if window_type in self.windows_map:
-                window = self.windows_map[window_type](lambda: self.marionette, handle)
+                window = self.windows_map[window_type](self.marionette, handle)
             else:
-                window = BaseWindow(lambda: self.marionette, handle)
+                window = BaseWindow(self.marionette, handle)
 
             if expected_class is not None and type(window) is not expected_class:
                 raise errors.UnexpectedWindowTypeError('Expected window "%s" but got "%s"' %
                                                        (expected_class, type(window)))
 
             # Before continuing ensure the chrome window has been completed loading
             Wait(self.marionette).until(
                 lambda _: self.loaded(handle),
@@ -208,21 +208,21 @@ class Windows(BaseLib):
 
 class BaseWindow(BaseLib):
     """Base class for any kind of chrome window."""
 
     # l10n class attributes will be set by each window class individually
     dtds = []
     properties = []
 
-    def __init__(self, marionette_getter, window_handle):
-        BaseLib.__init__(self, marionette_getter)
-        self._l10n = L10n(self.get_marionette)
-        self._prefs = Preferences(self.get_marionette)
-        self._windows = Windows(self.get_marionette)
+    def __init__(self, marionette, window_handle):
+        BaseLib.__init__(self, marionette)
+        self._l10n = L10n(self.marionette)
+        self._prefs = Preferences(self.marionette)
+        self._windows = Windows(self.marionette)
 
         if window_handle not in self.marionette.chrome_window_handles:
             raise errors.UnknownWindowError('Window with handle "%s" does not exist' %
                                             window_handle)
         self._handle = window_handle
 
     def __eq__(self, other):
         return self.handle == other.handle
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -184,23 +184,27 @@ var Bookmarks = Object.freeze({
       let item = yield insertBookmark(insertInfo, parent);
 
       // Notify onItemAdded to listeners.
       let observers = PlacesUtils.bookmarks.getObservers();
       // We need the itemId to notify, though once the switch to guids is
       // complete we may stop using it.
       let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
       let itemId = yield PlacesUtils.promiseItemId(item.guid);
+
+      // Pass tagging information for the observers to skip over these notifications when needed.
+      let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
+      let isTagsFolder = parent._id == PlacesUtils.tagsFolderId;
       notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
                                          item.type, uri, item.title || null,
                                          PlacesUtils.toPRTime(item.dateAdded), item.guid,
-                                         item.parentGuid, item.source ]);
+                                         item.parentGuid, item.source ],
+                                       { isTagging: isTagging || isTagsFolder });
 
       // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
-      let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
       if (isTagging) {
         for (let entry of (yield fetchBookmarksByURL(item))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                PlacesUtils.toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
                                                entry.guid, entry.parentGuid,
                                                "", item.source ]);
         }
@@ -425,22 +429,23 @@ var Bookmarks = Object.freeze({
         throw new Error("No bookmarks found for the provided GUID.");
 
       item = yield removeBookmark(item, options);
 
       // Notify onItemRemoved to listeners.
       let { source = Bookmarks.SOURCES.DEFAULT } = options;
       let observers = PlacesUtils.bookmarks.getObservers();
       let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
+      let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
       notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
                                            item.type, uri, item.guid,
                                            item.parentGuid,
-                                           source ]);
+                                           source ],
+                                         { isTagging: isUntagging });
 
-      let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
       if (isUntagging) {
         for (let entry of (yield fetchBookmarksByURL(item))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                PlacesUtils.toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
                                                entry.guid, entry.parentGuid,
                                                "", source ]);
         }
@@ -785,19 +790,30 @@ var Bookmarks = Object.freeze({
  * Sends a bookmarks notification through the given observers.
  *
  * @param observers
  *        array of nsINavBookmarkObserver objects.
  * @param notification
  *        the notification name.
  * @param args
  *        array of arguments to pass to the notification.
+ * @param information
+ *        Information about the notification, so we can filter based
+ *        based on the observer's preferences.
  */
-function notify(observers, notification, args) {
+function notify(observers, notification, args, information = {}) {
   for (let observer of observers) {
+    if (information.isTagging && observer.skipTags) {
+      continue;
+    }
+
+    if (information.isDescendantRemoval && observer.skipDescendantsOnItemRemoval) {
+      continue;
+    }
+
     try {
       observer[notification](...args);
     } catch (ex) {}
   }
 }
 
 // Update implementation.
 
@@ -1479,17 +1495,20 @@ Task.async(function* (db, folderGuids, o
   // Notify listeners in reverse order to serve children before parents.
   let { source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = options;
   let observers = PlacesUtils.bookmarks.getObservers();
   for (let item of itemsRemoved.reverse()) {
     let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
     notify(observers, "onItemRemoved", [ item._id, item._parentId,
                                          item.index, item.type, uri,
                                          item.guid, item.parentGuid,
-                                         source ]);
+                                         source ],
+                                       // Notify observers that this item is being
+                                       // removed as a descendent.
+                                       { isDescendantRemoval: true });
 
     let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
     if (isUntagging) {
       for (let entry of (yield fetchBookmarksByURL(item))) {
         notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                              PlacesUtils.toPRTime(entry.lastModified),
                                              entry.type, entry._parentId,
                                              entry.guid, entry.parentGuid,
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3767,16 +3767,25 @@
   "URLCLASSIFIER_LOOKUP_TIME": {
     "alert_emails": ["safebrowsing-telemetry@mozilla.org"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 500,
     "n_buckets": 10,
     "description": "Time spent per dbservice lookup (ms)"
   },
+  "URLCLASSIFIER_SHUTDOWN_TIME": {
+    "alert_emails": ["safebrowsing-telemetry@mozilla.org"],
+    "expires_in_version": "58",
+    "kind": "exponential",
+    "high": 60000,
+    "n_buckets": 50,
+    "bug_numbers": [1315140],
+    "description": "Time spent per dbservice shutdown (ms)"
+  },
   "URLCLASSIFIER_CL_CHECK_TIME": {
     "alert_emails": ["safebrowsing-telemetry@mozilla.org"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 500,
     "n_buckets": 10,
     "description": "Time spent per classifier lookup (ms)"
   },
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -1732,16 +1732,18 @@ nsUrlClassifierDBService::Observe(nsISup
 nsresult
 nsUrlClassifierDBService::Shutdown()
 {
   LOG(("shutting down db service\n"));
 
   if (!gDbBackgroundThread)
     return NS_OK;
 
+  Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer;
+
   mCompleters.Clear();
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
     prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
     prefs->RemoveObserver(CHECK_TRACKING_PREF, this);
     prefs->RemoveObserver(CHECK_TRACKING_PB_PREF, this);
--- a/toolkit/content/timepicker.xhtml
+++ b/toolkit/content/timepicker.xhtml
@@ -10,21 +10,19 @@
   <script type="application/javascript" src="chrome://global/content/bindings/timekeeper.js"></script>
   <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
   <script type="application/javascript" src="chrome://global/content/bindings/timepicker.js"></script>
 </head>
 <body>
   <div id="time-picker"></div>
   <template id="spinner-template">
     <div class="spinner-container">
-      <button class="up">â–²</button>
-      <div class="stack">
-        <div class="spinner"></div>
-      </div>
-      <button class="down">â–¼</button>
+      <button class="up"/>
+      <div class="spinner"></div>
+      <button class="down"/>
     </div>
   </template>
   <script type="application/javascript">
   // We need to hide the scroll bar but maintain its scrolling
   // capability, so using |overflow: hidden| is not an option.
   // Instead, we are inserting a user agent stylesheet that is
   // capable of selecting scrollbars, and do |display: none|.
   var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -10,25 +10,27 @@
    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    xmlns:xbl="http://www.mozilla.org/xbl">
   <binding id="datetime-popup"
            extends="chrome://global/content/bindings/popup.xml#arrowpanel">
     <implementation>
       <field name="dateTimePopupFrame">
         this.querySelector("#dateTimePopupFrame");
       </field>
-      <field name="TIME_PICKER_WIDTH" readonly="true">"14em"</field>
-      <field name="TIME_PICKER_HEIGHT" readonly="true">"14em"</field>
+      <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
+      <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
       <method name="loadPicker">
         <parameter name="type"/>
         <parameter name="detail"/>
         <body><![CDATA[
           this.hidden = false;
           this.type = type;
           this.pickerState = {};
+          // TODO: Resize picker according to content zoom level
+          this.style.fontSize = "10px";
           switch (type) {
             case "time": {
               this.detail = detail;
               this.dateTimePopupFrame.addEventListener("load", this, true);
               this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
               this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
               this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
               break;
--- a/toolkit/content/widgets/spinner.js
+++ b/toolkit/content/widgets/spinner.js
@@ -13,66 +13,75 @@
  */
 
 function Spinner(props, context) {
   this.context = context;
   this._init(props);
 }
 
 {
-  const debug = 0 ? console.log.bind(console, '[spinner]') : function() {};
+  const debug = 0 ? console.log.bind(console, "[spinner]") : function() {};
 
-  const ITEM_HEIGHT = 20,
-        VIEWPORT_SIZE = 5,
-        VIEWPORT_COUNT = 5;
+  const ITEM_HEIGHT = 2.5,
+        VIEWPORT_SIZE = 7,
+        VIEWPORT_COUNT = 5,
+        SCROLL_TIMEOUT = 100;
 
   Spinner.prototype = {
     /**
      * Initializes a spinner. Set the default states and properties, cache
      * element references, create the HTML markup, and add event listeners.
      *
      * @param  {Object} props [Properties passed in from parent]
      *         {
      *           {Function} setValue: Takes a value and set the state to
      *             the parent component.
      *           {Function} getDisplayString: Takes a value, and output it
      *             as localized strings.
-     *           {Number} itemHeight [optional]: Item height in pixels.
      *           {Number} viewportSize [optional]: Number of items in a
      *             viewport.
+     *           {Boolean} hideButtons [optional]: Hide up & down buttons
+     *           {Number} rootFontSize [optional]: Used to support zoom in/out
      *         }
      */
     _init(props) {
-      const { setValue, getDisplayString, itemHeight = ITEM_HEIGHT } = props;
+      const { setValue, getDisplayString, hideButtons, rootFontSize = 10 } = props;
 
       const spinnerTemplate = document.getElementById("spinner-template");
       const spinnerElement = document.importNode(spinnerTemplate.content, true);
 
       // Make sure viewportSize is an odd number because we want to have the selected
       // item in the center. If it's an even number, use the default size instead.
       const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;
 
       this.state = {
         items: [],
         isScrolling: false
       };
       this.props = {
-        setValue, getDisplayString, itemHeight, viewportSize,
+        setValue, getDisplayString, viewportSize, rootFontSize,
         // We can assume that the viewportSize is an odd number. Calculate how many
         // items we need to insert on top of the spinner so that the selected is at
         // the center. Ex: if viewportSize is 5, we need 2 items on top.
         viewportTopOffset: (viewportSize - 1) / 2
       };
       this.elements = {
+        container: spinnerElement.querySelector(".spinner-container"),
         spinner: spinnerElement.querySelector(".spinner"),
         up: spinnerElement.querySelector(".up"),
         down: spinnerElement.querySelector(".down"),
         itemsViewElements: []
       };
 
+      this.elements.spinner.style.height = (ITEM_HEIGHT * viewportSize) + "rem";
+
+      if (hideButtons) {
+        this.elements.container.classList.add("hide-buttons");
+      }
+
       this.context.appendChild(spinnerElement);
       this._attachEventListeners();
     },
 
     /**
      * Only the parent component calls setState on the spinner.
      * It checks if the items have changed and updates the spinner.
      * If only the value has changed, smooth scrolls to the new value.
@@ -114,17 +123,17 @@ function Spinner(props, context) {
      * - Update the index state
      * - If a smooth scroll has reached its destination, set [isScrolling] state
      *   to false
      * - If the value has changed, update the [value] state and call [setValue]
      * - If infinite scrolling is on, reset the scrolling position if necessary
      */
     _onScroll() {
       const { items, itemsView, isInfiniteScroll } = this.state;
-      const { viewportSize, viewportTopOffset, itemHeight } = this.props;
+      const { viewportSize, viewportTopOffset } = this.props;
       const { spinner, itemsViewElements } = this.elements;
 
       this.state.index = this._getIndexByOffset(spinner.scrollTop);
 
       const value = itemsView[this.state.index + viewportTopOffset].value;
 
       // Check if smooth scrolling has reached its destination.
       // This prevents input box jump when input box changes values.
@@ -143,16 +152,26 @@ function Spinner(props, context) {
       if (items.length >= viewportSize && isInfiniteScroll) {
         // If the scroll position is near the top or bottom, jump back to the middle
         // so user can keep scrolling up or down.
         if (this.state.index < viewportSize ||
             this.state.index > itemsView.length - viewportSize) {
           this._scrollTo(this.state.value, true);
         }
       }
+
+      // Use a timer to detect if a scroll event has not fired within some time
+      // (defined in SCROLL_TIMEOUT). This is required because we need to hide
+      // highlight and hover state when user is scrolling.
+      clearTimeout(this.state.scrollTimer);
+      this.elements.spinner.classList.add("scrolling");
+      this.state.scrollTimer = setTimeout(() => {
+        this.elements.spinner.classList.remove("scrolling");
+        this.elements.spinner.dispatchEvent(new CustomEvent("ScrollStop"));
+      }, SCROLL_TIMEOUT);
     },
 
     /**
      * Updates the spinner items to the current states.
      */
     _updateItems() {
       const { viewportSize, viewportTopOffset } = this.props;
       const { items, isInfiniteScroll } = this.state;
@@ -186,35 +205,46 @@ function Spinner(props, context) {
      * Make sure the number or child elements is the same as length
      * and keep the elements' references for updating textContent
      *
      * @param {Number} length [The number of child elements]
      * @param {DOMElement} parent [The parent element reference]
      */
     _prepareNodes(length, parent) {
       const diff = length - parent.childElementCount;
-      let count = Math.abs(diff);
+
+      if (!diff) {
+        return;
+      }
 
       if (diff > 0) {
         // Add more elements if length is greater than current
         let frag = document.createDocumentFragment();
 
-        for (let i = 0; i < count; i++) {
+        // Remove margin bottom on the last element before appending
+        if (parent.lastChild) {
+          parent.lastChild.style.marginBottom = "";
+        }
+
+        for (let i = 0; i < diff; i++) {
           let el = document.createElement("div");
           frag.appendChild(el);
           this.elements.itemsViewElements.push(el);
         }
         parent.appendChild(frag);
       } else if (diff < 0) {
         // Remove elements if length is less than current
-        for (let i = 0; i < count; i++) {
+        for (let i = 0; i < Math.abs(diff); i++) {
           parent.removeChild(parent.lastChild);
         }
         this.elements.itemsViewElements.splice(diff);
       }
+
+      parent.lastChild.style.marginBottom =
+        (ITEM_HEIGHT * this.props.viewportTopOffset) + "rem";
     },
 
     /**
      * Set the display string and class name to the elements.
      *
      * @param {Array<Object>} items
      *        [{
      *          {Number/String} value: The value in its original form
@@ -255,37 +285,44 @@ function Spinner(props, context) {
       switch (event.type) {
         case "scroll": {
           this._onScroll();
           break;
         }
         case "mousedown": {
           // Use preventDefault to keep focus on input boxes
           event.preventDefault();
+          event.target.setCapture();
           this.state.mouseState = {
             down: true,
             layerX: event.layerX,
             layerY: event.layerY
           };
           if (event.target == up) {
+            // An "active" class is needed to simulate :active pseudo-class
+            // because element is not focused.
+            event.target.classList.add("active");
             this._smoothScrollToIndex(index + 1);
           }
           if (event.target == down) {
+            event.target.classList.add("active");
             this._smoothScrollToIndex(index - 1);
           }
           if (event.target.parentNode == spinner) {
             // Listen to dragging events
-            event.target.setCapture();
             spinner.addEventListener("mousemove", this, { passive: true });
             spinner.addEventListener("mouseleave", this, { passive: true });
           }
           break;
         }
         case "mouseup": {
           this.state.mouseState.down = false;
+          if (event.target == up || event.target == down) {
+            event.target.classList.remove("active");
+          }
           if (event.target.parentNode == spinner) {
             // Check if user clicks or drags, scroll to the item if clicked,
             // otherwise get the current index and smooth scroll there.
             if (event.layerX == mouseState.layerX && event.layerY == mouseState.layerY) {
               const newIndex = this._getIndexByOffset(event.target.offsetTop) - viewportTopOffset;
               if (index == newIndex) {
                 // Set value manually if the clicked element is already centered.
                 // This happens when the picker first opens, and user pick the
@@ -321,17 +358,17 @@ function Spinner(props, context) {
     },
 
     /**
      * Find the index by offset
      * @param {Number} offset: Offset value in pixel.
      * @return {Number}  Index number
      */
     _getIndexByOffset(offset) {
-      return Math.round(offset / this.props.itemHeight);
+      return Math.round(offset / (ITEM_HEIGHT * this.props.rootFontSize));
     },
 
     /**
      * Find the index of a value that is the closest to the current position.
      * If centering is true, find the index closest to the center.
      *
      * @param {Number/String} value: The value to find
      * @param {Boolean} centering: Whether or not to find the value closest to center
@@ -376,17 +413,17 @@ function Spinner(props, context) {
      * @param  {Number/String} value: Value to scroll to
      * @param  {Boolean} centering: Whether or not to scroll to center location
      */
     _scrollTo(value, centering) {
       const index = this._getScrollIndex(value, centering);
       // Do nothing if the value is not found
       if (index > -1) {
         this.state.index = index;
-        this.elements.spinner.scrollTop = this.state.index * this.props.itemHeight;
+        this.elements.spinner.scrollTop = this.state.index * ITEM_HEIGHT * this.props.rootFontSize;
       }
     },
 
     /**
      * Smooth scroll to a value.
      *
      * @param  {Number/String} value: Value to scroll to
      */
--- a/toolkit/content/widgets/timepicker.js
+++ b/toolkit/content/widgets/timepicker.js
@@ -1,21 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-'use strict';
+"use strict";
 
 function TimePicker(context) {
   this.context = context;
   this._attachEventListeners();
 }
 
 {
-  const debug = 0 ? console.log.bind(console, '[timepicker]') : function() {};
+  const debug = 0 ? console.log.bind(console, "[timepicker]") : function() {};
 
   const DAY_PERIOD_IN_HOURS = 12,
         SECOND_IN_MS = 1000,
         MINUTE_IN_MS = 60000,
         DAY_IN_MS = 86400000;
 
   TimePicker.prototype = {
     /**
@@ -56,19 +56,16 @@ function TimePicker(context) {
         min: this._parseTimeString(min) || new Date(0),
         max: this._parseTimeString(max) || new Date(DAY_IN_MS - 1),
         stepInMs: step ? step * SECOND_IN_MS : MINUTE_IN_MS,
         format: format || "12"
       });
       timeKeeper.setState({ hour: timerHour, minute: timerMinute });
 
       this.state = { timeKeeper };
-
-      // TODO: Resize picker based on zoom level
-      document.documentElement.style.fontSize = "10px";
     },
 
     /**
      * Convert a time string from DOM attribute to a date object.
      *
      * @param  {String} timeString: (ex. "10:30", "23:55", "12:34:56.789")
      * @return {Date/Boolean} Date object or false if date is invalid.
      */
@@ -113,30 +110,62 @@ function TimePicker(context) {
           setValue: wrapSetValueFn(value => {
             timeKeeper.setMinute(value);
             this.state.isMinuteSet = true;
           }),
           getDisplayString: minute => numberFormat(minute)
         }, this.context)
       };
 
+      this._insertLayoutElement({
+        tag: "div",
+        textContent: ":",
+        className: "colon",
+        insertBefore: this.components.minute.elements.container
+      });
+
       // The AM/PM spinner is only available in 12hr mode
       // TODO: Replace AM & PM string with localized string
       if (format == "12") {
         this.components.dayPeriod = new Spinner({
           setValue: wrapSetValueFn(value => {
             timeKeeper.setDayPeriod(value);
             this.state.isDayPeriodSet = true;
           }),
-          getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM"
+          getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
+          hideButtons: true
         }, this.context);
+
+        this._insertLayoutElement({
+          tag: "div",
+          className: "spacer",
+          insertBefore: this.components.dayPeriod.elements.container
+        });
       }
     },
 
     /**
+     * Insert element for layout purposes.
+     *
+     * @param {Object}
+     *        {
+     *          {String} tag: The tag to create
+     *          {DOMElement} insertBefore: The DOM node to insert before
+     *          {String} className [optional]: Class name
+     *          {String} textContent [optional]: Text content
+     *        }
+     */
+    _insertLayoutElement({ tag, insertBefore, className, textContent }) {
+      let el = document.createElement(tag);
+      el.textContent = textContent;
+      el.className = className;
+      this.context.insertBefore(el, insertBefore);
+    },
+
+    /**
      * Set component states.
      */
     _setComponentStates() {
       const { timeKeeper, isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
       const isInvalid = timeKeeper.state.isInvalid;
       // Value is set to min if it's first opened and time state is invalid
       const setToMinValue = !isHourSet && !isMinuteSet && !isDayPeriodSet && isInvalid;
 
@@ -183,17 +212,17 @@ function TimePicker(context) {
           minute,
           isHourSet,
           isMinuteSet,
           isDayPeriodSet
         }
       }, "*");
     },
     _attachEventListeners() {
-      window.addEventListener('message', this);
+      window.addEventListener("message", this);
     },
 
     /**
      * Handle events.
      *
      * @param  {Event} event
      */
     handleEvent(event) {
--- a/toolkit/locales/en-US/chrome/global/findbar.dtd
+++ b/toolkit/locales/en-US/chrome/global/findbar.dtd
@@ -4,16 +4,16 @@
 
 <!-- LOCALIZATION NOTE : FILE This file contains the entities needed to -->
 <!-- LOCALIZATION NOTE : FILE use the Find Bar. --> 
 
 <!ENTITY next.tooltip "Find the next occurrence of the phrase">
 <!ENTITY previous.tooltip "Find the previous occurrence of the phrase">
 <!ENTITY findCloseButton.tooltip "Close find bar">
 <!ENTITY highlightAll.label "Highlight All">
-<!ENTITY highlightAll.accesskey "a">
+<!ENTITY highlightAll.accesskey "l">
 <!ENTITY highlightAll.tooltiptext "Highlight all occurrences of the phrase">
 <!ENTITY caseSensitive.label "Match Case">
 <!ENTITY caseSensitive.accesskey "c">
 <!ENTITY caseSensitive.tooltiptext "Search with case sensitivity">
 <!ENTITY entireWord.label "Whole Words">
 <!ENTITY entireWord.accesskey "w">
 <!ENTITY entireWord.tooltiptext "Search whole words only">
--- a/toolkit/themes/shared/timepicker.css
+++ b/toolkit/themes/shared/timepicker.css
@@ -1,88 +1,153 @@
 /* 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/. */
 
 :root {
-  --font-size: 1.2rem;
-  --spinner-item-height: 2rem;
-  --spinner-width: 5rem;
-  --spinner-height: 10rem;
-  --scroller-width: 1.5rem;
-  --disabled-color: #ccc;
-  --selected-color: #fff;
-  --selected-bgcolor: #83BFFC;
-  --hover-bgcolor: #aaa;
-  --hover-outline: #999;
+  --font-size-default: 1.1rem;
+  --spinner-width: 3rem;
+  --spinner-margin-top-bottom: 0.4rem;
+  --spinner-item-height: 2.4rem;
+  --spinner-item-margin-bottom: 0.1rem;
+  --spinner-button-height: 1.2rem;
+  --colon-width: 2rem;
+  --day-period-spacing-width: 1rem;
+
+  --border: 0.1rem solid #D6D6D6;
+  --border-radius: 0.3rem;
+
+  --font-color: #191919;
+  --fill-color: #EBEBEB;
+
+  --selected-font-color: #FFFFFF;
+  --selected-fill-color: #0996F8;
+
+  --button-font-color: #858585;
+  --button-font-color-hover: #4D4D4D;
+  --button-font-color-active: #191919;
+
+  --disabled-opacity: 0.2;
+}
+
+html {
+  font-size: 10px;
 }
 
 body {
   margin: 0;
-  font-size: var(--font-size);
+  color: var(--font-color);
+  font-size: var(--font-size-default);
 }
 
 #time-picker {
   display: flex;
   flex-direction: row;
+  justify-content: space-around;
 }
 
 .spinner-container {
   font-family: sans-serif;
   display: flex;
   flex-direction: column;
   width: var(--spinner-width);
 }
 
-.spinner-container button {
+.spinner-container > button {
   -moz-appearance: none;
   border: none;
   background: none;
-  height: var(--spinner-item-height);
+  background-color: var(--button-font-color);
+  height: var(--spinner-button-height);
+}
+
+.spinner-container > button:hover {
+  background-color: var(--button-font-color-hover);
+}
+
+.spinner-container > button.active {
+  background-color: var(--button-font-color-active);
 }
 
-.spinner-container .stack {
-  position: relative;
-  height: var(--spinner-height);
+.spinner-container > button.up {
+  mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-previous") no-repeat 50% 50%;
+}
+
+.spinner-container > button.down {
+  mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-next") no-repeat 50% 50%;
 }
 
-.spinner-container .spinner {
-  position: absolute;
-  height: var(--spinner-height);
+.spinner-container.hide-buttons > button {
+  visibility: hidden;
+}
+
+.spinner-container > .spinner {
+  position: relative;
   width: 100%;
+  margin: var(--spinner-margin-top-bottom) 0;
   cursor: default;
   overflow-y: scroll;
   scroll-snap-type: mandatory;
   scroll-snap-points-y: repeat(100%);
 }
 
-.spinner-container .spinner > div {
+.spinner-container > .spinner > div {
+  box-sizing: border-box;
   position: relative;
   text-align: center;
-  padding: calc(var(--spinner-item-height) / 4) 0;
-  height: calc(var(--spinner-item-height) / 2);
+  padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0;
+  margin-bottom: var(--spinner-item-margin-bottom);
+  height: var(--spinner-item-height);
   -moz-user-select: none;
   scroll-snap-coordinate: 0 0;
 }
 
-.spinner-container .spinner > div:last-child {
-  margin-bottom: calc(var(--spinner-item-height) * 2);
-}
-
-.spinner-container .spinner > div.selection {
-  color: var(--selected-color);
-}
-
-.spinner-container .spinner > div.selection::before {
+.spinner-container > .spinner > div:hover::before {
+  background: var(--fill-color);
+  border: var(--border);
+  border-radius: var(--border-radius);
   content: "";
-  background: var(--selected-bgcolor);
   position: absolute;
-  top: 5%;
-  bottom: 5%;
-  left: 10%;
-  right: 10%;
-  border-radius: 5%;
+  top: 0%;
+  bottom: 0%;
+  left: 0%;
+  right: 0%;
   z-index: -10;
 }
 
-.spinner-container .spinner > div.disabled {
-  color: var(--disabled-color);
+.spinner-container > .spinner:not(.scrolling) > div.selection {
+  color: var(--selected-font-color);
+}
+
+.spinner-container > .spinner > div.selection::before {
+  background: var(--selected-fill-color);
+  border: none;
+  border-radius: var(--border-radius);
+  content: "";
+  position: absolute;
+  top: 0%;
+  bottom: 0%;
+  left: 0%;
+  right: 0%;
+  z-index: -10;
 }
+
+.spinner-container > .spinner > div.disabled::before,
+.spinner-container > .spinner.scrolling > div.selection::before,
+.spinner-container > .spinner.scrolling > div:hover::before {
+  display: none;
+}
+
+.spinner-container > .spinner > div.disabled {
+  opacity: var(--disabled-opacity);
+}
+
+.colon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: var(--colon-width);
+  margin-bottom: 0.3rem;
+}
+
+.spacer {
+  width: var(--day-period-spacing-width);
+}
\ No newline at end of file
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -579,27 +579,29 @@ LayerManager*
 PuppetWidget::GetLayerManager(PLayerTransactionChild* aShadowManager,
                               LayersBackend aBackendHint,
                               LayerManagerPersistence aPersistence)
 {
   if (!mLayerManager) {
     mLayerManager = new ClientLayerManager(this);
   }
   ShadowLayerForwarder* lf = mLayerManager->AsShadowForwarder();
-  if (!lf->HasShadowManager() && aShadowManager) {
+  if (lf && !lf->HasShadowManager() && aShadowManager) {
     lf->SetShadowManager(aShadowManager);
   }
   return mLayerManager;
 }
 
 LayerManager*
 PuppetWidget::RecreateLayerManager(PLayerTransactionChild* aShadowManager)
 {
   mLayerManager = new ClientLayerManager(this);
-  mLayerManager->AsShadowForwarder()->SetShadowManager(aShadowManager);
+  if (ShadowLayerForwarder* lf = mLayerManager->AsShadowForwarder()) {
+    lf->SetShadowManager(aShadowManager);
+  }
   return mLayerManager;
 }
 
 nsresult
 PuppetWidget::RequestIMEToCommitComposition(bool aCancel)
 {
 #ifdef MOZ_CROSS_PROCESS_IME
   if (!mTabChild) {
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -1326,45 +1326,52 @@ void nsBaseWidget::CreateCompositor(int 
 
   if (mInitialZoomConstraints) {
     UpdateZoomConstraints(mInitialZoomConstraints->mPresShellID,
                           mInitialZoomConstraints->mViewID,
                           Some(mInitialZoomConstraints->mConstraints));
     mInitialZoomConstraints.reset();
   }
 
-  TextureFactoryIdentifier textureFactoryIdentifier;
-  PLayerTransactionChild* shadowManager = nullptr;
-
-  nsTArray<LayersBackend> backendHints;
-  gfxPlatform::GetPlatform()->GetCompositorBackends(ComputeShouldAccelerate(), backendHints);
-
-  bool success = false;
-  if (!backendHints.IsEmpty()) {
-    shadowManager = mCompositorBridgeChild->SendPLayerTransactionConstructor(
-      backendHints, 0, &textureFactoryIdentifier, &success);
+  ShadowLayerForwarder* lf = lm->AsShadowForwarder();
+  // As long as we are creating a ClientLayerManager above lf must be non-null.
+  MOZ_ASSERT(lf);
+
+  if (lf) {
+    TextureFactoryIdentifier textureFactoryIdentifier;
+    PLayerTransactionChild* shadowManager = nullptr;
+
+    nsTArray<LayersBackend> backendHints;
+    gfxPlatform::GetPlatform()->GetCompositorBackends(ComputeShouldAccelerate(), backendHints);
+
+    bool success = false;
+    if (!backendHints.IsEmpty()) {
+      shadowManager = mCompositorBridgeChild->SendPLayerTransactionConstructor(
+        backendHints, 0, &textureFactoryIdentifier, &success);
+    }
+
+    if (!success) {
+      NS_WARNING("Failed to create an OMT compositor.");
+      DestroyCompositor();
+      mLayerManager = nullptr;
+      return;
+    }
+
+    lf->SetShadowManager(shadowManager);
+    if (ClientLayerManager* clm = lm->AsClientLayerManager()) {
+      clm->UpdateTextureFactoryIdentifier(textureFactoryIdentifier);
+    }
+    // Some popup or transparent widgets may use a different backend than the
+    // compositors used with ImageBridge and VR (and more generally web content).
+    if (WidgetTypeSupportsAcceleration()) {
+      ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
+      gfx::VRManagerChild::IdentifyTextureHost(textureFactoryIdentifier);
+    }
   }
 
-  ShadowLayerForwarder* lf = lm->AsShadowForwarder();
-
-  if (!success || !lf) {
-    NS_WARNING("Failed to create an OMT compositor.");
-    DestroyCompositor();
-    mLayerManager = nullptr;
-    return;
-  }
-
-  lf->SetShadowManager(shadowManager);
-  lm->UpdateTextureFactoryIdentifier(textureFactoryIdentifier);
-  // Some popup or transparent widgets may use a different backend than the
-  // compositors used with ImageBridge and VR (and more generally web content).
-  if (WidgetTypeSupportsAcceleration()) {
-    ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
-    gfx::VRManagerChild::IdentifyTextureHost(textureFactoryIdentifier);
-  }
   WindowUsesOMTC();
 
   mLayerManager = lm.forget();
 
   // Only track compositors for top-level windows, since other window types
   // may use the basic compositor.  Except on the OS X - see bug 1306383
 #if defined(XP_MACOSX)
   bool getCompositorFromThisWindow = true;