Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 18 Jul 2016 17:14:33 +0200
changeset 389164 7324716faa31f0e982a4c83144ea19ea84085197
parent 389163 102f746a6474f266c6ac3762c03d2f0d4ff32479 (current diff)
parent 389058 cde56ead650fd302be1d440507485b9abf7c163a (diff)
child 389165 3b0019807c250fd3ec18dd35a7e8e5d5522906de
push id23312
push userdmitchell@mozilla.com
push dateMon, 18 Jul 2016 17:58:50 +0000
milestone50.0a1
Merge mozilla-central to autoland
mobile/android/base/java/org/mozilla/gecko/widget/DividerItemDecoration.java
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -358,17 +358,17 @@ var FullScreen = {
         // target would be the browser which was the parameter of
         // `remoteFrameFullscreenChanged` call. If the fullscreen
         // request was initiated from an in-process browser, we need
         // to get its corresponding browser here.
         let browser;
         if (event.target == gBrowser) {
           browser = event.originalTarget;
         } else {
-          let topWin = event.target.ownerDocument.defaultView.top;
+          let topWin = event.target.ownerGlobal.top;
           browser = gBrowser.getBrowserForContentWindow(topWin);
         }
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
         this.enterDomFullscreen(browser);
         break;
       }
       case "MozDOMFullscreen:Exited":
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1161,17 +1161,17 @@ var PlacesToolbarHelper = {
       element = element.parentNode;
     }
     return null;
   },
 
   onWidgetUnderflow: function(aNode, aContainer) {
     // The view gets broken by being removed and reinserted by the overflowable
     // toolbar, so we have to force an uninit and reinit.
-    let win = aNode.ownerDocument.defaultView;
+    let win = aNode.ownerGlobal;
     if (aNode.id == "personal-bookmarks" && win == window) {
       this._resetView();
     }
   },
 
   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
     if (aWidgetId == "personal-bookmarks" && !this._isCustomizing) {
       // It's possible (with the "Add to Menu", "Add to Toolbar" context
@@ -1904,33 +1904,33 @@ var BookmarkingUI = {
       gNavigatorBundle.getString("starButtonOverflowed.label");
   },
   get _starButtonOverflowedStarredLabel() {
     delete this._starButtonOverflowedStarredLabel;
     return this._starButtonOverflowedStarredLabel =
       gNavigatorBundle.getString("starButtonOverflowedStarred.label");
   },
   onWidgetOverflow: function(aNode, aContainer) {
-    let win = aNode.ownerDocument.defaultView;
+    let win = aNode.ownerGlobal;
     if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
       return;
 
     let currentLabel = aNode.getAttribute("label");
     if (!this._starButtonLabel)
       this._starButtonLabel = currentLabel;
 
     if (currentLabel == this._starButtonLabel) {
       let desiredLabel = this._itemIds.length > 0 ? this._starButtonOverflowedStarredLabel
                                                  : this._starButtonOverflowedLabel;
       aNode.setAttribute("label", desiredLabel);
     }
   },
 
   onWidgetUnderflow: function(aNode, aContainer) {
-    let win = aNode.ownerDocument.defaultView;
+    let win = aNode.ownerGlobal;
     if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
       return;
 
     // The view gets broken by being removed and reinserted. Uninit
     // here so popupshowing will generate a new one:
     this._uninitView();
 
     if (aNode.getAttribute("label") != this._starButtonLabel)
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -178,17 +178,17 @@ SocialUI = {
   },
 
   closeSocialPanelForLinkTraversal: function (target, linkNode) {
     // No need to close the panel if this traversal was not retargeted
     if (target == "" || target == "_self")
       return;
 
     // Check to see whether this link traversal was in a social panel
-    let win = linkNode.ownerDocument.defaultView;
+    let win = linkNode.ownerGlobal;
     let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIWebNavigation)
                                   .QueryInterface(Ci.nsIDocShell)
                                   .chromeEventHandler;
     let containerParent = container.parentNode;
     if (containerParent.classList.contains("social-panel") &&
         containerParent instanceof Ci.nsIDOMXULPopupElement) {
       // allow the link traversal to finish before closing the panel
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4405,17 +4405,17 @@ var XULBrowserWindow = {
     let pageTooltip = document.getElementById("aHTMLTooltip");
     let tooltipNode = pageTooltip.triggerNode;
     if (tooltipNode) {
       // Optimise for the common case
       if (aWebProgress.isTopLevel) {
         pageTooltip.hidePopup();
       }
       else {
-        for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
+        for (let tooltipWindow = tooltipNode.ownerGlobal;
              tooltipWindow != tooltipWindow.parent;
              tooltipWindow = tooltipWindow.parent) {
           if (tooltipWindow == aWebProgress.DOMWindow) {
             pageTooltip.hidePopup();
             break;
           }
         }
       }
@@ -6036,17 +6036,17 @@ var IndexedDBPromptHelper = {
   function IndexedDBPromptHelper_observe(subject, topic, data) {
     if (topic != this._permissionsPrompt) {
       throw new Error("Unexpected topic!");
     }
 
     var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
 
     var browser = requestor.getInterface(Ci.nsIDOMNode);
-    if (browser.ownerDocument.defaultView != window) {
+    if (browser.ownerGlobal != window) {
       // Only listen for notifications for browsers in our chrome window.
       return;
     }
 
     var host = browser.currentURI.asciiHost;
 
     var message;
     var responseTopic;
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -199,32 +199,32 @@ var showContentContextMenu = function (e
         InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
     }
 
     // Set the event target first as the copy image command needs it to
     // determine what was context-clicked on. Then, update the state of the
     // commands on the context menu.
     docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
             .setCommandNode(event.target);
-    event.target.ownerDocument.defaultView.updateCommands("contentcontextmenu");
+    event.target.ownerGlobal.updateCommands("contentcontextmenu");
 
     let customMenuItems = PageMenuChild.build(event.target);
     let principal = doc.nodePrincipal;
     sendRpcMessage("contextmenu",
                    { editFlags, spellInfo, customMenuItems, addonInfo,
                      principal, docLocation, charSet, baseURI, referrer,
                      referrerPolicy, contentType, contentDisposition,
                      frameOuterWindowID, selectionInfo, disableSetDesktopBg,
                      loginFillInfo, parentAllowsMixedContent },
                    { event, popupNode: event.target });
   }
   else {
     // Break out to the parent window and pass the add-on info along
     let browser = docShell.chromeEventHandler;
-    let mainWin = browser.ownerDocument.defaultView;
+    let mainWin = browser.ownerGlobal;
     mainWin.gContextMenuContentData = {
       isRemote: false,
       event: event,
       popupNode: event.target,
       browser: browser,
       addonInfo: addonInfo,
       documentURIObject: doc.documentURIObject,
       docLocation: docLocation,
@@ -910,17 +910,17 @@ var LightWeightThemeWebInstallListener =
         });
         break;
       }
       case "PreviewBrowserTheme": {
         sendAsyncMessage("LightWeightThemeWebInstaller:Preview", {
           baseURI: event.target.baseURI,
           themeData: event.target.getAttribute("data-browsertheme"),
         });
-        this._previewWindow = event.target.ownerDocument.defaultView;
+        this._previewWindow = event.target.ownerGlobal;
         this._previewWindow.addEventListener("pagehide", this, true);
         break;
       }
       case "pagehide": {
         sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview");
         this._resetPreviewWindow();
         break;
       }
@@ -1165,17 +1165,17 @@ var PageInfoListener = {
     }
     // Send that page info media fetching has finished.
     sendAsyncMessage("PageInfo:mediaData", {isComplete: true});
   },
 
   getMediaItems: function(document, strings, elem)
   {
     // Check for images defined in CSS (e.g. background, borders)
-    let computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
+    let computedStyle = elem.ownerGlobal.getComputedStyle(elem);
     // A node can have multiple media items associated with it - for example,
     // multiple background images.
     let mediaItems = [];
 
     let addImage = (url, type, alt, elem, isBg) => {
       let element = this.serializeElementInfo(document, url, type, alt, elem, isBg);
       mediaItems.push([url, type, alt, element, isBg]);
     };
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -582,17 +582,17 @@ nsContextMenu.prototype = {
   },
 
   openPasswordManager: function() {
     LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
   },
 
   inspectNode: function() {
     let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-    let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
+    let gBrowser = this.browser.ownerGlobal.gBrowser;
     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
 
     return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       let inspector = toolbox.getCurrentPanel();
 
       // new-node-front tells us when the node has been selected, whether the
       // browser is remote or not.
       let onNewNode = inspector.selection.once("new-node-front");
--- a/browser/base/content/test/general/browser_bug431826.js
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -20,33 +20,33 @@ add_task(function* () {
   });
 
   let advancedDiv, advancedDivVisibility, technicalDivCollapsed;
 
   yield remote(() => {
     let div = content.document.getElementById("badCertAdvancedPanel");
     // Confirm that the expert section is collapsed
     Assert.ok(div, "Advanced content div should exist");
-    Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").display,
+    Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
       "none", "Advanced content should not be visible by default");
   });
 
   // Tweak the expert mode pref
   gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
 
   promise = remote(function () {
     return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
   });
   gBrowser.reload();
   yield promise;
 
   yield remote(() => {
     let div = content.document.getElementById("badCertAdvancedPanel");
     Assert.ok(div, "Advanced content div should exist");
-    Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").display,
+    Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
       "block", "Advanced content should be visible by default");
   });
 
   // Clean up
   gBrowser.removeCurrentTab();
   if (gPrefService.prefHasUserValue("browser.xul.error_pages.expert_bad_cert"))
     gPrefService.clearUserPref("browser.xul.error_pages.expert_bad_cert");
 });
--- a/browser/base/content/test/general/browser_trackingUI_1.js
+++ b/browser/base/content/test/general/browser_trackingUI_1.js
@@ -108,29 +108,29 @@ function testTrackingPageUnblocked() {
 
 function* testTrackingProtectionForTab(tab) {
   info("Load a test page not containing tracking elements");
   yield promiseTabLoadEvent(tab, BENIGN_PAGE);
   testBenignPage();
 
   info("Load a test page containing tracking elements");
   yield promiseTabLoadEvent(tab, TRACKING_PAGE);
-  testTrackingPage(tab.ownerDocument.defaultView);
+  testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   yield tabReloadPromise;
   testTrackingPageUnblocked();
 
   info("Re-enable TP for the page (which reloads the page)");
   tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-block");
   yield tabReloadPromise;
-  testTrackingPage(tab.ownerDocument.defaultView);
+  testTrackingPage(tab.ownerGlobal);
 }
 
 add_task(function* testNormalBrowsing() {
   yield UrlClassifierTestUtils.addTestTrackers();
 
   tabbrowser = gBrowser;
   let tab = tabbrowser.selectedTab = tabbrowser.addTab();
 
--- a/browser/base/content/test/general/browser_trackingUI_2.js
+++ b/browser/base/content/test/general/browser_trackingUI_2.js
@@ -20,17 +20,17 @@ registerCleanupFunction(function() {
   Services.prefs.clearUserPref(PREF);
   Services.prefs.clearUserPref(PB_PREF);
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 function hidden(el) {
-  let win = el.ownerDocument.defaultView;
+  let win = el.ownerGlobal;
   let display = win.getComputedStyle(el).getPropertyValue("display", null);
   let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
 
   return display === "none" || opacity === "0";
 }
 
 add_task(function* testNormalBrowsing() {
   yield UrlClassifierTestUtils.addTestTrackers();
--- a/browser/base/content/test/general/browser_trackingUI_5.js
+++ b/browser/base/content/test/general/browser_trackingUI_5.js
@@ -80,17 +80,17 @@ add_task(function* testExceptionAddition
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   yield pushPrefs([PB_PREF, true]);
 
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
 
   info("Load a test page containing tracking elements");
   yield promiseTabLoadEvent(tab, TRACKING_PAGE);
 
-  testTrackingPage(tab.ownerDocument.defaultView);
+  testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "foobar");
 
   yield tabReloadPromise;
   testTrackingPageUnblocked();
@@ -112,17 +112,17 @@ add_task(function* testExceptionPersiste
   let tab = browser.selectedTab = browser.addTab();
 
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection.enabled, "TP is still enabled");
 
   info("Load a test page containing tracking elements");
   yield promiseTabLoadEvent(tab, TRACKING_PAGE);
 
-  testTrackingPage(tab.ownerDocument.defaultView);
+  testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "foobar");
 
   yield tabReloadPromise;
   testTrackingPageUnblocked();
--- a/browser/base/content/test/general/contextmenu_common.js
+++ b/browser/base/content/test/general/contextmenu_common.js
@@ -8,17 +8,17 @@ function openContextMenuFor(element, shi
       lastElement.blur();
     element.focus();
 
     // Some elements need time to focus and spellcheck before any tests are
     // run on them.
     function actuallyOpenContextMenuFor() {
       lastElement = element;
       var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
-      synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+      synthesizeMouse(element, 2, 2, eventDetails, element.ownerGlobal);
     }
 
     if (waitForSpellCheck) {
       var { onSpellCheck } = SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {});
       onSpellCheck(element, actuallyOpenContextMenuFor);
     }
     else {
       actuallyOpenContextMenuFor();
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -693,17 +693,17 @@ function assertMixedContentBlockingState
   }
 
   let {passiveLoaded,activeLoaded,activeBlocked} = states;
   let {gIdentityHandler} = tabbrowser.ownerGlobal;
   let doc = tabbrowser.ownerDocument;
   let identityBox = gIdentityHandler._identityBox;
   let classList = identityBox.classList;
   let connectionIcon = doc.getElementById("connection-icon");
-  let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon, "").
+  let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon).
                          getPropertyValue("list-style-image");
 
   let stateSecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
   let stateBroken = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
   let stateInsecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE;
   let stateActiveBlocked = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
   let stateActiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
   let statePassiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
@@ -770,19 +770,19 @@ function assertMixedContentBlockingState
   is(popupAttr.includes("passive-loaded"), passiveLoaded,
       "identity-popup has expected attr for passiveLoaded");
   is(bodyAttr.includes("passive-loaded"), passiveLoaded,
       "securityView-body has expected attr for passiveLoaded");
 
   // Make sure the correct icon is visible in the Control Center.
   // This logic is controlled with CSS, so this helps prevent regressions there.
   let securityView = doc.getElementById("identity-popup-securityView");
-  let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
+  let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
                        getPropertyValue("background-image");
-  let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
+  let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
                           getPropertyValue("background-image");
 
   if (stateInsecure) {
     is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
       "CC using 'not secure' icon");
     is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
       "CC using 'not secure' icon");
   }
@@ -826,33 +826,33 @@ function assertMixedContentBlockingState
   // Wait for the panel to be closed before continuing. The promisePopupHidden
   // function cannot be used because it's unreliable unless promisePopupShown is
   // also called before closing the panel. This cannot be done until all callers
   // are made asynchronous (bug 1221114).
   return new Promise(resolve => executeSoon(resolve));
 }
 
 function is_hidden(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
   if (style.display == "-moz-popup")
     return ["hiding","closed"].indexOf(element.state) != -1;
 
   // Hiding a parent element will hide all its children
   if (element.parentNode != element.ownerDocument)
     return is_hidden(element.parentNode);
 
   return false;
 }
 
 function is_visible(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return false;
   if (style.visibility != "visible")
     return false;
   if (style.display == "-moz-popup" && element.state != "open")
     return false;
 
   // Hiding a parent element will hide all its children
@@ -892,17 +892,17 @@ function promisePopupShown(popup) {
   return promisePopupEvent(popup, "shown");
 }
 
 function promisePopupHidden(popup) {
   return promisePopupEvent(popup, "hidden");
 }
 
 function promiseNotificationShown(notification) {
-  let win = notification.browser.ownerDocument.defaultView;
+  let win = notification.browser.ownerGlobal;
   if (win.PopupNotifications.panel.state == "open") {
     return Promise.resolve();
   }
   let panelPromise = promisePopupShown(win.PopupNotifications.panel);
   notification.reshow();
   return panelPromise;
 }
 
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -245,33 +245,33 @@ function makeActionURI(action, params) {
   for (let key in params) {
     encodedParams[key] = encodeURIComponent(params[key]);
   }
   let url = "moz-action:" + action + "," + JSON.stringify(encodedParams);
   return NetUtil.newURI(url);
 }
 
 function is_hidden(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
   if (style.display == "-moz-popup")
     return ["hiding","closed"].indexOf(element.state) != -1;
 
   // Hiding a parent element will hide all its children
   if (element.parentNode != element.ownerDocument)
     return is_hidden(element.parentNode);
 
   return false;
 }
 
 function is_visible(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return false;
   if (style.visibility != "visible")
     return false;
   if (style.display == "-moz-popup" && element.state != "open")
     return false;
 
   // Hiding a parent element will hide all its children
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -160,17 +160,17 @@ function promisePopupEvent(popup, eventS
     popup.removeEventListener(eventType, onPopupShown);
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function promiseNotificationShown(notification) {
-  let win = notification.browser.ownerDocument.defaultView;
+  let win = notification.browser.ownerGlobal;
   if (win.PopupNotifications.panel.state == "open") {
     return Promise.resolve();
   }
   let panelPromise = promisePopupEvent(win.PopupNotifications.panel, "shown");
   notification.reshow();
   return panelPromise;
 }
 
--- a/browser/base/content/webrtcIndicator.js
+++ b/browser/base/content/webrtcIndicator.js
@@ -121,17 +121,17 @@ function onPopupMenuCommand(event) {
   let item = event.target;
   webrtcUI.showSharingDoorhanger(item.stream,
                                  item.parentNode.getAttribute("type"));
 }
 
 function onFirefoxButtonClick(event) {
   event.target.blur();
   let activeStreams = webrtcUI.getActiveStreams(true, true, true);
-  activeStreams[0].browser.ownerDocument.defaultView.focus();
+  activeStreams[0].browser.ownerGlobal.focus();
 }
 
 var PositionHandler = {
   positionCustomized: false,
   threshold: 10,
   adjustPosition: function() {
     if (!this.positionCustomized) {
       // Center the window horizontally on the screen (not the available area).
--- a/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
+++ b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
@@ -7,17 +7,17 @@ const URI = BASE_ORIGIN +
 // opens `uri' in a new tab with the provided userContextId and focuses it.
 // returns the newly opened tab
 function* openTabInUserContext(uri, userContextId) {
   // open the tab in the correct userContextId
   let tab = gBrowser.addTab(uri, {userContextId});
 
   // select tab and make sure its browser is focused
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   yield BrowserTestUtils.browserLoaded(browser);
   return {tab, browser};
 }
 
 add_task(function* setup() {
   // make sure userContext is enabled.
--- a/browser/components/contextualidentity/test/browser/browser_favicon.js
+++ b/browser/components/contextualidentity/test/browser/browser_favicon.js
@@ -32,17 +32,17 @@ function getIconFile() {
 }
 
 function* openTabInUserContext(uri, userContextId) {
   // open the tab in the correct userContextId
   let tab = gBrowser.addTab(uri, {userContextId});
 
   // select tab and make sure its browser is focused
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   yield BrowserTestUtils.browserLoaded(browser);
   return {tab, browser};
 }
 
 function loadIndexHandler(metadata, response) {
   response.setStatusLine(metadata.httpVersion, 200, "Ok");
--- a/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
+++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
@@ -56,17 +56,17 @@ function loadImagePageHandler(metadata, 
 }
 
 function* openTabInUserContext(uri, userContextId) {
   // Open the tab in the correct userContextId.
   let tab = gBrowser.addTab(uri, {userContextId});
 
   // Select tab and make sure its browser is focused.
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   yield BrowserTestUtils.browserLoaded(browser);
   return {tab, browser};
 }
 
 function getCookiesForOA(host, userContextId) {
   return Services.cookies.getCookiesFromHost(host, {userContextId});
--- a/browser/components/contextualidentity/test/browser/browser_serviceworkers.js
+++ b/browser/components/contextualidentity/test/browser/browser_serviceworkers.js
@@ -11,17 +11,17 @@ const NUM_USER_CONTEXTS = 3;
 // opens `uri' in a new tab with the provided userContextId and focuses it.
 // returns the newly opened tab
 function openTabInUserContext(uri, userContextId) {
   // open the tab in the correct userContextId
   let tab = gBrowser.addTab(uri, {userContextId});
 
   // select tab and make sure its browser is focused
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   return tab;
 }
 
 add_task(function* setup() {
   // make sure userContext is enabled.
   yield new Promise(resolve => {
     SpecialPowers.pushPrefEnv({"set": [
--- a/browser/components/contextualidentity/test/browser/browser_usercontext.js
+++ b/browser/components/contextualidentity/test/browser/browser_usercontext.js
@@ -15,17 +15,17 @@ const BASE_URI = "http://mochi.test:8888
 // opens `uri' in a new tab with the provided userContextId and focuses it.
 // returns the newly opened tab
 function openTabInUserContext(uri, userContextId) {
   // open the tab in the correct userContextId
   let tab = gBrowser.addTab(uri, {userContextId});
 
   // select tab and make sure its browser is focused
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   return tab;
 }
 
 add_task(function* setup() {
   // make sure userContext is enabled.
   yield new Promise(resolve => {
     SpecialPowers.pushPrefEnv({"set": [
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -814,27 +814,27 @@ var CustomizableUIInternal = {
     } finally {
       this.endBatchUpdate();
     }
   },
 
   addPanelCloseListeners: function(aPanel) {
     gELS.addSystemEventListener(aPanel, "click", this, false);
     gELS.addSystemEventListener(aPanel, "keypress", this, false);
-    let win = aPanel.ownerDocument.defaultView;
+    let win = aPanel.ownerGlobal;
     if (!gPanelsForWindow.has(win)) {
       gPanelsForWindow.set(win, new Set());
     }
     gPanelsForWindow.get(win).add(this._getPanelForNode(aPanel));
   },
 
   removePanelCloseListeners: function(aPanel) {
     gELS.removeSystemEventListener(aPanel, "click", this, false);
     gELS.removeSystemEventListener(aPanel, "keypress", this, false);
-    let win = aPanel.ownerDocument.defaultView;
+    let win = aPanel.ownerGlobal;
     let panels = gPanelsForWindow.get(win);
     if (panels) {
       panels.delete(this._getPanelForNode(aPanel));
     }
   },
 
   ensureButtonContextMenu: function(aNode, aAreaNode) {
     const kPanelItemContextMenu = "customizationPanelItemContextMenu";
@@ -955,17 +955,17 @@ var CustomizableUIInternal = {
     let area = gAreas.get(aArea);
     let isToolbar = area.get("type") == CustomizableUI.TYPE_TOOLBAR;
     let isOverflowable = isToolbar && area.get("overflowable");
     let showInPrivateBrowsing = gPalette.has(aWidgetId)
                               ? gPalette.get(aWidgetId).showInPrivateBrowsing
                               : true;
 
     for (let areaNode of areaNodes) {
-      let window = areaNode.ownerDocument.defaultView;
+      let window = areaNode.ownerGlobal;
       if (!showInPrivateBrowsing &&
           PrivateBrowsingUtils.isWindowPrivate(window)) {
         continue;
       }
 
       let container = areaNode.customizationTarget;
       let widgetNode = window.document.getElementById(aWidgetId);
       if (widgetNode && isOverflowable) {
@@ -1015,17 +1015,17 @@ var CustomizableUIInternal = {
 
   onCustomizeEnd: function(aWindow) {
     this._clearPreviousUIState();
   },
 
   registerBuildArea: function(aArea, aNode) {
     // We ensure that the window is registered to have its customization data
     // cleaned up when unloading.
-    let window = aNode.ownerDocument.defaultView;
+    let window = aNode.ownerGlobal;
     if (window.closed) {
       return;
     }
     this.registerBuildWindow(window);
 
     // Also register this build area's toolbox.
     if (aNode.toolbox) {
       gBuildWindows.get(window).add(aNode.toolbox);
@@ -1133,17 +1133,17 @@ var CustomizableUIInternal = {
     // Go through each of the nodes associated with this area and move the
     // widget to the requested location.
     for (let areaNode of areaNodes) {
       this.insertNodeInWindow(aWidgetId, areaNode, isNew);
     }
   },
 
   insertNodeInWindow: function(aWidgetId, aAreaNode, isNew) {
-    let window = aAreaNode.ownerDocument.defaultView;
+    let window = aAreaNode.ownerGlobal;
     let showInPrivateBrowsing = gPalette.has(aWidgetId)
                               ? gPalette.get(aWidgetId).showInPrivateBrowsing
                               : true;
 
     if (!showInPrivateBrowsing && PrivateBrowsingUtils.isWindowPrivate(window)) {
       return;
     }
 
@@ -1493,17 +1493,17 @@ var CustomizableUIInternal = {
         }
       } else {
         //XXXunf Need to think this through more, and formalize.
         Services.obs.notifyObservers(aNode,
                                      "customizedui-widget-command",
                                      aWidget.id);
       }
     } else if (aWidget.type == "view") {
-      let ownerWindow = aNode.ownerDocument.defaultView;
+      let ownerWindow = aNode.ownerGlobal;
       let area = this.getPlacementOfWidget(aNode.id).area;
       let anchor = aNode;
       if (area != CustomizableUI.AREA_PANEL) {
         let wrapper = this.wrapWidget(aWidget.id).forWindow(ownerWindow);
         if (wrapper && wrapper.anchor) {
           this.hidePanelForNode(aNode);
           anchor = wrapper.anchor;
         }
@@ -1713,17 +1713,17 @@ var CustomizableUIInternal = {
       }
     }
 
     // If we get here, we can actually hide the popup:
     this.hidePanelForNode(aEvent.target);
   },
 
   getUnusedWidgets: function(aWindowPalette) {
-    let window = aWindowPalette.ownerDocument.defaultView;
+    let window = aWindowPalette.ownerGlobal;
     let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
     // We use a Set because there can be overlap between the widgets in
     // gPalette and the items in the palette, especially after the first
     // customization, since programmatically generated widgets will remain
     // in the toolbox palette.
     let widgets = new Set();
 
     // It's possible that some widgets have been defined programmatically and
@@ -2493,17 +2493,17 @@ var CustomizableUIInternal = {
 
   getCustomizeTargetForArea: function(aArea, aWindow) {
     let buildAreaNodes = gBuildAreas.get(aArea);
     if (!buildAreaNodes) {
       return null;
     }
 
     for (let node of buildAreaNodes) {
-      if (node.ownerDocument.defaultView === aWindow) {
+      if (node.ownerGlobal == aWindow) {
         return node.customizationTarget ? node.customizationTarget : node;
       }
     }
 
     return null;
   },
 
   reset: function() {
@@ -2579,17 +2579,17 @@ var CustomizableUIInternal = {
       let placements = gPlacements.get(areaId);
       let isFirstChangedToolbar = true;
       for (let areaNode of areaNodes) {
         this.buildArea(areaId, placements, areaNode);
 
         let area = gAreas.get(areaId);
         if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
           let defaultCollapsed = area.get("defaultCollapsed");
-          let win = areaNode.ownerDocument.defaultView;
+          let win = areaNode.ownerGlobal;
           if (defaultCollapsed !== null) {
             win.setToolbarVisibility(areaNode, !defaultCollapsed, isFirstChangedToolbar);
           }
         }
         isFirstChangedToolbar = false;
       }
     }
   },
@@ -2705,17 +2705,17 @@ var CustomizableUIInternal = {
     let placement = this.getPlacementOfWidget(aWidgetId);
     if (!placement) {
       return false;
     }
     let areaNodes = gBuildAreas.get(placement.area);
     if (!areaNodes) {
       return false;
     }
-    let container = [...areaNodes].filter((n) => n.ownerDocument.defaultView == aWindow);
+    let container = [...areaNodes].filter((n) => n.ownerGlobal == aWindow);
     if (!container.length) {
       return false;
     }
     let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0];
     if (existingNode) {
       return true;
     }
 
@@ -3808,17 +3808,17 @@ function WidgetGroupWrapper(aWidget) {
     if (!placement) {
       return [];
     }
     let area = placement.area;
     let buildAreas = gBuildAreas.get(area);
     if (!buildAreas) {
       return [];
     }
-    return Array.from(buildAreas, (node) => this.forWindow(node.ownerDocument.defaultView));
+    return Array.from(buildAreas, (node) => this.forWindow(node.ownerGlobal));
   });
 
   this.__defineGetter__("areaType", function() {
     let areaProps = gAreas.get(aWidget.currentArea);
     return areaProps && areaProps.get("type");
   });
 
   Object.freeze(this);
@@ -3952,17 +3952,17 @@ function XULWidgetSingleWrapper(aWidgetI
       return null;
     }
     if (aNode) {
       // Return the last known node if it's still in the DOM...
       if (aNode.ownerDocument.contains(aNode)) {
         return aNode;
       }
       // ... or the toolbox
-      let toolbox = aNode.ownerDocument.defaultView.gNavToolbox;
+      let toolbox = aNode.ownerGlobal.gNavToolbox;
       if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
         return aNode;
       }
       // If it isn't, clear the cached value and fall through to the "slow" case:
       aNode = null;
     }
 
     let doc = weakDoc.get();
@@ -4013,31 +4013,31 @@ function OverflowableToolbar(aToolbarNod
 
   this._toolbar.setAttribute("overflowable", "true");
   let doc = this._toolbar.ownerDocument;
   this._target = this._toolbar.customizationTarget;
   this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
   this._list.toolbox = this._toolbar.toolbox;
   this._list.customizationTarget = this._list;
 
-  let window = this._toolbar.ownerDocument.defaultView;
+  let window = this._toolbar.ownerGlobal;
   if (window.gBrowserInit.delayedStartupFinished) {
     this.init();
   } else {
     Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
   }
 }
 
 OverflowableToolbar.prototype = {
   initialized: false,
   _forceOnOverflow: false,
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "browser-delayed-startup-finished" &&
-        aSubject == this._toolbar.ownerDocument.defaultView) {
+        aSubject == this._toolbar.ownerGlobal) {
       Services.obs.removeObserver(this, "browser-delayed-startup-finished");
       this.init();
     }
   },
 
   init: function() {
     let doc = this._toolbar.ownerDocument;
     let window = doc.defaultView;
@@ -4074,17 +4074,17 @@ OverflowableToolbar.prototype = {
 
     if (!this.initialized) {
       Services.obs.removeObserver(this, "browser-delayed-startup-finished");
       return;
     }
 
     this._disable();
 
-    let window = this._toolbar.ownerDocument.defaultView;
+    let window = this._toolbar.ownerGlobal;
     window.removeEventListener("resize", this);
     window.gNavToolbox.removeEventListener("customizationstarting", this);
     window.gNavToolbox.removeEventListener("aftercustomization", this);
     this._chevron.removeEventListener("command", this);
     this._chevron.removeEventListener("dragover", this);
     this._chevron.removeEventListener("dragend", this);
     this._panel.removeEventListener("popuphiding", this);
     CustomizableUI.removeListener(this);
@@ -4185,17 +4185,17 @@ OverflowableToolbar.prototype = {
         if (!this._toolbar.hasAttribute("overflowing")) {
           CustomizableUI.addListener(this);
         }
         this._toolbar.setAttribute("overflowing", "true");
       }
       child = prevChild;
     }
 
-    let win = this._target.ownerDocument.defaultView;
+    let win = this._target.ownerGlobal;
     win.UpdateUrlbarSearchSplitterState();
   },
 
   _onResize: function(aEvent) {
     if (!this._lazyResizeHandler) {
       this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
                                                  LAZY_RESIZE_INTERVAL_MS);
     }
@@ -4234,17 +4234,17 @@ OverflowableToolbar.prototype = {
       if (!inserted) {
         this._target.appendChild(child);
       }
       child.removeAttribute("cui-anchorid");
       child.removeAttribute("overflowedItem");
       CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
     }
 
-    let win = this._target.ownerDocument.defaultView;
+    let win = this._target.ownerGlobal;
     win.UpdateUrlbarSearchSplitterState();
 
     if (!this._collapsed.size) {
       this._toolbar.removeAttribute("overflowing");
       CustomizableUI.removeListener(this);
     }
   },
 
@@ -4393,17 +4393,17 @@ OverflowableToolbar.prototype = {
       return this._list;
     }
     return this._target;
   },
 
   _hideTimeoutId: null,
   _showWithTimeout: function() {
     this.show().then(function () {
-      let window = this._toolbar.ownerDocument.defaultView;
+      let window = this._toolbar.ownerGlobal;
       if (this._hideTimeoutId) {
         window.clearTimeout(this._hideTimeoutId);
       }
       this._hideTimeoutId = window.setTimeout(() => {
         if (!this._panel.firstChild.matches(":hover")) {
           this._panel.hidePopup();
         }
       }, OVERFLOW_PANEL_HIDE_DELAY_MS);
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -499,58 +499,53 @@ const CustomizableWidgets = [
       });
       return item;
     },
   }, {
     id: "privatebrowsing-button",
     shortcutId: "key_privatebrowsing",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(e) {
-      if (e.target && e.target.ownerDocument && e.target.ownerDocument.defaultView) {
-        let win = e.target.ownerDocument.defaultView;
-        if (typeof win.OpenBrowserWindow == "function") {
-          win.OpenBrowserWindow({private: true});
-        }
+      let win = e.target && e.target.ownerGlobal;
+      if (win && typeof win.OpenBrowserWindow == "function") {
+        win.OpenBrowserWindow({private: true});
       }
     }
   }, {
     id: "save-page-button",
     shortcutId: "key_savePage",
     tooltiptext: "save-page-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
-                aEvent.target.ownerDocument &&
-                aEvent.target.ownerDocument.defaultView;
+                aEvent.target.ownerGlobal;
       if (win && typeof win.saveBrowser == "function") {
         win.saveBrowser(win.gBrowser.selectedBrowser);
       }
     }
   }, {
     id: "find-button",
     shortcutId: "key_find",
     tooltiptext: "find-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
-                aEvent.target.ownerDocument &&
-                aEvent.target.ownerDocument.defaultView;
+                aEvent.target.ownerGlobal;
       if (win && win.gFindBar) {
         win.gFindBar.onFindCommand();
       }
     }
   }, {
     id: "open-file-button",
     shortcutId: "openFileKb",
     tooltiptext: "open-file-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target
-                && aEvent.target.ownerDocument
-                && aEvent.target.ownerDocument.defaultView;
+                && aEvent.target.ownerGlobal;
       if (win && typeof win.BrowserOpenFileWindow == "function") {
         win.BrowserOpenFileWindow();
       }
     }
   }, {
     id: "sidebar-button",
     type: "view",
     viewId: "PanelUI-sidebar",
@@ -618,18 +613,17 @@ const CustomizableWidgets = [
     }
   }, {
     id: "add-ons-button",
     shortcutId: "key_openAddons",
     tooltiptext: "add-ons-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
-                aEvent.target.ownerDocument &&
-                aEvent.target.ownerDocument.defaultView;
+                aEvent.target.ownerGlobal;
       if (win && typeof win.BrowserOpenAddonsMgr == "function") {
         win.BrowserOpenAddonsMgr();
       }
     }
   }, {
     id: "zoom-controls",
     type: "custom",
     tooltiptext: "zoom-controls.tooltiptext2",
@@ -895,17 +889,17 @@ const CustomizableWidgets = [
   },
   {
     id: "feed-button",
     type: "view",
     viewId: "PanelUI-feeds",
     tooltiptext: "feed-button.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onClick: function(aEvent) {
-      let win = aEvent.target.ownerDocument.defaultView;
+      let win = aEvent.target.ownerGlobal;
       let feeds = win.gBrowser.selectedBrowser.feeds;
 
       // Here, we only care about the case where we have exactly 1 feed and the
       // user clicked...
       let isClick = (aEvent.button == 0 || aEvent.button == 1);
       if (feeds && feeds.length == 1 && isClick) {
         aEvent.preventDefault();
         aEvent.stopPropagation();
@@ -921,17 +915,17 @@ const CustomizableWidgets = [
       // For no feeds or only a single one, don't show the panel.
       if (!gotView) {
         aEvent.preventDefault();
         aEvent.stopPropagation();
         return;
       }
     },
     onCreated: function(node) {
-      let win = node.ownerDocument.defaultView;
+      let win = node.ownerGlobal;
       let selectedBrowser = win.gBrowser.selectedBrowser;
       let feeds = selectedBrowser && selectedBrowser.feeds;
       if (!feeds || !feeds.length) {
         node.setAttribute("disabled", "true");
       }
     }
   }, {
     id: "characterencoding-button",
@@ -1022,17 +1016,17 @@ const CustomizableWidgets = [
       this.updateCurrentCharset(document);
     },
     onCommand: function(aEvent) {
       let node = aEvent.target;
       if (!node.hasAttribute || !node.section) {
         return;
       }
 
-      let window = node.ownerDocument.defaultView;
+      let window = node.ownerGlobal;
       let section = node.section;
       let value = node.value;
 
       // The behavior as implemented here is directly based off of the
       // `MultiplexHandler()` method in browser.js.
       if (section != "detectors") {
         window.BrowserSetForcedCharacterSet(value);
       } else {
@@ -1156,18 +1150,17 @@ if (Services.prefs.getBoolPref("privacy.
   });
 }
 
 let preferencesButton = {
   id: "preferences-button",
   defaultArea: CustomizableUI.AREA_PANEL,
   onCommand: function(aEvent) {
     let win = aEvent.target &&
-              aEvent.target.ownerDocument &&
-              aEvent.target.ownerDocument.defaultView;
+              aEvent.target.ownerGlobal;
     if (win && typeof win.openPreferences == "function") {
       win.openPreferences();
     }
   }
 };
 if (AppConstants.platform == "win") {
   preferencesButton.label = "preferences-button.labelWin";
   preferencesButton.tooltiptext = "preferences-button.tooltipWin2";
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -1238,34 +1238,34 @@ CustomizeMode.prototype = {
     this._onUIChange();
   },
 
   onWidgetRemoved: function(aWidgetId, aArea) {
     this._onUIChange();
   },
 
   onWidgetBeforeDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
-    if (aContainer.ownerDocument.defaultView != this.window || this.resetting) {
+    if (aContainer.ownerGlobal != this.window || this.resetting) {
       return;
     }
     if (aContainer.id == CustomizableUI.AREA_PANEL) {
       this._removePanelCustomizationPlaceholders();
     }
     // If we get called for widgets that aren't in the window yet, they might not have
     // a parentNode at all.
     if (aNodeToChange.parentNode) {
       this.unwrapToolbarItem(aNodeToChange.parentNode);
     }
     if (aSecondaryNode) {
       this.unwrapToolbarItem(aSecondaryNode.parentNode);
     }
   },
 
   onWidgetAfterDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
-    if (aContainer.ownerDocument.defaultView != this.window || this.resetting) {
+    if (aContainer.ownerGlobal != this.window || this.resetting) {
       return;
     }
     // If the node is still attached to the container, wrap it again:
     if (aNodeToChange.parentNode) {
       let place = CustomizableUI.getPlaceForItem(aNodeToChange);
       this.wrapToolbarItem(aNodeToChange, place);
       if (aSecondaryNode) {
         this.wrapToolbarItem(aSecondaryNode, place);
@@ -1692,17 +1692,17 @@ CustomizeMode.prototype = {
                                          targetParent.lastChild;
         dragValue = "after";
       } else {
         dragOverItem = targetParent.children[position];
         if (!targetIsToolbar) {
           dragValue = "before";
         } else {
           // Check if the aDraggedItem is hovered past the first half of dragOverItem
-          let window = dragOverItem.ownerDocument.defaultView;
+          let window = dragOverItem.ownerGlobal;
           let direction = window.getComputedStyle(dragOverItem, null).direction;
           let itemRect = dragOverItem.getBoundingClientRect();
           let dropTargetCenter = itemRect.left + (itemRect.width / 2);
           let existingDir = dragOverItem.getAttribute("dragover");
           if ((existingDir == "before") == (direction == "ltr")) {
             dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
           } else {
             dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2;
@@ -1981,28 +1981,28 @@ CustomizeMode.prototype = {
     }
 
     /* Discard drag events that originated from a separate window to
        prevent content->chrome privilege escalations. */
     let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
     // mozSourceNode is null in the dragStart event handler or if
     // the drag event originated in an external application.
     return !mozSourceNode ||
-           mozSourceNode.ownerDocument.defaultView != this.window;
+           mozSourceNode.ownerGlobal != this.window;
   },
 
   _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
     if (!aItem) {
       return;
     }
 
     if (aItem.getAttribute("dragover") != aValue) {
       aItem.setAttribute("dragover", aValue);
 
-      let window = aItem.ownerDocument.defaultView;
+      let window = aItem.ownerGlobal;
       let draggedItem = window.document.getElementById(aDraggedItemId);
       if (!aInToolbar) {
         this._setGridDragActive(aItem, draggedItem, aValue);
       } else {
         let targetArea = this._getCustomizableParent(aItem);
         this._updateToolbarCustomizationOutline(window, targetArea);
         let makeSpaceImmediately = false;
         if (!gDraggingInToolbars.has(targetArea.id)) {
@@ -2033,17 +2033,17 @@ CustomizeMode.prototype = {
           // Force a layout flush:
           aItem.getBoundingClientRect();
           aItem.removeAttribute("notransition");
         }
       }
     }
   },
   _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
-    this._updateToolbarCustomizationOutline(aItem.ownerDocument.defaultView);
+    this._updateToolbarCustomizationOutline(aItem.ownerGlobal);
     let currentArea = this._getCustomizableParent(aItem);
     if (!currentArea) {
       return;
     }
     let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
     if (isToolbar) {
       if (aNoTransition) {
         aItem.setAttribute("notransition", "true");
--- a/browser/components/customizableui/DragPositionManager.jsm
+++ b/browser/components/customizableui/DragPositionManager.jsm
@@ -10,17 +10,17 @@ var gManagers = new WeakMap();
 
 const kPaletteId = "customization-palette";
 const kPlaceholderClass = "panel-customization-placeholder";
 
 this.EXPORTED_SYMBOLS = ["DragPositionManager"];
 
 function AreaPositionManager(aContainer) {
   // Caching the direction and bounds of the container for quick access later:
-  let window = aContainer.ownerDocument.defaultView;
+  let window = aContainer.ownerGlobal;
   this._dir = window.getComputedStyle(aContainer).direction;
   let containerRect = aContainer.getBoundingClientRect();
   this._containerInfo = {
     left: containerRect.left,
     right: containerRect.right,
     top: containerRect.top,
     width: containerRect.width
   };
@@ -29,17 +29,17 @@ function AreaPositionManager(aContainer)
   this.update(aContainer);
 }
 
 AreaPositionManager.prototype = {
   _nodePositionStore: null,
   _wideCache: null,
 
   update: function(aContainer) {
-    let window = aContainer.ownerDocument.defaultView;
+    let window = aContainer.ownerGlobal;
     this._nodePositionStore = new WeakMap();
     this._wideCache = new Set();
     let last = null;
     let singleItemHeight;
     for (let child of aContainer.children) {
       if (child.hidden) {
         continue;
       }
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -296,32 +296,32 @@ function promisePanelElementHidden(win, 
 }
 
 function isPanelUIOpen() {
   return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing";
 }
 
 function subviewShown(aSubview) {
   let deferred = Promise.defer();
-  let win = aSubview.ownerDocument.defaultView;
+  let win = aSubview.ownerGlobal;
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds.");
   }, 20000);
   function onViewShowing(e) {
     aSubview.removeEventListener("ViewShowing", onViewShowing);
     win.clearTimeout(timeoutId);
     deferred.resolve();
   }
   aSubview.addEventListener("ViewShowing", onViewShowing);
   return deferred.promise;
 }
 
 function subviewHidden(aSubview) {
   let deferred = Promise.defer();
-  let win = aSubview.ownerDocument.defaultView;
+  let win = aSubview.ownerGlobal;
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Subview (" + aSubview.id + ") did not hide within 20 seconds.");
   }, 20000);
   function onViewHiding(e) {
     aSubview.removeEventListener("ViewHiding", onViewHiding);
     win.clearTimeout(timeoutId);
     deferred.resolve();
   }
@@ -471,17 +471,17 @@ function popupHidden(aPopup) {
  *   yield popupShownPromise;
  *
  *  let popupHiddenPromise = promisePopupEvent(somePopup, "hidden");
  *  // ... something that hides a popup
  *  yield popupHiddenPromise;
  */
 function promisePopupEvent(aPopup, aEventSuffix) {
   let deferred = Promise.defer();
-  let win = aPopup.ownerDocument.defaultView;
+  let win = aPopup.ownerGlobal;
   let eventType = "popup" + aEventSuffix;
 
   function onPopupEvent(e) {
     aPopup.removeEventListener(eventType, onPopupEvent);
     deferred.resolve();
   }
 
   aPopup.addEventListener(eventType, onPopupEvent);
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -109,17 +109,17 @@ BrowserAction.prototype = {
           // with the fewest complications.
           event.preventDefault();
           this.emit("click");
         }
       },
     });
 
     this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
-                       (evt, tab) => { this.updateWindow(tab.ownerDocument.defaultView); });
+                       (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
 
     this.widget = widget;
   },
 
   // Update the toolbar button |node| with the tab context data
   // in |tabData|.
   updateButton(node, tabData) {
     let title = tabData.title || this.extension.name;
@@ -191,17 +191,17 @@ BrowserAction.prototype = {
   },
 
   // Update the toolbar button when the extension changes the icon,
   // title, badge, etc. If it only changes a parameter for a single
   // tab, |tab| will be that tab. Otherwise it will be null.
   updateOnChange(tab) {
     if (tab) {
       if (tab.selected) {
-        this.updateWindow(tab.ownerDocument.defaultView);
+        this.updateWindow(tab.ownerGlobal);
       }
     } else {
       for (let window of WindowListManager.browserWindows()) {
         this.updateWindow(window);
       }
     }
   },
 
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -119,17 +119,17 @@ CommandList.prototype = {
     // and it is currently ignored when set to the empty string.
     keyElement.setAttribute("oncommand", "//");
 
     /* eslint-disable mozilla/balanced-listeners */
     // We remove all references to the key elements when the extension is shutdown,
     // therefore the listeners for these elements will be garbage collected.
     keyElement.addEventListener("command", (event) => {
       if (name == "_execute_page_action") {
-        let win = event.target.ownerDocument.defaultView;
+        let win = event.target.ownerGlobal;
         pageActionFor(this.extension).triggerAction(win);
       } else {
         this.emit("command", name);
       }
     });
     /* eslint-enable mozilla/balanced-listeners */
 
     return keyElement;
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -46,17 +46,17 @@ var gMenuBuilder = {
         // the root menu item itself either.
         continue;
       }
       rootElement.setAttribute("ext-type", "top-level-menu");
       rootElement = this.removeTopLevelMenuIfNeeded(rootElement);
 
       // Display the extension icon on the root element.
       if (root.extension.manifest.icons) {
-        let parentWindow = contextData.menu.ownerDocument.defaultView;
+        let parentWindow = contextData.menu.ownerGlobal;
         let extension = root.extension;
 
         let {icon} = IconDetails.getPreferredIcon(extension.manifest.icons, extension,
                                                   16 * parentWindow.devicePixelRatio);
 
         // The extension icons in the manifest are not pre-resolved, since
         // they're sometimes used by the add-on manager when the extension is
         // not enabled, and its URLs are not resolvable.
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -59,17 +59,17 @@ PageAction.prototype = {
   setProperty(tab, prop, value) {
     if (value != null) {
       this.tabContext.get(tab)[prop] = value;
     } else {
       delete this.tabContext.get(tab)[prop];
     }
 
     if (tab.selected) {
-      this.updateButton(tab.ownerDocument.defaultView);
+      this.updateButton(tab.ownerGlobal);
     }
   },
 
   // Updates the page action button in the given window to reflect the
   // properties of the currently selected tab:
   //
   // Updates "tooltiptext" and "aria-label" to match "title" property.
   // Updates "image" to match the "icon" property.
@@ -178,17 +178,17 @@ PageAction.prototype = {
       this.emit("click", tab);
     }
   },
 
   handleLocationChange(eventType, tab, fromBrowse) {
     if (fromBrowse) {
       this.tabContext.clear(tab);
     }
-    this.updateButton(tab.ownerDocument.defaultView);
+    this.updateButton(tab.ownerGlobal);
   },
 
   shutdown() {
     this.tabContext.shutdown();
 
     for (let window of WindowListManager.browserWindows()) {
       if (this.buttons.has(window)) {
         this.buttons.get(window).remove();
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -21,17 +21,17 @@ var {
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(context, target, sender) {
   // The message was sent from a content script to a <browser> element.
   // We can just get the |tab| from |target|.
   if (target instanceof Ci.nsIDOMXULElement) {
     // The message came from a content script.
-    let tabbrowser = target.ownerDocument.defaultView.gBrowser;
+    let tabbrowser = target.ownerGlobal.gBrowser;
     if (!tabbrowser) {
       return;
     }
     let tab = tabbrowser.getTabForBrowser(target);
 
     sender.tab = TabManager.convert(context.extension, tab);
   } else if ("tabId" in sender) {
     // The message came from an ExtensionContext. In that case, it should
@@ -49,17 +49,17 @@ var pageDataMap = new WeakMap();
 // This listener fires whenever an extension page opens in a tab
 // (either initiated by the extension or the user). Its job is to fill
 // in some tab-specific details and keep data around about the
 // ExtensionContext.
 extensions.on("page-load", (type, page, params, sender, delegate) => {
   if (params.type == "tab" || params.type == "popup") {
     let browser = params.docShell.chromeEventHandler;
 
-    let parentWindow = browser.ownerDocument.defaultView;
+    let parentWindow = browser.ownerGlobal;
     page.windowId = WindowManager.getId(parentWindow);
 
     let tab = parentWindow.gBrowser.getTabForBrowser(browser);
     if (tab) {
       sender.tabId = TabManager.getId(tab);
       page.tabId = TabManager.getId(tab);
     }
 
@@ -201,35 +201,35 @@ let tabListener = {
         this.emitDetached(tab, this.adoptedTabs.get(tab));
       } else {
         this.emitRemoved(tab, true);
       }
     }
   },
 
   emitAttached(tab) {
-    let newWindowId = WindowManager.getId(tab.ownerDocument.defaultView);
+    let newWindowId = WindowManager.getId(tab.ownerGlobal);
     let tabId = TabManager.getId(tab);
 
     this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
   },
 
   emitDetached(tab, adoptedBy) {
-    let oldWindowId = WindowManager.getId(tab.ownerDocument.defaultView);
+    let oldWindowId = WindowManager.getId(tab.ownerGlobal);
     let tabId = TabManager.getId(tab);
 
     this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
   },
 
   emitCreated(tab) {
     this.emit("tab-created", {tab});
   },
 
   emitRemoved(tab, isWindowClosing) {
-    let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
+    let windowId = WindowManager.getId(tab.ownerGlobal);
     let tabId = TabManager.getId(tab);
 
     this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
   },
 
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
 
@@ -238,17 +238,17 @@ let tabListener = {
       AllWindowEvents.addListener("progress", this);
 
       this.tabReadyInitialized = true;
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
-      let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+      let gBrowser = browser.ownerGlobal.gBrowser;
       let tab = gBrowser.getTabForBrowser(browser);
 
       let deferred = this.tabReadyPromises.get(tab);
       if (deferred) {
         deferred.resolve(tab);
         this.tabReadyPromises.delete(tab);
       }
     }
@@ -262,17 +262,17 @@ let tabListener = {
 };
 
 extensions.registerSchemaAPI("tabs", (extension, context) => {
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
-        let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
+        let windowId = WindowManager.getId(tab.ownerGlobal);
         fire({tabId, windowId});
       }).api(),
 
       onCreated: new EventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
           fire(TabManager.convert(extension, event.tab));
         };
 
@@ -287,17 +287,17 @@ extensions.registerSchemaAPI("tabs", (ex
        * Since multiple tabs currently can't be highlighted, onHighlighted
        * essentially acts an alias for self.tabs.onActivated but returns
        * the tabId in an array to match the API.
        * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
       */
       onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabIds = [TabManager.getId(tab)];
-        let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
+        let windowId = WindowManager.getId(tab.ownerGlobal);
         fire({tabIds, windowId});
       }).api(),
 
       onAttached: new EventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
           fire(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
@@ -358,17 +358,17 @@ extensions.registerSchemaAPI("tabs", (ex
           let tab = event.originalTarget;
 
           if (ignoreNextMove.has(tab)) {
             ignoreNextMove.delete(tab);
             return;
           }
 
           fire(TabManager.getId(tab), {
-            windowId: WindowManager.getId(tab.ownerDocument.defaultView),
+            windowId: WindowManager.getId(tab.ownerGlobal),
             fromIndex: event.detail,
             toIndex: tab._tPos,
           });
         };
 
         AllWindowEvents.addListener("TabMove", moveListener);
         AllWindowEvents.addListener("TabOpen", openListener);
         return () => {
@@ -388,17 +388,17 @@ extensions.registerSchemaAPI("tabs", (ex
             }
           }
           return [nonempty, result];
         }
 
         let fireForBrowser = (browser, changed) => {
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
-            let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+            let gBrowser = browser.ownerGlobal.gBrowser;
             let tabElem = gBrowser.getTabForBrowser(browser);
 
             let tab = TabManager.convert(extension, tabElem);
             fire(tab.id, changeInfo, tab);
           }
         };
 
         let listener = event => {
@@ -545,30 +545,30 @@ extensions.registerSchemaAPI("tabs", (ex
 
       remove: function(tabs) {
         if (!Array.isArray(tabs)) {
           tabs = [tabs];
         }
 
         for (let tabId of tabs) {
           let tab = TabManager.getTab(tabId);
-          tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
+          tab.ownerGlobal.gBrowser.removeTab(tab);
         }
 
         return Promise.resolve();
       },
 
       update: function(tabId, updateProperties) {
         let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
 
         if (!tab) {
           return Promise.reject({message: `No tab found with tabId: ${tabId}`});
         }
 
-        let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
+        let tabbrowser = tab.ownerGlobal.gBrowser;
 
         if (updateProperties.url !== null) {
           let url = context.uri.resolve(updateProperties.url);
 
           if (!context.checkLoadURL(url, {dontReportErrors: true})) {
             return Promise.reject({message: `Illegal URL: ${url}`});
           }
 
@@ -876,17 +876,17 @@ extensions.registerSchemaAPI("tabs", (ex
         for (let tabId of tabIds) {
           let tab = TabManager.getTab(tabId);
           // Ignore invalid tab ids.
           if (!tab) {
             continue;
           }
 
           // If the window is not specified, use the window from the tab.
-          let window = destinationWindow || tab.ownerDocument.defaultView;
+          let window = destinationWindow || tab.ownerGlobal;
           let gBrowser = window.gBrowser;
 
           let insertionPoint = indexMap.get(window) || index;
           // If the index is -1 it should go to the end of the tabs.
           if (insertionPoint == -1) {
             insertionPoint = gBrowser.tabs.length;
           }
 
@@ -897,17 +897,17 @@ extensions.registerSchemaAPI("tabs", (ex
           let numPinned = gBrowser._numPinnedTabs;
           let ok = tab.pinned ? insertionPoint <= numPinned : insertionPoint >= numPinned;
           if (!ok) {
             continue;
           }
 
           indexMap.set(window, insertionPoint + 1);
 
-          if (tab.ownerDocument.defaultView !== window) {
+          if (tab.ownerGlobal != window) {
             // If the window we are moving the tab in is different, then move the tab
             // to the new window.
             tab = gBrowser.adoptTab(tab, insertionPoint, false);
           } else {
             // If the window we are moving is the same, just move the tab.
             gBrowser.moveTabTo(tab, insertionPoint);
           }
           tabsMoved.push(tab);
@@ -917,17 +917,17 @@ extensions.registerSchemaAPI("tabs", (ex
       },
 
       duplicate: function(tabId) {
         let tab = TabManager.getTab(tabId);
         if (!tab) {
           return Promise.reject({message: `Invalid tab ID: ${tabId}`});
         }
 
-        let gBrowser = tab.ownerDocument.defaultView.gBrowser;
+        let gBrowser = tab.ownerGlobal.gBrowser;
         let newTab = gBrowser.duplicateTab(tab);
 
         return new Promise(resolve => {
           // We need to use SSTabRestoring because any attributes set before
           // are ignored. SSTabRestored is too late and results in a jump in
           // the UI. See http://bit.ly/session-store-api for more information.
           newTab.addEventListener("SSTabRestoring", function listener() {
             // As the tab is restoring, move it to the correct position.
@@ -947,26 +947,26 @@ extensions.registerSchemaAPI("tabs", (ex
             return resolve(TabManager.convert(extension, newTab));
           });
         });
       },
 
       getZoom(tabId) {
         let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
 
-        let {ZoomManager} = tab.ownerDocument.defaultView;
+        let {ZoomManager} = tab.ownerGlobal;
         let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
 
         return Promise.resolve(zoom);
       },
 
       setZoom(tabId, zoom) {
         let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
 
-        let {FullZoom, ZoomManager} = tab.ownerDocument.defaultView;
+        let {FullZoom, ZoomManager} = tab.ownerGlobal;
 
         if (zoom === 0) {
           // A value of zero means use the default zoom factor.
           return FullZoom.reset(tab.linkedBrowser);
         } else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
           FullZoom.setZoom(zoom, tab.linkedBrowser);
         } else {
           return Promise.reject({
@@ -975,17 +975,17 @@ extensions.registerSchemaAPI("tabs", (ex
         }
 
         return Promise.resolve();
       },
 
       _getZoomSettings(tabId) {
         let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
 
-        let {FullZoom} = tab.ownerDocument.defaultView;
+        let {FullZoom} = tab.ownerGlobal;
 
         return {
           mode: "automatic",
           scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
           defaultZoomFactor: 1,
         };
       },
 
@@ -1001,17 +1001,17 @@ extensions.registerSchemaAPI("tabs", (ex
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
 
       onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
         let getZoomLevel = browser => {
-          let {ZoomManager} = browser.ownerDocument.defaultView;
+          let {ZoomManager} = browser.ownerGlobal;
 
           return ZoomManager.getZoomForBrowser(browser);
         };
 
         // Stores the last known zoom level for each tab's browser.
         // WeakMap[<browser> -> number]
         let zoomLevels = new WeakMap();
 
@@ -1035,17 +1035,17 @@ extensions.registerSchemaAPI("tabs", (ex
           // For non-remote browsers, this event is dispatched on the document
           // rather than on the <browser>.
           if (browser instanceof Ci.nsIDOMDocument) {
             browser = browser.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
           }
 
-          let {gBrowser} = browser.ownerDocument.defaultView;
+          let {gBrowser} = browser.ownerGlobal;
           let tab = gBrowser.getTabForBrowser(browser);
           if (!tab) {
             // We only care about zoom events in the top-level browser of a tab.
             return;
           }
 
           let oldZoomFactor = zoomLevels.get(browser);
           let newZoomFactor = getZoomLevel(browser);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -68,17 +68,17 @@ class BasePopup {
     Services.scriptSecurityManager.checkLoadURIWithPrincipal(
       extension.principal, popupURI,
       Services.scriptSecurityManager.DISALLOW_SCRIPT);
 
     this.extension = extension;
     this.popupURI = popupURI;
     this.viewNode = viewNode;
     this.browserStyle = browserStyle;
-    this.window = viewNode.ownerDocument.defaultView;
+    this.window = viewNode.ownerGlobal;
 
     this.contentReady = new Promise(resolve => {
       this._resolveContentReady = resolve;
     });
 
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
 
     this.browser = null;
@@ -323,17 +323,17 @@ TabContext.prototype = {
     if (event.type == "TabSelect") {
       let tab = event.target;
       this.emit("tab-select", tab);
       this.emit("location-change", tab);
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
-    let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+    let gBrowser = browser.ownerGlobal.gBrowser;
     if (browser === gBrowser.selectedBrowser) {
       let tab = gBrowser.getTabForBrowser(browser);
       this.emit("location-change", tab, true);
     }
   },
 
   shutdown() {
     AllWindowEvents.removeListener("progress", this);
@@ -379,17 +379,17 @@ ExtensionTabManager.prototype = {
     return false;
   },
 
   hasTabPermission(tab) {
     return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
   },
 
   convert(tab) {
-    let window = tab.ownerDocument.defaultView;
+    let window = tab.ownerGlobal;
     let browser = tab.linkedBrowser;
 
     let mutedInfo = {muted: tab.muted};
     if (tab.muteReason === null) {
       mutedInfo.reason = "user";
     } else if (tab.muteReason) {
       mutedInfo.reason = "extension";
       mutedInfo.extensionId = tab.muteReason;
@@ -490,17 +490,17 @@ global.TabManager = {
     this.initListener();
 
     let id = this._nextId++;
     this._tabs.set(tab, id);
     return id;
   },
 
   getBrowserId(browser) {
-    let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+    let gBrowser = browser.ownerGlobal.gBrowser;
     // Some non-browser windows have gBrowser but not
     // getTabForBrowser!
     if (gBrowser && gBrowser.getTabForBrowser) {
       let tab = gBrowser.getTabForBrowser(browser);
       if (tab) {
         return this.getId(tab);
       }
     }
--- a/browser/components/newtab/NewTabSearchProvider.jsm
+++ b/browser/components/newtab/NewTabSearchProvider.jsm
@@ -66,17 +66,17 @@ SearchProvider.prototype = {
     return ContentSearch.searchSuggestionUIStrings;
   },
 
   removeFormHistory({browser}, suggestion) {
     ContentSearch.removeFormHistoryEntry({target: browser}, suggestion);
   },
 
   manageEngines(browser) {
-    const browserWin = browser.ownerDocument.defaultView;
+    const browserWin = browser.ownerGlobal;
     browserWin.openPreferences("paneSearch");
   },
 
   asyncGetState: Task.async(function*() {
     let state = yield ContentSearch.currentStateObj(true);
     return state;
   }),
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -97,16 +97,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/Feeds.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SelfSupportBackend",
                                   "resource:///modules/SelfSupportBackend.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUsageTelemetry",
+                                  "resource:///modules/BrowserUsageTelemetry.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                   "resource://gre/modules/LoginManagerParent.jsm");
@@ -310,18 +313,18 @@ BrowserGlue.prototype = {
         this._setSyncAutoconnectDelay();
         break;
       case "fxaccounts:onverified":
         this._showSyncStartedDoorhanger();
         break;
       case "fxaccounts:device_disconnected":
         this._onDeviceDisconnected();
         break;
-      case "weave:engine:clients:display-uri":
-        this._onDisplaySyncURI(subject);
+      case "weave:engine:clients:display-uris":
+        this._onDisplaySyncURIs(subject);
         break;
       case "session-save":
         this._setPrefToSaveSession(true);
         subject.QueryInterface(Ci.nsISupportsPRBool);
         subject.data = true;
         break;
       case "places-init-complete":
         if (!this._migrationImportsDefaultBookmarks)
@@ -529,17 +532,17 @@ BrowserGlue.prototype = {
     os.addObserver(this, "quit-application-granted", false);
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.addObserver(this, "browser-lastwindow-close-requested", false);
       os.addObserver(this, "browser-lastwindow-close-granted", false);
     }
     os.addObserver(this, "weave:service:ready", false);
     os.addObserver(this, "fxaccounts:onverified", false);
     os.addObserver(this, "fxaccounts:device_disconnected", false);
-    os.addObserver(this, "weave:engine:clients:display-uri", false);
+    os.addObserver(this, "weave:engine:clients:display-uris", false);
     os.addObserver(this, "session-save", false);
     os.addObserver(this, "places-init-complete", false);
     this._isPlacesInitObserver = true;
     os.addObserver(this, "places-database-locked", false);
     this._isPlacesLockedObserver = true;
     os.addObserver(this, "distribution-customization-complete", false);
     os.addObserver(this, "places-shutdown", false);
     this._isPlacesShutdownObserver = true;
@@ -596,17 +599,17 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "restart-in-safe-mode");
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.removeObserver(this, "browser-lastwindow-close-requested");
       os.removeObserver(this, "browser-lastwindow-close-granted");
     }
     os.removeObserver(this, "weave:service:ready");
     os.removeObserver(this, "fxaccounts:onverified");
     os.removeObserver(this, "fxaccounts:device_disconnected");
-    os.removeObserver(this, "weave:engine:clients:display-uri");
+    os.removeObserver(this, "weave:engine:clients:display-uris");
     os.removeObserver(this, "session-save");
     if (this._bookmarksBackupIdleTime) {
       this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
       delete this._bookmarksBackupIdleTime;
     }
     if (this._isPlacesInitObserver)
       os.removeObserver(this, "places-init-complete");
     if (this._isPlacesLockedObserver)
@@ -776,16 +779,17 @@ BrowserGlue.prototype = {
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
 
     NewTabMessages.init();
 
     SessionStore.init();
+    BrowserUsageTelemetry.init();
     BrowserUITelemetry.init();
     ContentSearch.init();
     FormValidationHandler.init();
 
     ContentClick.init();
     RemotePrompt.init();
     Feeds.init();
     ContentPrefServiceParent.init();
@@ -1169,16 +1173,17 @@ BrowserGlue.prototype = {
     try {
       let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
+    BrowserUsageTelemetry.uninit();
     SelfSupportBackend.uninit();
     NewTabMessages.uninit();
 
     CaptivePortalWatcher.uninit();
 
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
@@ -2431,34 +2436,69 @@ BrowserGlue.prototype = {
       return;
     }
 
     let chromeWindow = RecentWindow.getMostRecentBrowserWindow();
     chromeWindow.openPreferences(...args);
   },
 
   /**
-   * Called as an observer when Sync's "display URI" notification is fired.
-   *
-   * We open the received URI in a background tab.
+   * Called as an observer when Sync's "display URIs" notification is fired.
    *
-   * Eventually, this will likely be replaced by a more robust tab syncing
-   * feature. This functionality is considered somewhat evil by UX because it
-   * opens a new tab automatically without any prompting. However, it is a
-   * lesser evil than sending a tab to a specific device (from e.g. Fennec)
-   * and having nothing happen on the receiving end.
+   * We open the received URIs in background tabs.
    */
-  _onDisplaySyncURI: function _onDisplaySyncURI(data) {
+  _onDisplaySyncURIs: function _onDisplaySyncURIs(data) {
     try {
-      let tabbrowser = RecentWindow.getMostRecentBrowserWindow({private: false}).gBrowser;
-
       // The payload is wrapped weirdly because of how Sync does notifications.
-      tabbrowser.addTab(data.wrappedJSObject.object.uri);
+      const URIs = data.wrappedJSObject.object;
+
+      const findWindow = () => RecentWindow.getMostRecentBrowserWindow({private: false});
+
+      // win can be null, but it's ok, we'll assign it later in openTab()
+      let win = findWindow();
+
+      const openTab = URI => {
+        let tab;
+        if (!win) {
+          Services.appShell.hiddenDOMWindow.open(URI.uri);
+          win = findWindow();
+          tab = win.gBrowser.tabs[0];
+        } else {
+          tab = win.gBrowser.addTab(URI.uri);
+        }
+        tab.setAttribute("attention", true);
+        return tab;
+      };
+
+      const firstTab = openTab(URIs[0]);
+      URIs.slice(1).forEach(URI => openTab(URI));
+
+      let title, body;
+      const deviceName = Weave.Service.clientsEngine.getClientName(URIs[0].clientId);
+      const bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+      if (URIs.length == 1) {
+        title = bundle.GetStringFromName("tabArrivingNotification.title");
+        const pageTitle = URIs[0].title || firstTab.linkedBrowser.contentTitle
+                          || URIs[0].uri;
+        body = bundle.formatStringFromName("tabArrivingNotification.body", [pageTitle, deviceName], 2);
+      } else {
+        title = bundle.GetStringFromName("tabsArrivingNotification.title");
+        const tabArrivingBody = URIs.every(URI => URI.clientId == URIs[0].clientId) ?
+                                "tabsArrivingNotification.body" : "tabsArrivingNotificationMultiple.body";
+        body = bundle.formatStringFromName(tabArrivingBody, [URIs.length, deviceName], 2);
+      }
+
+      const clickCallback = (subject, topic, data) => {
+        if (topic == "alertclickcallback") {
+          win.gBrowser.selectedTab = firstTab;
+        }
+      }
+      AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
     } catch (ex) {
-      Cu.reportError("Error displaying tab received by Sync: " + ex);
+      Cu.reportError("Error displaying tab(s) received by Sync: " + ex);
     }
   },
 
   _onDeviceDisconnected() {
     let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
     let title = bundle.GetStringFromName("deviceDisconnectedNotification.title");
     let body = bundle.GetStringFromName("deviceDisconnectedNotification.body");
 
@@ -2554,17 +2594,17 @@ ContentPermissionPrompt.prototype = {
    *                               Permission is granted if action is null or ALLOW_ACTION.
    * @param aNotificationId        The id of the PopupNotification.
    * @param aAnchorId              The id for the PopupNotification anchor.
    * @param aOptions               Options for the PopupNotification
    */
   _showPrompt: function CPP_showPrompt(aRequest, aMessage, aPermission, aActions,
                                        aNotificationId, aAnchorId, aOptions) {
     var browser = this._getBrowserForRequest(aRequest);
-    var chromeWin = browser.ownerDocument.defaultView;
+    var chromeWin = browser.ownerGlobal;
     var requestPrincipal = aRequest.principal;
 
     // Transform the prompt actions into PopupNotification actions.
     var popupNotificationActions = [];
     for (var i = 0; i < aActions.length; i++) {
       let promptAction = aActions[i];
 
       // Don't offer action in PB mode if the action remembers permission for more than a session.
@@ -2757,17 +2797,17 @@ ContentPermissionPrompt.prototype = {
       return;
     }
 
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       autoAllow = true;
     }
 
     var browser = this._getBrowserForRequest(request);
-    var chromeWin = browser.ownerDocument.defaultView;
+    var chromeWin = browser.ownerGlobal;
     if (!chromeWin.PopupNotifications)
       // Ignore requests from browsers hosted in windows that don't support
       // PopupNotifications.
       return;
 
     // Show the prompt.
     switch (perm.type) {
     case "geolocation":
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -181,17 +181,17 @@ let InternalFaviconLoader = {
     Services.obs.addObserver(this, "inner-window-destroyed", false);
     Services.ppmm.addMessageListener("Toolkit:inner-window-destroyed", msg => {
       this.removeRequestsForInner(msg.data);
     });
   },
 
   loadFavicon(browser, principal, uri) {
     this.ensureInitialized();
-    let win = browser.ownerDocument.defaultView;
+    let win = browser.ownerGlobal;
     if (!gFaviconLoadDataMap.has(win)) {
       gFaviconLoadDataMap.set(win, []);
       let unloadHandler = event => {
         let doc = event.target;
         let eventWin = doc.defaultView;
         if (eventWin == win) {
           win.removeEventListener("unload", unloadHandler);
           this.onUnload(win);
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1596,17 +1596,17 @@ var PlacesControllerDragHelper = {
 
       let data = dt.mozGetDataAt(flavor, i);
       let unwrapped;
       if (flavor != TAB_DROP_TYPE) {
         // There's only ever one in the D&D case.
         unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
       }
       else if (data instanceof XULElement && data.localName == "tab" &&
-               data.ownerDocument.defaultView instanceof ChromeWindow) {
+               data.ownerGlobal instanceof ChromeWindow) {
         let uri = data.linkedBrowser.currentURI;
         let spec = uri ? uri.spec : "about:blank";
         let title = data.label;
         unwrapped = { uri: spec,
                       title: data.label,
                       type: PlacesUtils.TYPE_X_MOZ_URL};
       }
       else
--- a/browser/components/places/tests/browser/browser_library_middleclick.js
+++ b/browser/components/places/tests/browser/browser_library_middleclick.js
@@ -23,17 +23,17 @@ var gTabsListener = {
       return;
 
     if (++this._openTabsCount == gCurrentTest.URIs.length) {
       is(gBrowser.tabs.length, gCurrentTest.URIs.length + 1,
          "We have opened " + gCurrentTest.URIs.length + " new tab(s)");
     }
 
     var tab = aEvent.target;
-    is(tab.ownerDocument.defaultView, window,
+    is(tab.ownerGlobal, window,
        "Tab has been opened in current browser window");
   },
 
   onLocationChange: function(aBrowser, aWebProgress, aRequest, aLocationURI,
                              aFlags) {
     var spec = aLocationURI.spec;
     ok(true, spec);
     // When a new tab is opened, location is first set to "about:blank", so
@@ -57,17 +57,17 @@ var gTabsListener = {
       this._openTabsCount = 0;
 
       executeSoon(function () {
         // Close all tabs.
         while (gBrowser.tabs.length > 1)
           gBrowser.removeCurrentTab();
 
         // Test finished.  This will move to the next one.
-        waitForFocus(gCurrentTest.finish, gBrowser.ownerDocument.defaultView);
+        waitForFocus(gCurrentTest.finish, gBrowser.ownerGlobal);
       });
     }
   }
 }
 
 //------------------------------------------------------------------------------
 // Open bookmark in a new tab.
 
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -131,17 +131,17 @@ function synthesizeClickOnSelectedTreeCe
   let rowID = min.value;
   tbo.ensureRowIsVisible(rowID);
   // Calculate the click coordinates.
   var rect = tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text");
   var x = rect.x + rect.width / 2;
   var y = rect.y + rect.height / 2;
   // Simulate the click.
   EventUtils.synthesizeMouse(aTree.body, x, y, aOptions || {},
-                             aTree.ownerDocument.defaultView);
+                             aTree.ownerGlobal);
 }
 
 /**
  * Asynchronously check a url is visited.
  *
  * @param aURI The URI.
  * @return {Promise}
  * @resolves When the check has been added successfully.
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -331,17 +331,17 @@ var gSubDialog = {
     }
 
     let forward = !aEvent.shiftKey;
     // check if focus is leaving the frame (incl. the close button):
     if ((aEvent.target == this._closeButton && !forward) ||
         (isLastFocusableElement(aEvent.originalTarget) && forward)) {
       aEvent.preventDefault();
       aEvent.stopImmediatePropagation();
-      let parentWin = this._getBrowser().ownerDocument.defaultView;
+      let parentWin = this._getBrowser().ownerGlobal;
       if (forward) {
         fm.moveFocus(parentWin, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
       } else {
         // Somehow, moving back 'past' the opening doc is not trivial. Cheat by doing it in 2 steps:
         fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
         fm.moveFocus(parentWin, null, fm.MOVEFOCUS_BACKWARD, fm.FLAG_BYKEY);
       }
     }
--- a/browser/components/preferences/in-content/tests/head.js
+++ b/browser/components/preferences/in-content/tests/head.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/Promise.jsm");
 
 const kDefaultWait = 2000;
 
 function is_hidden(aElement) {
-  var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, "");
+  var style = aElement.ownerGlobal.getComputedStyle(aElement);
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
 
   // Hiding a parent element will hide all its children
   if (aElement.parentNode != aElement.ownerDocument)
     return is_hidden(aElement.parentNode);
--- a/browser/components/safebrowsing/content/test/browser_whitelisted.js
+++ b/browser/components/safebrowsing/content/test/browser_whitelisted.js
@@ -27,15 +27,15 @@ function testWhitelistedPage(window) {
 
 add_task(function* testNormalBrowsing() {
   tabbrowser = gBrowser;
   let tab = tabbrowser.selectedTab = tabbrowser.addTab();
 
   info("Load a test page that's whitelisted");
   Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "example.com,www.ItIsaTrap.org,example.net");
   yield promiseTabLoadEvent(tab, TEST_PAGE, "load");
-  testWhitelistedPage(tab.ownerDocument.defaultView);
+  testWhitelistedPage(tab.ownerGlobal);
 
   info("Load a test page that's no longer whitelisted");
   Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "");
   yield promiseTabLoadEvent(tab, TEST_PAGE, "AboutBlockedLoaded");
-  testBlockedPage(tab.ownerDocument.defaultView);
+  testBlockedPage(tab.ownerGlobal);
 });
--- a/browser/components/search/test/browser_oneOffHeader.js
+++ b/browser/components/search/test/browser_oneOffHeader.js
@@ -44,17 +44,17 @@ function getOneOffs() {
 }
 
 const msg = isMac ? 5 : 1;
 const utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
 const scale = utils.screenPixelsPerCSSPixel;
 function* synthesizeNativeMouseMove(aElement) {
   let rect = aElement.getBoundingClientRect();
-  let win = aElement.ownerDocument.defaultView;
+  let win = aElement.ownerGlobal;
   let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
   let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
 
   // Wait for the mouseup event to occur before continuing.
   return new Promise((resolve, reject) => {
     function eventOccurred(e)
     {
       aElement.removeEventListener("mouseover", eventOccurred, true);
--- a/browser/components/search/test/browser_searchbar_openpopup.js
+++ b/browser/components/search/test/browser_searchbar_openpopup.js
@@ -19,17 +19,17 @@ const isWindows = Services.appinfo.OS ==
 const mouseDown = isWindows ? 2 : 1;
 const mouseUp = isWindows ? 4 : 2;
 const utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
 const scale = utils.screenPixelsPerCSSPixel;
 
 function* synthesizeNativeMouseClick(aElement) {
   let rect = aElement.getBoundingClientRect();
-  let win = aElement.ownerDocument.defaultView;
+  let win = aElement.ownerGlobal;
   let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
   let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
 
   // Wait for the mouseup event to occur before continuing.
   return new Promise((resolve, reject) => {
     function eventOccurred(e)
     {
       aElement.removeEventListener("mouseup", eventOccurred, true);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -672,17 +672,17 @@ var SessionStoreInternal = {
    * This method handles incoming messages sent by the session store content
    * script via the Frame Message Manager or Parent Process Message Manager,
    * and thus enables communication with OOP tabs.
    */
   receiveMessage(aMessage) {
     // If we got here, that means we're dealing with a frame message
     // manager message, so the target will be a <xul:browser>.
     var browser = aMessage.target;
-    let win = browser.ownerDocument.defaultView;
+    let win = browser.ownerGlobal;
     let tab = win ? win.gBrowser.getTabForBrowser(browser) : null;
 
     // Ensure we receive only specific messages from <xul:browser>s that
     // have no tab or window assigned, e.g. the ones that preload
     // about:newtab pages, or windows that have closed.
     if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
       throw new Error(`received unexpected message '${aMessage.name}' ` +
                       `from a browser that has no tab or window`);
@@ -885,17 +885,17 @@ var SessionStoreInternal = {
   },
 
   /* ........ Window Event Handlers .............. */
 
   /**
    * Implement nsIDOMEventListener for handling various window and tab events
    */
   handleEvent: function ssi_handleEvent(aEvent) {
-    let win = aEvent.currentTarget.ownerDocument.defaultView;
+    let win = aEvent.currentTarget.ownerGlobal;
     let target = aEvent.originalTarget;
     switch (aEvent.type) {
       case "TabOpen":
         this.onTabAdd(win);
         break;
       case "TabBrowserCreated":
         this.onTabBrowserCreated(win, target);
         break;
@@ -2046,17 +2046,17 @@ var SessionStoreInternal = {
     if (!aWindow.__SSi) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     this.restoreWindows(aWindow, aState, {overwriteTabs: aOverwrite});
   },
 
   getTabState: function ssi_getTabState(aTab) {
-    if (!aTab.ownerDocument.defaultView.__SSi) {
+    if (!aTab.ownerGlobal.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     let tabState = TabState.collect(aTab);
 
     return JSON.stringify(tabState);
   },
 
@@ -2071,30 +2071,30 @@ var SessionStoreInternal = {
     }
     if (typeof tabState != "object") {
       throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!("entries" in tabState)) {
       throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    let window = aTab.ownerDocument.defaultView;
+    let window = aTab.ownerGlobal;
     if (!("__SSi" in window)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
     this.restoreTab(aTab, tabState);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
-    if (!aTab.ownerDocument.defaultView.__SSi) {
+    if (!aTab.ownerGlobal.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aWindow.gBrowser) {
       throw Components.Exception("Invalid window object: no gBrowser", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // Create a new tab.
     let userContextId = aTab.getAttribute("usercontextid");
@@ -2113,17 +2113,17 @@ var SessionStoreInternal = {
     // Flush to get the latest tab state to duplicate.
     let browser = aTab.linkedBrowser;
     TabStateFlusher.flush(browser).then(() => {
       // The new tab might have been closed in the meantime.
       if (newTab.closing || !newTab.linkedBrowser) {
         return;
       }
 
-      let window = newTab.ownerDocument && newTab.ownerDocument.defaultView;
+      let window = newTab.ownerGlobal;
 
       // The tab or its window might be gone.
       if (!window || !window.__SSi) {
         return;
       }
 
       // Update state with flushed data. We can't use TabState.clone() here as
       // the tab to duplicate may have already been closed. In that case we
@@ -2300,23 +2300,23 @@ var SessionStoreInternal = {
 
     // If the tab hasn't been restored, then set the data there, otherwise we
     // could lose newly added data.
     if (!aTab.__SS_extdata) {
       aTab.__SS_extdata = {};
     }
 
     aTab.__SS_extdata[aKey] = aStringValue;
-    this.saveStateDelayed(aTab.ownerDocument.defaultView);
+    this.saveStateDelayed(aTab.ownerGlobal);
   },
 
   deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
     if (aTab.__SS_extdata && aKey in aTab.__SS_extdata) {
       delete aTab.__SS_extdata[aKey];
-      this.saveStateDelayed(aTab.ownerDocument.defaultView);
+      this.saveStateDelayed(aTab.ownerGlobal);
     }
   },
 
   getGlobalValue: function ssi_getGlobalValue(aKey) {
     return this._globalState.get(aKey);
   },
 
   setGlobalValue: function ssi_setGlobalValue(aKey, aStringValue) {
@@ -2504,17 +2504,17 @@ var SessionStoreInternal = {
    * loaded in the parent and pages loaded in the child process.
    *
    * This method might be called multiple times before it has finished
    * flushing the browser tab. If that occurs, the loadArguments from
    * the most recent call to navigateAndRestore will be used once the
    * flush has finished.
    */
   navigateAndRestore(tab, loadArguments, historyIndex) {
-    let window = tab.ownerDocument.defaultView;
+    let window = tab.ownerGlobal;
     NS_ASSERT(window.__SSi, "tab's window must be tracked");
     let browser = tab.linkedBrowser;
 
     // Were we already waiting for a flush from a previous call to
     // navigateAndRestore on this tab?
     let alreadyRestoring =
       this._remotenessChangingBrowsers.has(browser.permanentKey);
 
@@ -2542,17 +2542,17 @@ var SessionStoreInternal = {
         this._remotenessChangingBrowsers.get(browser.permanentKey);
       this._remotenessChangingBrowsers.delete(browser.permanentKey);
 
       // The tab might have been closed/gone in the meantime.
       if (tab.closing || !tab.linkedBrowser) {
         return;
       }
 
-      let window = tab.ownerDocument && tab.ownerDocument.defaultView;
+      let window = tab.ownerGlobal;
 
       // The tab or its window might be gone.
       if (!window || !window.__SSi || window.closed) {
         return;
       }
 
       let tabState = TabState.clone(tab);
       let options = {restoreImmediately: true};
@@ -3205,17 +3205,17 @@ var SessionStoreInternal = {
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
     NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
               "must reset tab before calling restoreTab()");
 
     let restoreImmediately = options.restoreImmediately;
     let loadArguments = options.loadArguments;
     let browser = tab.linkedBrowser;
-    let window = tab.ownerDocument.defaultView;
+    let window = tab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let forceOnDemand = options.forceOnDemand;
 
     // Increase the busy state counter before modifying the tab.
     this._setWindowStateBusy(window);
 
     // It's important to set the window state to dirty so that
     // we collect their data for the first time when saving state.
@@ -3345,17 +3345,17 @@ var SessionStoreInternal = {
    *        its remoteness (out-of-process) state.
    */
   restoreTabContent: function (aTab, aLoadArguments = null) {
     if (aTab.hasAttribute("customizemode")) {
       return;
     }
 
     let browser = aTab.linkedBrowser;
-    let window = aTab.ownerDocument.defaultView;
+    let window = aTab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let tabData = TabState.clone(aTab);
     let activeIndex = tabData.index - 1;
     let activePageData = tabData.entries[activeIndex] || null;
     let uri = activePageData ? activePageData.url || null : null;
     if (aLoadArguments) {
       uri = aLoadArguments.uri;
     }
@@ -4140,17 +4140,17 @@ var SessionStoreInternal = {
    *
    * @param aTab
    *        The tab that will be "reset"
    */
   _resetLocalTabRestoringState: function (aTab) {
     NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
               "given tab is not restoring");
 
-    let window = aTab.ownerDocument.defaultView;
+    let window = aTab.ownerGlobal;
     let browser = aTab.linkedBrowser;
 
     // Keep the tab's previous state for later in this method
     let previousState = browser.__SS_restoreState;
 
     // The browser is no longer in any sort of restoring state.
     delete browser.__SS_restoreState;
 
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -113,17 +113,17 @@ var TabStateInternal = {
 
     // After copyFromCache() was called we check for properties that are kept
     // in the cache only while the tab is pending or restoring. Once that
     // happened those properties will be removed from the cache and will
     // be read from the tab/browser every time we collect data.
 
     // Store the tab icon.
     if (!("image" in tabData)) {
-      let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
+      let tabbrowser = tab.ownerGlobal.gBrowser;
       tabData.image = tabbrowser.getIcon(tab);
     }
 
     // If there is a userTypedValue set, then either the user has typed something
     // in the URL bar, or a new tab was opened with a URI to load.
     // If so, we also track whether we were still in the process of loading something.
     if (!("userTypedValue" in tabData) && browser.userTypedValue) {
       tabData.userTypedValue = browser.userTypedValue;
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -413,18 +413,17 @@ var FormDataListener = {
   init: function () {
     addEventListener("input", this, true);
     addEventListener("change", this, true);
     gFrameTree.addObserver(this);
   },
 
   handleEvent: function (event) {
     let frame = event.target &&
-                event.target.ownerDocument &&
-                event.target.ownerDocument.defaultView;
+                event.target.ownerGlobal;
 
     // Don't collect form data for frames created at or after the load event
     // as SessionStore can't restore form data for those.
     if (frame && gFrameTree.contains(frame)) {
       MessageQueue.push("formdata", () => this.collect());
     }
   },
 
--- a/browser/components/sessionstore/test/browser_463206.js
+++ b/browser/components/sessionstore/test/browser_463206.js
@@ -13,17 +13,17 @@ add_task(function* () {
   yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
   // "Type in" some random values.
   yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
     function typeText(aTextField, aValue) {
       aTextField.value = aValue;
 
       let event = aTextField.ownerDocument.createEvent("UIEvents");
-      event.initUIEvent("input", true, true, aTextField.ownerDocument.defaultView, 0);
+      event.initUIEvent("input", true, true, aTextField.ownerGlobal, 0);
       aTextField.dispatchEvent(event);
     }
 
     typeText(content.document.getElementById("out1"), Date.now());
     typeText(content.document.getElementsByName("1|#out2")[0], Math.random());
     typeText(content.frames[0].frames[1].document.getElementById("in1"), new Date());
   });
 
--- a/browser/components/sessionstore/test/browser_sessionStoreContainer.js
+++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
@@ -72,17 +72,17 @@ add_task(function* () {
 // Opens "uri" in a new tab with the provided userContextId and focuses it.
 // Returns the newly opened tab.
 function* openTabInUserContext(userContextId) {
   // Open the tab in the correct userContextId.
   let tab = gBrowser.addTab("http://example.com", { userContextId });
 
   // Select tab and make sure its browser is focused.
   gBrowser.selectedTab = tab;
-  tab.ownerDocument.defaultView.focus();
+  tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   yield BrowserTestUtils.browserLoaded(browser);
   return { tab, browser };
 }
 
 function waitForNewCookie() {
   return new Promise(resolve => {
--- a/browser/components/sessionstore/test/content-forms.js
+++ b/browser/components/sessionstore/test/content-forms.js
@@ -33,17 +33,17 @@ function queryElement(data) {
     return doc.evaluate(data.xpath, doc, null, xptype, null).singleNodeValue;
   }
 
   throw new Error("couldn't query element");
 }
 
 function dispatchUIEvent(input, type) {
   let event = input.ownerDocument.createEvent("UIEvents");
-  event.initUIEvent(type, true, true, input.ownerDocument.defaultView, 0);
+  event.initUIEvent(type, true, true, input.ownerGlobal, 0);
   input.dispatchEvent(event);
 }
 
 function defineListener(type, cb) {
   addMessageListener("ss-test:" + type, function ({data}) {
     sendAsyncMessage("ss-test:" + type, cb(data));
   });
 }
--- a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
+++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
@@ -363,17 +363,17 @@ function checkItem(node, item) {
 
 function* testContextMenu(syncedTabsDeckComponent, contextSelector, triggerSelector, menuSelectors) {
   let contextMenu = document.querySelector(contextSelector);
   let triggerElement = syncedTabsDeckComponent._window.document.querySelector(triggerSelector);
   let isClosed = triggerElement.classList.contains("closed");
 
   let promisePopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
 
-  let chromeWindow = triggerElement.ownerDocument.defaultView.top;
+  let chromeWindow = triggerElement.ownerGlobal.top;
   let rect = triggerElement.getBoundingClientRect();
   let contentRect = chromeWindow.SidebarUI.browser.getBoundingClientRect();
   // The offsets in `rect` are relative to the content window, but
   // `synthesizeMouseAtPoint` calls `nsIDOMWindowUtils.sendMouseEvent`,
   // which interprets the offsets relative to the containing *chrome* window.
   // This means we need to account for the width and height of any elements
   // outside the `browser` element, like `sidebarheader`.
   let offsetX = contentRect.x + rect.x + (rect.width / 2);
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -359,17 +359,17 @@ this.UITour = {
     let originalUrl = ReaderMode.getOriginalUrl(aLocation);
     if (this._readerViewTriggerRegEx.test(originalUrl)) {
       this.startSubTour("readinglist");
     }
   },
 
   onPageEvent: function(aMessage, aEvent) {
     let browser = aMessage.target;
-    let window = browser.ownerDocument.defaultView;
+    let window = browser.ownerGlobal;
 
     // Does the window have tabs? We need to make sure since windowless browsers do
     // not have tabs.
     if (!window.gBrowser) {
       // When using windowless browsers we don't have a valid |window|. If that's the case,
       // use the most recent window as a target for UITour functions (see Bug 1111022).
       window = Services.wm.getMostRecentWindow("navigator:browser");
     }
@@ -716,17 +716,17 @@ this.UITour = {
         break;
       }
 
       case "closeTab": {
         // Find the <tabbrowser> element of the <browser> for which the event
         // was generated originally. If the browser where the UI tour is loaded
         // is windowless, just ignore the request to close the tab. The request
         // is also ignored if this is the only tab in the window.
-        let tabBrowser = browser.ownerDocument.defaultView.gBrowser;
+        let tabBrowser = browser.ownerGlobal.gBrowser;
         if (tabBrowser && tabBrowser.browsers.length > 1) {
           tabBrowser.removeTab(tabBrowser.getTabForBrowser(browser));
         }
         break;
       }
     }
 
     this.initForBrowser(browser, window);
@@ -750,17 +750,17 @@ this.UITour = {
 
     window.addEventListener("SSWindowClosing", this);
   },
 
   handleEvent: function(aEvent) {
     log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
     switch (aEvent.type) {
       case "TabSelect": {
-        let window = aEvent.target.ownerDocument.defaultView;
+        let window = aEvent.target.ownerGlobal;
 
         // Teardown the browser of the tab we just switched away from.
         if (aEvent.detail && aEvent.detail.previousTab) {
           let previousTab = aEvent.detail.previousTab;
           let openTourWindows = this.tourBrowsersByWindow.get(window);
           if (openTourWindows.has(previousTab.linkedBrowser)) {
             this.teardownTourForBrowser(window, previousTab.linkedBrowser, false);
           }
@@ -967,17 +967,17 @@ this.UITour = {
 
   sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
     let detail = {data: aData, callbackID: aCallbackID};
     log.debug("sendPageCallback", detail);
     aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
   },
 
   isElementVisible: function(aElement) {
-    let targetStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
+    let targetStyle = aElement.ownerGlobal.getComputedStyle(aElement);
     return !aElement.ownerDocument.hidden &&
              targetStyle.display != "none" &&
              targetStyle.visibility == "visible";
   },
 
   getTarget: function(aWindow, aTargetName, aSticky = false) {
     log.debug("getTarget:", aTargetName);
     let deferred = Promise.defer();
@@ -1820,17 +1820,17 @@ this.UITour = {
       panel.hidePopup();
     } else if (aMenuName == "loop") {
       let panel = aWindow.document.getElementById("loop-notification-panel");
       panel.hidePopup();
     }
   },
 
   hideAnnotationsForPanel: function(aEvent, aTargetPositionCallback) {
-    let win = aEvent.target.ownerDocument.defaultView;
+    let win = aEvent.target.ownerGlobal;
     let annotationElements = new Map([
       // [annotationElement (panel), method to hide the annotation]
       [win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
       [win.document.getElementById("UITourTooltip"), UITour.hideInfo.bind(UITour)],
     ]);
     annotationElements.forEach((hideMethod, annotationElement) => {
       if (annotationElement.state != "closed") {
         let targetName = annotationElement.getAttribute("targetName");
@@ -2058,17 +2058,17 @@ this.UITour = {
   },
 
   _addAnnotationPanelMutationObserver: function(aPanelEl) {
     if (AppConstants.platform == "linux") {
       let observer = this._annotationPanelMutationObservers.get(aPanelEl);
       if (observer) {
         return;
       }
-      let win = aPanelEl.ownerDocument.defaultView;
+      let win = aPanelEl.ownerGlobal;
       observer = new win.MutationObserver(this._annotationMutationCallback);
       this._annotationPanelMutationObservers.set(aPanelEl, observer);
       let observerOptions = {
         attributeFilter: ["height", "width"],
         attributes: true,
       };
       observer.observe(aPanelEl, observerOptions);
     }
--- a/browser/components/uitour/test/head.js
+++ b/browser/components/uitour/test/head.js
@@ -49,33 +49,33 @@ function taskify(fun) {
     return Task.spawn(fun).then(done, (reason) => {
       ok(false, reason);
       done();
     });
   };
 }
 
 function is_hidden(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
   if (style.display == "-moz-popup")
     return ["hiding","closed"].indexOf(element.state) != -1;
 
   // Hiding a parent element will hide all its children
   if (element.parentNode != element.ownerDocument)
     return is_hidden(element.parentNode);
 
   return false;
 }
 
 function is_visible(element) {
-  var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
+  var style = element.ownerGlobal.getComputedStyle(element);
   if (style.display == "none")
     return false;
   if (style.visibility != "visible")
     return false;
   if (style.display == "-moz-popup" && element.state != "open")
     return false;
 
   // Hiding a parent element will hide all its children
@@ -218,17 +218,17 @@ function promisePanelElementHidden(win, 
 }
 
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
   ok(is_hidden(element), msg);
 }
 
 function isTourBrowser(aBrowser) {
-  let chromeWindow = aBrowser.ownerDocument.defaultView;
+  let chromeWindow = aBrowser.ownerGlobal;
   return UITour.tourBrowsersByWindow.has(chromeWindow) &&
          UITour.tourBrowsersByWindow.get(chromeWindow).has(aBrowser);
 }
 
 function promisePageEvent() {
   return new Promise((resolve) => {
     Services.mm.addMessageListener("UITour:onPageEvent", function onPageEvent(aMessage) {
       Services.mm.removeMessageListener("UITour:onPageEvent", onPageEvent);
--- a/browser/extensions/pocket/content/Pocket.jsm
+++ b/browser/extensions/pocket/content/Pocket.jsm
@@ -60,17 +60,17 @@ var Pocket = {
     let window = document.defaultView;
     let iframe = window.pktUI.getPanelFrame();
 
     iframe.removeEventListener("load", Pocket.onFrameLoaded, true);
     window.pktUI.pocketPanelDidShow();
   },
 
   onPanelViewHiding(event) {
-    let window = event.target.ownerDocument.defaultView;
+    let window = event.target.ownerGlobal;
     window.pktUI.pocketPanelDidHide(event);
   },
 
   _urlToSave: null,
   _titleToSave: null,
   savePage(browser, url, title) {
     let document = browser.ownerDocument;
     let pocketWidget = document.getElementById("pocket-button");
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -33,8 +33,23 @@ syncStartNotification.body2 = %S will be
 # LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
 # These strings are used in a notification shown after Sync was disconnected remotely.
 deviceDisconnectedNotification.title = Sync disconnected
 deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
 
 # LOCALIZATION NOTE (sendTabToAllDevices.menuitem)
 # Displayed in the Send Tabs context menu when right clicking a tab, a page or a link.
 sendTabToAllDevices.menuitem = All Devices
+
+# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotification.body,
+# tabsArrivingNotification.title, tabsArrivingNotification.body)
+# These strings are used in a notification shown when we're opening tab(s) another device sent us to display.
+tabArrivingNotification.title = Tab received
+# LOCALIZATION NOTE (tabArrivingNotification.body) %1 is the title of the tab and %2 is the device name.
+tabArrivingNotification.body = “%1$S” has arrived from %2$S.
+
+tabsArrivingNotification.title = Multiple tabs received
+# LOCALIZATION NOTE (tabsArrivingNotification.body) %1 is the number of tabs received and %2 is the device name.
+tabsArrivingNotification.body = %1$S tabs have arrived from %2$S.
+# LOCALIZATION NOTE (tabsArrivingNotificationMultiple.body)
+# This string is used in a notification shown when we're opening tab(s) that several devices sent us to display.
+# %S is the number of tabs received
+tabsArrivingNotificationMultiple.body = %S tabs have arrived from your connected devices.
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -105,17 +105,17 @@ var AboutHome = {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
     }
   },
 
   receiveMessage: function(aMessage) {
-    let window = aMessage.target.ownerDocument.defaultView;
+    let window = aMessage.target.ownerGlobal;
 
     switch (aMessage.name) {
       case "AboutHome:RestorePreviousSession":
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].
                  getService(Ci.nsISessionStore);
         if (ss.canRestoreLastSession) {
           ss.restoreLastSession();
         }
new file mode 100644
--- /dev/null
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -0,0 +1,175 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Observed topic names.
+const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
+const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
+const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
+const DOMWINDOW_CLOSED_TOPIC = "domwindowclosed";
+
+// Probe names.
+const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
+const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
+const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
+const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
+
+function getOpenTabsAndWinsCounts() {
+  let tabCount = 0;
+  let winCount = 0;
+
+  let browserEnum = Services.wm.getEnumerator("navigator:browser");
+  while (browserEnum.hasMoreElements()) {
+    let win = browserEnum.getNext();
+    winCount++;
+    tabCount += win.gBrowser.tabs.length;
+  }
+
+  return { tabCount, winCount };
+}
+
+let BrowserUsageTelemetry = {
+  init() {
+    Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false);
+  },
+
+  /**
+   * Handle subsession splits in the parent process.
+   */
+  afterSubsessionSplit() {
+    // Scalars just got cleared due to a subsession split. We need to set the maximum
+    // concurrent tab and window counts so that they reflect the correct value for the
+    // new subsession.
+    const counts = getOpenTabsAndWinsCounts();
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+    Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+    Services.obs.removeObserver(this, DOMWINDOW_CLOSED_TOPIC, false);
+    Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+    Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
+  },
+
+  observe(subject, topic, data) {
+    switch(topic) {
+      case WINDOWS_RESTORED_TOPIC:
+        this._setupAfterRestore();
+        break;
+      case DOMWINDOW_OPENED_TOPIC:
+        this._onWindowOpen(subject);
+        break;
+      case DOMWINDOW_CLOSED_TOPIC:
+        this._unregisterWindow(subject);
+        break;
+      case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
+        this.afterSubsessionSplit();
+        break;
+    }
+  },
+
+  handleEvent(event) {
+    switch(event.type) {
+      case "TabOpen":
+        this._onTabOpen();
+        break;
+    }
+  },
+
+  /**
+   * This gets called shortly after the SessionStore has finished restoring
+   * windows and tabs. It counts the open tabs and adds listeners to all the
+   * windows.
+   */
+  _setupAfterRestore() {
+    // Make sure to catch new chrome windows and subsession splits.
+    Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+    Services.obs.addObserver(this, DOMWINDOW_CLOSED_TOPIC, false);
+    Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+
+    // Attach the tabopen handlers to the existing Windows.
+    let browserEnum = Services.wm.getEnumerator("navigator:browser");
+    while (browserEnum.hasMoreElements()) {
+      this._registerWindow(browserEnum.getNext());
+    }
+
+    // Get the initial tab and windows max counts.
+    const counts = getOpenTabsAndWinsCounts();
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+    Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+  },
+
+  /**
+   * Adds listeners to a single chrome window.
+   */
+  _registerWindow(win) {
+    win.addEventListener("TabOpen", this, true);
+  },
+
+  /**
+   * Removes listeners from a single chrome window.
+   */
+  _unregisterWindow(win) {
+    // Ignore non-browser windows.
+    if (!(win instanceof Ci.nsIDOMWindow) ||
+        win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+      return;
+    }
+
+    win.removeEventListener("TabOpen", this, true);
+  },
+
+  /**
+   * Updates the tab counts.
+   * @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This
+   *        is computed manually if not provided.
+   */
+  _onTabOpen(tabCount = 0) {
+    // Use the provided tab count if available. Otherwise, go on and compute it.
+    tabCount = tabCount || getOpenTabsAndWinsCounts().tabCount;
+    // Update the "tab opened" count and its maximum.
+    Services.telemetry.scalarAdd(TAB_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+    Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, tabCount);
+  },
+
+  /**
+   * Tracks the window count and registers the listeners for the tab count.
+   * @param{Object} win The window object.
+   */
+  _onWindowOpen(win) {
+    // Make sure to have a |nsIDOMWindow|.
+    if (!(win instanceof Ci.nsIDOMWindow)) {
+      return;
+    }
+
+    let onLoad = () => {
+      win.removeEventListener("load", onLoad, false);
+
+      // Ignore non browser windows.
+      if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+        return;
+      }
+
+      this._registerWindow(win);
+      // Track the window open event and check the maximum.
+      const counts = getOpenTabsAndWinsCounts();
+      Services.telemetry.scalarAdd(WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+      Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+
+      // We won't receive the "TabOpen" event for the first tab within a new window.
+      // Account for that.
+      this._onTabOpen(counts.tabCount);
+    };
+    win.addEventListener("load", onLoad, false);
+  },
+};
--- a/browser/modules/CaptivePortalWatcher.jsm
+++ b/browser/modules/CaptivePortalWatcher.jsm
@@ -160,17 +160,17 @@ this.CaptivePortalWatcher = {
     this._captivePortalTab = null;
 
     // Check parentNode in case the object hasn't been gc'd yet.
     if (!tab || tab.closing || !tab.parentNode) {
       // User has closed the tab already.
       return;
     }
 
-    let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
+    let tabbrowser = tab.ownerGlobal.gBrowser;
 
     // If after the login, the captive portal has redirected to some other page,
     // leave it open if the tab has focus.
     if (tab.linkedBrowser.currentURI.spec != this.canonicalURL &&
         tabbrowser.selectedTab == tab) {
       return;
     }
 
--- a/browser/modules/CastingApps.jsm
+++ b/browser/modules/CastingApps.jsm
@@ -74,17 +74,17 @@ var CastingApps = {
       return;
     }
 
     // Make sure we have a player app for the given service
     let app = SimpleServiceDiscovery.findAppForService(service);
     if (!app)
       return;
 
-    video.title = videoElement.ownerDocument.defaultView.top.document.title;
+    video.title = videoElement.ownerGlobal.top.document.title;
     if (video.element) {
       // If the video is currently playing on the device, pause it
       if (!video.element.paused) {
         video.element.pause();
       }
     }
 
     app.stop(() => {
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -27,17 +27,17 @@ var ContentClick = {
         this.contentAreaClick(message.json, message.target)
         break;
     }
   },
 
   contentAreaClick: function (json, browser) {
     // This is heavily based on contentAreaClick from browser.js (Bug 903016)
     // The json is set up in a way to look like an Event.
-    let window = browser.ownerDocument.defaultView;
+    let window = browser.ownerGlobal;
 
     if (!json.href) {
       // Might be middle mouse navigation.
       if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
           !Services.prefs.getBoolPref("general.autoScroll")) {
         window.middleMousePaste(json);
       }
       return;
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -74,17 +74,17 @@ this.TabCrashHandler = {
 
         this.browserMap.set(browser.permanentKey, aSubject.childID);
         break;
     }
   },
 
   receiveMessage: function(message) {
     let browser = message.target.browser;
-    let gBrowser = browser.ownerDocument.defaultView.gBrowser;
+    let gBrowser = browser.ownerGlobal.gBrowser;
     let tab = gBrowser.getTabForBrowser(browser);
 
     switch(message.name) {
       case "Load": {
         this.onAboutTabCrashedLoad(message);
         break;
       }
 
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -38,17 +38,17 @@ this.ContentLinkHandler = {
 
   onLinkEvent: function(event, chromeGlobal) {
     var link = event.originalTarget;
     var rel = link.rel && link.rel.toLowerCase();
     if (!link || !link.ownerDocument || !rel || !link.href)
       return;
 
     // Ignore sub-frames (bugs 305472, 479408).
-    let window = link.ownerDocument.defaultView;
+    let window = link.ownerGlobal;
     if (window != window.top)
       return;
 
     var feedAdded = false;
     var iconAdded = false;
     var searchAdded = false;
     var rels = {};
     for (let relString of rel.split(/\s+/))
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -225,27 +225,22 @@ this.ContentSearch = {
       "engineName",
       "searchString",
       "healthReportKey",
       "searchPurpose",
     ]);
     let engine = Services.search.getEngineByName(data.engineName);
     let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
     let browser = msg.target;
-    let win;
-    try {
-      win = browser.ownerDocument.defaultView;
-    }
-    catch (err) {
+    let win = browser.ownerGlobal;
+    if (!win) {
       // The browser may have been closed between the time its content sent the
-      // message and the time we handle it.  In that case, trying to call any
-      // method on it will throw.
+      // message and the time we handle it.
       return;
     }
-
     let where = win.whereToOpenLink(data.originalEvent);
 
     // There is a chance that by the time we receive the search message, the user
     // has switched away from the tab that triggered the search. If, based on the
     // event, we need to load the search in the same tab that triggered it (i.e.
     // where === "current"), openUILinkIn will not work because that tab is no
     // longer the current one. For this case we manually load the URI.
     if (where === "current") {
@@ -419,17 +414,17 @@ this.ContentSearch = {
     this.performSearch(msg, data);
   },
 
   _onMessageSetCurrentEngine: function (msg, data) {
     Services.search.currentEngine = Services.search.getEngineByName(data);
   },
 
   _onMessageManageEngines: function (msg, data) {
-    let browserWin = msg.target.ownerDocument.defaultView;
+    let browserWin = msg.target.ownerGlobal;
     browserWin.openPreferences("paneSearch");
   },
 
   _onMessageGetSuggestions: Task.async(function* (msg, data) {
     this._ensureDataHasProperties(data, [
       "engineName",
       "searchString",
     ]);
--- a/browser/modules/FormSubmitObserver.jsm
+++ b/browser/modules/FormSubmitObserver.jsm
@@ -102,17 +102,17 @@ FormSubmitObserver.prototype =
     // panel attached to the element.
     if (!aInvalidElements.length) {
       return;
     }
 
     // Insure that this is the FormSubmitObserver associated with the
     // element / window this notification is about.
     let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
-    if (this._content != element.ownerDocument.defaultView.top.document.defaultView) {
+    if (this._content != element.ownerGlobal.top.document.defaultView) {
       return;
     }
 
     if (!(element instanceof HTMLInputElement ||
           element instanceof HTMLTextAreaElement ||
           element instanceof HTMLSelectElement ||
           element instanceof HTMLButtonElement)) {
       return;
@@ -196,17 +196,17 @@ FormSubmitObserver.prototype =
     // and where the content begin for the other elements.
     let offset = 0;
     let position = "";
 
     if (aElement.tagName == 'INPUT' &&
         (aElement.type == 'radio' || aElement.type == 'checkbox')) {
       panelData.position = "bottomcenter topleft";
     } else {
-      let win = aElement.ownerDocument.defaultView;
+      let win = aElement.ownerGlobal;
       let style = win.getComputedStyle(aElement, null);
       if (style.direction == 'rtl') {
         offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
       } else {
         offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
       }
       let zoomFactor = this._getWindowUtils().fullZoom;
       panelData.offset = Math.round(offset * zoomFactor);
--- a/browser/modules/FormValidationHandler.jsm
+++ b/browser/modules/FormValidationHandler.jsm
@@ -43,17 +43,17 @@ var FormValidationHandler =
     this._hidePopup();
   },
 
   /*
    * Events
    */
 
   receiveMessage: function (aMessage) {
-    let window = aMessage.target.ownerDocument.defaultView;
+    let window = aMessage.target.ownerGlobal;
     let json = aMessage.json;
     let tabBrowser = window.gBrowser;
     switch (aMessage.name) {
       case "FormValidation:ShowPopup":
         // target is the <browser>, make sure we're receiving a message
         // from the foreground tab.
         if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
           return;
--- a/browser/modules/NetworkPrioritizer.jsm
+++ b/browser/modules/NetworkPrioritizer.jsm
@@ -69,22 +69,22 @@ function _handleEvent(aEvent) {
 
 
 // Methods that impact a browser. Put into single object for organization.
 var BrowserHelper = {
   onOpen: function NP_BH_onOpen(aBrowser) {
     _priorityBackup.set(aBrowser.permanentKey, Ci.nsISupportsPriority.PRIORITY_NORMAL);
 
     // If the tab is in the focused window, leave priority as it is
-    if (aBrowser.ownerDocument.defaultView != _lastFocusedWindow)
+    if (aBrowser.ownerGlobal != _lastFocusedWindow)
       this.decreasePriority(aBrowser);
   },
 
   onSelect: function NP_BH_onSelect(aBrowser) {
-    let windowEntry = WindowHelper.getEntry(aBrowser.ownerDocument.defaultView);
+    let windowEntry = WindowHelper.getEntry(aBrowser.ownerGlobal);
     if (windowEntry.lastSelectedBrowser)
       this.decreasePriority(windowEntry.lastSelectedBrowser);
     this.increasePriority(aBrowser);
 
     windowEntry.lastSelectedBrowser = aBrowser;
   },
 
   onRemotenessChange: function (aBrowser) {
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -242,17 +242,17 @@ PluginContent.prototype = {
                    [right, top],
                    [right, bottom],
                    [centerX, centerY]];
 
     if (right <= 0 || top <= 0) {
       return false;
     }
 
-    let contentWindow = plugin.ownerDocument.defaultView;
+    let contentWindow = plugin.ownerGlobal;
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
 
     for (let [x, y] of points) {
       let el = cwu.elementFromPoint(x, y, true, true);
       if (el !== plugin) {
         return false;
       }
@@ -521,17 +521,17 @@ PluginContent.prototype = {
     // if this isn't a known plugin, we can't activate it
     // (this also guards pluginHost.getPermissionStringForType against
     // unexpected input)
     if (!this.isKnownPlugin(objLoadingContent))
       return false;
 
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
     let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
-    let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
+    let principal = objLoadingContent.ownerGlobal.top.document.nodePrincipal;
     let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
 
     let isFallbackTypeValid =
       objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
       objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
 
     return !objLoadingContent.activated &&
            pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
@@ -607,17 +607,17 @@ PluginContent.prototype = {
     if (overlay) {
       overlay.addEventListener("click", this, true);
     }
   },
 
   onOverlayClick: function (event) {
     let document = event.target.ownerDocument;
     let plugin = document.getBindingParent(event.target);
-    let contentWindow = plugin.ownerDocument.defaultView.top;
+    let contentWindow = plugin.ownerGlobal.top;
     let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     let overlay = this.getPluginUI(plugin, "main");
     // Have to check that the target is not the link to update the plugin
     if (!(event.originalTarget instanceof contentWindow.HTMLAnchorElement) &&
         (event.originalTarget.getAttribute('anonid') != 'closeIcon') &&
         !overlay.hasAttribute('dismissed') &&
         event.button == 0 &&
         event.isTrusted) {
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -342,17 +342,17 @@ var ProcessHangMonitor = {
   },
 
   untrackWindow: function(win) {
     win.gBrowser.tabContainer.removeEventListener("TabSelect", this, true);
     win.gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this, true);
   },
 
   handleEvent: function(event) {
-    let win = event.target.ownerDocument.defaultView;
+    let win = event.target.ownerGlobal;
 
     // If a new tab is selected or if a tab changes remoteness, then
     // we may need to show or hide a hang notification.
 
     if (event.type == "TabSelect" || event.type == "TabRemotenessChange") {
       this.updateWindow(win);
     }
   },
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -76,17 +76,17 @@ var ReaderParent = {
         }
         this.updateReaderButton(browser);
         break;
       }
     }
   },
 
   updateReaderButton: function(browser) {
-    let win = browser.ownerDocument.defaultView;
+    let win = browser.ownerGlobal;
     if (browser != win.gBrowser.selectedBrowser) {
       return;
     }
 
     let button = win.document.getElementById("reader-mode-button");
     let command = win.document.getElementById("View:ReaderView");
     if (browser.currentURI.spec.startsWith("about:reader")) {
       button.setAttribute("readeractive", true);
@@ -129,28 +129,28 @@ var ReaderParent = {
   buttonClick(event) {
     if (event.button != 0) {
       return;
     }
     this.toggleReaderMode(event);
   },
 
   toggleReaderMode: function(event) {
-    let win = event.target.ownerDocument.defaultView;
+    let win = event.target.ownerGlobal;
     let browser = win.gBrowser.selectedBrowser;
     browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
   },
 
   /**
    * Shows an info panel from the UITour for Reader Mode.
    *
    * @param browser The <browser> that the tour should be started for.
    */
   showReaderModeInfoPanel(browser) {
-    let win = browser.ownerDocument.defaultView;
+    let win = browser.ownerGlobal;
     let targetPromise = UITour.getTarget(win, "readerMode-urlBar");
     targetPromise.then(target => {
       let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
       let icon = "chrome://browser/skin/";
       if (win.devicePixelRatio > 1) {
         icon += "reader-tour@2x.png";
       } else {
         icon += "reader-tour.png";
--- a/browser/modules/RemotePrompt.jsm
+++ b/browser/modules/RemotePrompt.jsm
@@ -30,17 +30,17 @@ var RemotePrompt = {
         } else {
           this.openTabPrompt(message.data, message.target)
         }
         break;
     }
   },
 
   openTabPrompt: function(args, browser) {
-    let window = browser.ownerDocument.defaultView;
+    let window = browser.ownerGlobal;
     let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
     let callbackInvoked = false;
     let newPrompt;
     let needRemove = false;
     let promptId = args._remoteId;
 
     function onPromptClose(forceCleanup) {
       // It's possible that we removed the prompt during the
@@ -89,17 +89,17 @@ var RemotePrompt = {
       // there's other stuff in nsWindowWatcher::OpenWindowInternal
       // that we might need to do here as well.
     } catch (ex) {
       onPromptClose(true);
     }
   },
 
   openModalWindow: function(args, browser) {
-    let window = browser.ownerDocument.defaultView;
+    let window = browser.ownerGlobal;
     try {
       PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
       let bag = PromptUtils.objectToPropBag(args);
 
       Services.ww.openWindow(window, args.uri, "_blank",
                              "centerscreen,chrome,modal,titlebar", bag);
 
       PromptUtils.propBagToObject(bag, args);
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -9,16 +9,17 @@ XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/social/xpcshell.ini',
     'test/xpcshell/xpcshell.ini',
 ]
 
 EXTRA_JS_MODULES += [
     'AboutHome.jsm',
     'AboutNewTab.jsm',
     'BrowserUITelemetry.jsm',
+    'BrowserUsageTelemetry.jsm',
     'CaptivePortalWatcher.jsm',
     'CastingApps.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentLinkHandler.jsm',
     'ContentObservers.jsm',
     'ContentSearch.jsm',
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -18,8 +18,9 @@ support-files =
   contentSearchSuggestions.xml
 [browser_NetworkPrioritizer.js]
 [browser_SelfSupportBackend.js]
 support-files =
   ../../components/uitour/test/uitour.html
   ../../components/uitour/UITour-lib.js
 [browser_taskbar_preview.js]
 skip-if = os != "win"
+[browser_UsageTelemetry.js]
--- a/browser/modules/test/browser_SelfSupportBackend.js
+++ b/browser/modules/test/browser_SelfSupportBackend.js
@@ -15,16 +15,22 @@ const PREF_SELFSUPPORT_ENABLED = "browse
 const PREF_SELFSUPPORT_URL = "browser.selfsupport.url";
 const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
 
 const TEST_WAIT_RETRIES = 60;
 
 const TEST_PAGE_URL = getRootDirectory(gTestPath) + "uitour.html";
 const TEST_PAGE_URL_HTTPS = TEST_PAGE_URL.replace("chrome://mochitests/content/", "https://example.com/");
 
+function sendSessionRestoredNotification() {
+  let selfSupportBackendImpl =
+    Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
+  selfSupportBackendImpl.observe(null, "sessionstore-windows-restored", null);
+}
+
 /**
  * Find a browser, with an IFRAME as parent, who has aURL as the source attribute.
  *
  * @param aURL The URL to look for to identify the browser.
  *
  * @returns {Object} The browser element or null on failure.
  */
 function findSelfSupportBrowser(aURL) {
@@ -123,17 +129,17 @@ add_task(function* setupEnvironment() {
  * Test that the self support page can use the UITour API and close itself.
  */
 add_task(function* test_selfSupport() {
   // Initialise the SelfSupport backend and trigger the load.
   SelfSupportBackend.init();
 
   // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
   info("Sending sessionstore-windows-restored");
-  Services.obs.notifyObservers(null, "sessionstore-windows-restored", null);
+  sendSessionRestoredNotification();
 
   // Wait for the SelfSupport page to load.
   info("Waiting for the SelfSupport local page to load.");
   let selfSupportBrowser = yield promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
   Assert.ok(!!selfSupportBrowser, "SelfSupport browser must exist.");
 
   // Get a reference to the UITour API.
   info("Testing access to the UITour API.");
@@ -191,17 +197,17 @@ add_task(function* test_selfSupport() {
  */
 add_task(function* test_selfSupport_noHTTPS() {
   Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL);
 
   SelfSupportBackend.init();
 
   // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
   info("Sending sessionstore-windows-restored");
-  Services.obs.notifyObservers(null, "sessionstore-windows-restored", null);
+  sendSessionRestoredNotification();
 
   // Find the SelfSupport browser. We don't expect to find it since we are not using https.
   let selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL);
   Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
 
   // We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
   // doesn't create any problem.
   SelfSupportBackend.uninit();
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry.js
@@ -0,0 +1,143 @@
+"use strict";
+
+const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
+const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
+const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
+const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
+
+const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";
+
+/**
+ * An helper that checks the value of a scalar if it's expected to be > 0,
+ * otherwise makes sure that the scalar it's not reported.
+ */
+let checkScalar = (scalars, scalarName, value, msg) => {
+  if (value > 0) {
+    is(scalars[scalarName], value, msg);
+    return;
+  }
+  ok(!(scalarName in scalars), scalarName + " must not be reported.");
+};
+
+/**
+ * Get a snapshot of the scalars and check them against the provided values.
+ */
+let checkScalars = (maxTabs, tabOpenCount, maxWindows, windowsOpenCount) => {
+  const scalars =
+    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+  // Check the expected values. Scalars that are never set must not be reported.
+  checkScalar(scalars, MAX_CONCURRENT_TABS, maxTabs,
+              "The maximum tab count must match the expected value.");
+  checkScalar(scalars, TAB_EVENT_COUNT, tabOpenCount,
+              "The number of open tab event count must match the expected value.");
+  checkScalar(scalars, MAX_CONCURRENT_WINDOWS, maxWindows,
+              "The maximum window count must match the expected value.");
+  checkScalar(scalars, WINDOW_OPEN_COUNT, windowsOpenCount,
+              "The number of window open event count must match the expected value.");
+};
+
+add_task(function* test_tabsAndWindows() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let openedTabs = [];
+  let expectedTabOpenCount = 0;
+  let expectedWinOpenCount = 0;
+  let expectedMaxTabs = 0;
+  let expectedMaxWins = 0;
+
+  // Add a new tab and check that the count is right.
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+  expectedTabOpenCount = 1;
+  expectedMaxTabs = 2;
+  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount);
+
+  // Add two new tabs in the same window.
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+  expectedTabOpenCount += 2;
+  expectedMaxTabs += 2;
+  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount);
+
+  // Add a new window and then some tabs in it. An empty new windows counts as a tab.
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+  // The new window started with a new tab, so account for it.
+  expectedTabOpenCount += 4;
+  expectedWinOpenCount += 1;
+  expectedMaxWins = 2;
+  expectedMaxTabs += 4;
+
+  // Remove a tab from the first window, the max shouldn't change.
+  yield BrowserTestUtils.removeTab(openedTabs.pop());
+  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount);
+
+  // Remove all the extra windows and tabs.
+  for (let tab of openedTabs) {
+    yield BrowserTestUtils.removeTab(tab);
+  }
+  yield BrowserTestUtils.closeWindow(win);
+
+  // Make sure all the scalars still have the expected values.
+  checkScalars(expectedMaxTabs, expectedTabOpenCount, expectedMaxWins, expectedWinOpenCount);
+});
+
+add_task(function* test_subsessionSplit() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  // Add a new window (that will have 4 tabs).
+  let win = yield BrowserTestUtils.openNewBrowserWindow();
+  let openedTabs = [];
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+  openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+
+  // Check that the scalars have the right values.
+  checkScalars(5 /*maxTabs*/, 4 /*tabOpen*/, 2 /*maxWins*/, 1 /*winOpen*/);
+
+  // Remove a tab.
+  yield BrowserTestUtils.removeTab(openedTabs.pop());
+
+  // Simulate a subsession split by clearing the scalars (via |snapshotScalars|) and
+  // notifying the subsession split topic.
+  Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
+                                     true /* clearScalars*/);
+  Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");
+
+  // After a subsession split, only the MAX_CONCURRENT_* scalars must be available
+  // and have the correct value. No tabs or windows were opened so other scalars
+  // must not be reported.
+  checkScalars(4 /*maxTabs*/, 0 /*tabOpen*/, 2 /*maxWins*/, 0 /*winOpen*/);
+
+  // Remove all the extra windows and tabs.
+  for (let tab of openedTabs) {
+    yield BrowserTestUtils.removeTab(tab);
+  }
+  yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* test_privateMode() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  // Open a private window and load a website in it.
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+  yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
+  yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
+
+  // Check that tab and window count is recorded.
+  const scalars =
+    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+  is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value.");
+  is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value.");
+  is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value.");
+  is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value.");
+
+  // Clean up.
+  yield BrowserTestUtils.closeWindow(privateWin);
+});
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -109,32 +109,32 @@ this.webrtcUI = {
       return aCamera && state.camera ||
              aMicrophone && state.microphone ||
              aScreen && state.screen;
     }).map(aStream => {
       let state = aStream.state;
       let types = {camera: state.camera, microphone: state.microphone,
                    screen: state.screen};
       let browser = aStream.browser;
-      let browserWindow = browser.ownerDocument.defaultView;
+      let browserWindow = browser.ownerGlobal;
       let tab = browserWindow.gBrowser &&
                 browserWindow.gBrowser.getTabForBrowser(browser);
       return {uri: state.documentURI, tab: tab, browser: browser, types: types};
     });
   },
 
   swapBrowserForNotification: function(aOldBrowser, aNewBrowser) {
     for (let stream of this._streams) {
       if (stream.browser == aOldBrowser)
         stream.browser = aNewBrowser;
     }
   },
 
   showSharingDoorhanger: function(aActiveStream, aType) {
-    let browserWindow = aActiveStream.browser.ownerDocument.defaultView;
+    let browserWindow = aActiveStream.browser.ownerGlobal;
     if (aActiveStream.tab) {
       browserWindow.gBrowser.selectedTab = aActiveStream.tab;
     } else {
       aActiveStream.browser.focus();
     }
     browserWindow.focus();
     let PopupNotifications = browserWindow.PopupNotifications;
     let notif = PopupNotifications.getNotification("webRTC-sharing" + aType,
@@ -588,17 +588,17 @@ function prompt(aBrowser, aRequest) {
   notification =
     chromeWin.PopupNotifications.show(aBrowser, "webRTC-shareDevices", message,
                                       anchorId, mainAction, secondaryActions,
                                       options);
   notification.callID = aRequest.callID;
 }
 
 function removePrompt(aBrowser, aCallId) {
-  let chromeWin = aBrowser.ownerDocument.defaultView;
+  let chromeWin = aBrowser.ownerGlobal;
   let notification =
     chromeWin.PopupNotifications.getNotification("webRTC-shareDevices", aBrowser);
   if (notification && notification.callID == aCallId)
     notification.remove();
 }
 
 function getGlobalIndicator() {
   if (AppConstants.platform != "macosx") {
@@ -870,17 +870,17 @@ function updateIndicators(data, target) 
       gIndicatorWindow.updateIndicatorState();
   } else if (gIndicatorWindow) {
     gIndicatorWindow.close();
     gIndicatorWindow = null;
   }
 }
 
 function updateBrowserSpecificIndicator(aBrowser, aState) {
-  let chromeWin = aBrowser.ownerDocument.defaultView;
+  let chromeWin = aBrowser.ownerGlobal;
   let tabbrowser = chromeWin.gBrowser;
   if (tabbrowser) {
     let sharing;
     if (aState.screen) {
       sharing = "screen";
     } else if (aState.camera) {
       sharing = "camera";
     } else if (aState.microphone) {
@@ -1004,14 +1004,14 @@ function updateBrowserSpecificIndicator(
   screenSharingNotif =
     chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingScreen",
                                       stringBundle.getString(stringId + ".message"),
                                       "webRTC-sharingScreen-notification-icon",
                                       mainAction, secondaryActions, options);
 }
 
 function removeBrowserNotification(aBrowser, aNotificationId) {
-  let win = aBrowser.ownerDocument.defaultView;
+  let win = aBrowser.ownerGlobal;
   let notification =
     win.PopupNotifications.getNotification(aNotificationId, aBrowser);
   if (notification)
     win.PopupNotifications.remove(notification);
 }
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -63,17 +63,17 @@ define(function (require, exports, modul
       if (array.length > max + 1) {
         items.pop();
 
         let objectLink = this.props.objectLink || DOM.span;
         items.push(Caption({
           key: "more",
           object: objectLink({
             object: this.props.object
-          }, "more...")
+          }, "more…")
         }));
       }
 
       return items;
     },
 
     /**
      * Returns true if the passed object is an array with additional (custom)
@@ -183,17 +183,17 @@ define(function (require, exports, modul
    */
   let Reference = React.createFactory(React.createClass({
     displayName: "Reference",
 
     render: function () {
       let tooltip = "Circular reference";
       return (
         DOM.span({title: tooltip},
-          "[...]")
+          "[…]")
       );
     }
   }));
 
   function supportsObject(object, type) {
     return Array.isArray(object) ||
       Object.prototype.toString.call(object) === "[object Arguments]";
   }
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -89,17 +89,17 @@ define(function (require, exports, modul
 
       if (array.length > max + 1) {
         items.pop();
         let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
           object: objectLink({
             object: this.props.object
-          }, "more...")
+          }, "more…")
         }));
       }
 
       return items;
     },
 
     render: function () {
       let mode = this.props.mode || "short";
@@ -169,17 +169,17 @@ define(function (require, exports, modul
    * Renders cycle references in an array.
    */
   let Reference = React.createFactory(React.createClass({
     displayName: "Reference",
 
     render: function () {
       return (
         span({title: "Circular reference"},
-          "[...]"
+          "[…]"
         )
       );
     }
   }));
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -69,36 +69,36 @@ define(function (require, exports, modul
       };
 
       // Object members with non-empty values are preferred since it gives the
       // user a better overview of the object.
       let props = this.getProps(object, max, isInterestingProp);
 
       if (props.length <= max) {
         // There are not enough props yet (or at least, not enough props to
-        // be able to know whether we should print "more..." or not).
+        // be able to know whether we should print "more…" or not).
         // Let's display also empty members and functions.
         props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
         }));
       }
 
       // getProps() can return max+1 properties (it can't return more)
       // to indicate that there is more props than allowed. Remove the last
-      // one and append 'more...' postfix in such case.
+      // one and append 'more…' postfix in such case.
       if (props.length > max) {
         props.pop();
 
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
           key: "more",
           object: objectLink({
             object: object
-          }, "more...")
+          }, "more…")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         // NOTE: do not change comp._store.props directly to update a property,
         // it should be re-rendered or cloned with changed props
         let last = props.length - 1;
         props[last] = React.cloneElement(props[last], {
           delim: ""
--- a/devtools/client/shared/components/reps/named-node-map.js
+++ b/devtools/client/shared/components/reps/named-node-map.js
@@ -50,17 +50,17 @@ define(function (require, exports, modul
 
       if (items.length > max + 1) {
         items.pop();
         let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
           object: objectLink({
             object: this.props.object
-          }, "more...")
+          }, "more…")
         }));
       }
 
       return items;
     },
 
     propIterator: function (grip, max) {
       max = max || 3;
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -66,32 +66,32 @@ define(function (require, exports, modul
       }
 
       // Object members with non-empty values are preferred since it gives the
       // user a better overview of the object.
       let props = this.getProps(object, max, isInterestingProp);
 
       if (props.length <= max) {
         // There are not enough props yet (or at least, not enough props to
-        // be able to know whether we should print "more..." or not).
+        // be able to know whether we should print "more…" or not).
         // Let's display also empty members and functions.
         props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
         }));
       }
 
       if (props.length > max) {
         props.pop();
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
           key: "more",
           object: objectLink({
             object: object
-          }, "more...")
+          }, "more…")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
       }
 
       return props;
--- a/devtools/client/shared/components/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -90,44 +90,44 @@ window.onload = Task.async(function* () 
       }
     ];
 
     testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
   }
 
   function testMoreThanMaxProps() {
     const stub = Array(302).fill("foo");
-    const defaultOutput = `["foo", "foo", "foo", more...]`;
+    const defaultOutput = `["foo", "foo", "foo", more…]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
         expectedOutput: `[302]`,
       },
       {
         mode: "short",
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
-        expectedOutput: `[${Array(300).fill("\"foo\"").join(", ")}, more...]`,
+        expectedOutput: `[${Array(300).fill("\"foo\"").join(", ")}, more…]`,
       }
     ];
 
     testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
   }
 
   function testRecursiveArray() {
     let stub = [1];
     stub.push(stub);
-    const defaultOutput = `[1, [...]]`;
+    const defaultOutput = `[1, […]]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -150,17 +150,17 @@ window.onload = Task.async(function* () 
     let stub = [
       {
         p1: "s1",
         p2: ["a1", "a2", "a3"],
         p3: "s3",
         p4: "s4"
       }
     ];
-    const defaultOutput = `[Object{p1: "s1", p3: "s3", p4: "s4", more...}]`;
+    const defaultOutput = `[Object{p1: "s1", p3: "s3", p4: "s4", more…}]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -91,37 +91,37 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanMaxProps() {
-    // Test array = `["test string"...] //301 items`
+    // Test array = `["test string"…] //301 items`
     const testName = "testMoreThanMaxProps";
 
-    const defaultOutput = `[${Array(3).fill("\"test string\"").join(", ")}, more...]`;
+    const defaultOutput = `[${Array(3).fill("\"test string\"").join(", ")}, more…]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
         expectedOutput: `[302]`,
       },
       {
         mode: "short",
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
-        expectedOutput: `[${Array(300).fill("\"test string\"").join(", ")}, more...]`,
+        expectedOutput: `[${Array(300).fill("\"test string\"").join(", ")}, more…]`,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testRecursiveArray() {
     // @TODO This is not how this feature should actually work
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -95,29 +95,29 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanMaxProps() {
-    // Test object = `{p0: "0", p1: "1", p2: "2", ..., p101: "101"}`
+    // Test object = `{p0: "0", p1: "1", p2: "2", …, p101: "101"}`
     const testName = "testMoreThanMaxProps";
 
-    const defaultOutput = `Object {p0: "0", p1: "1", p2: "2", more...}`;
+    const defaultOutput = `Object {p0: "0", p1: "1", p2: "2", more…}`;
 
     // Generate string with 100 properties, which is the max limit
     // for 'long' mode.
     let props = "";
     for (let i = 0; i < 100; i++) {
       props += "p" + i + ": \"" + i + "\", ";
     }
 
-    const longOutput = `Object {${props}more...}`;
+    const longOutput = `Object {${props}more…}`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -135,17 +135,17 @@ window.onload = Task.async(function* () 
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testUninterestingProps() {
     // Test object: `{a: undefined, b: undefined, c: "c", d: 1}`
     // @TODO This is not how we actually want the preview to be output.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1276376
-    const expectedOutput = `Object {a: undefined, b: undefined, c: "c", more...}`;
+    const expectedOutput = `Object {a: undefined, b: undefined, c: "c", more…}`;
   }
 
   function testNestedObject() {
     // Test object: `{objProp: {id: 1}, strProp: "test string"}`
     const testName = "testNestedObject";
 
     const defaultOutput = `Object {objProp: Object, strProp: "test string"}`;
 
--- a/devtools/client/shared/components/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object.html
@@ -96,17 +96,17 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
   }
 
   function testMoreThanMaxProps() {
     let stub = {};
     for (let i = 0; i<100; i++) {
       stub[`p${i}`] = i
     }
-    const defaultOutput = `Object{p0: 0, p1: 1, p2: 2, more...}`;
+    const defaultOutput = `Object{p0: 0, p1: 1, p2: 2, more…}`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -122,17 +122,17 @@ window.onload = Task.async(function* () 
       }
     ];
 
     testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
   }
 
   function testUninterestingProps() {
     const stub = {a:undefined, b:undefined, c:"c", d:0};
-    const defaultOutput = `Object{c: "c", d: 0, a: undefined, more...}`;
+    const defaultOutput = `Object{c: "c", d: 0, a: undefined, more…}`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -117,16 +117,17 @@ skip-if = e10s # Bug 1221911, bug 122228
 [browser_html_tooltip-02.js]
 [browser_html_tooltip-03.js]
 [browser_html_tooltip-04.js]
 [browser_html_tooltip-05.js]
 [browser_html_tooltip_arrow-01.js]
 [browser_html_tooltip_arrow-02.js]
 [browser_html_tooltip_consecutive-show.js]
 [browser_html_tooltip_offset.js]
+[browser_html_tooltip_rtl.js]
 [browser_html_tooltip_variable-height.js]
 [browser_html_tooltip_width-auto.js]
 [browser_html_tooltip_xul-wrapper.js]
 [browser_inplace-editor-01.js]
 [browser_inplace-editor-02.js]
 [browser_inplace-editor_autocomplete_01.js]
 [browser_inplace-editor_autocomplete_02.js]
 [browser_inplace-editor_autocomplete_offset.js]
--- a/devtools/client/shared/test/browser_html_tooltip-02.js
+++ b/devtools/client/shared/test/browser_html_tooltip-02.js
@@ -41,16 +41,17 @@ add_task(function* () {
   useXulWrapper = true;
   yield runTests(doc);
 });
 
 function* runTests(doc) {
   yield testClickInTooltipContent(doc);
   yield testConsumeOutsideClicksFalse(doc);
   yield testConsumeOutsideClicksTrue(doc);
+  yield testConsumeWithRightClick(doc);
   yield testClickInOuterIframe(doc);
   yield testClickInInnerIframe(doc);
 }
 
 function* testClickInTooltipContent(doc) {
   info("Test a tooltip is not closed when clicking inside itself");
 
   let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
@@ -101,16 +102,38 @@ function* testConsumeOutsideClicksTrue(d
   yield onHidden;
 
   is(box4clicks, 0, "box4 catched no click event");
   is(tooltip.isVisible(), false, "Tooltip is hidden");
 
   tooltip.destroy();
 }
 
+function* testConsumeWithRightClick(doc) {
+  info("Test closing a tooltip with a right-click, with consumeOutsideClicks: true");
+  let box4 = doc.getElementById("box4");
+
+  let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: true, useXulWrapper});
+  tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
+  yield showTooltip(tooltip, doc.getElementById("box1"));
+
+  // Only left-click events should be consumed, so we expect to catch a click when using
+  // {button: 2}, which simulates a right-click.
+  info("Right click on box4, expect tooltip to be hidden, event should not be consumed");
+  let onBox4Clicked = once(box4, "click");
+  let onHidden = once(tooltip, "hidden");
+  EventUtils.synthesizeMouseAtCenter(box4, {button: 2}, doc.defaultView);
+  yield onHidden;
+  yield onBox4Clicked;
+
+  is(tooltip.isVisible(), false, "Tooltip is hidden");
+
+  tooltip.destroy();
+}
+
 function* testClickInOuterIframe(doc) {
   info("Test clicking an iframe outside of the tooltip closes the tooltip");
   let frame = doc.getElementById("frame");
 
   let tooltip = new HTMLTooltip({doc}, {useXulWrapper});
   tooltip.setContent(getTooltipContent(doc), {width: 100, height: 50});
   yield showTooltip(tooltip, doc.getElementById("box1"));
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_rtl.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_html_tooltip.js */
+"use strict";
+
+/**
+ * Test the HTMLTooltip anchor alignment changes with the anchor direction.
+ * - should be aligned to the right of RTL anchors
+ * - should be aligned to the left of LTR anchors
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
+  <?xml-stylesheet href="chrome://global/skin/global.css"?>
+  <?xml-stylesheet href="chrome://devtools/skin/tooltips.css"?>
+  <window
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+    htmlns="http://www.w3.org/1999/xhtml"
+    title="Tooltip test">
+    <hbox style="padding: 90px 0;" flex="1">
+      <hbox id="box1" flex="1" style="background:red; direction: rtl;">test1</hbox>
+      <hbox id="box2" flex="1" style="background:blue; direction: rtl;">test2</hbox>
+      <hbox id="box3" flex="1" style="background:red; direction: ltr;">test3</hbox>
+      <hbox id="box4" flex="1" style="background:blue; direction: ltr;">test4</hbox>
+    </hbox>
+  </window>`;
+
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+const TOOLBOX_WIDTH = 500;
+const TOOLTIP_WIDTH = 150;
+const TOOLTIP_HEIGHT = 30;
+
+add_task(function* () {
+  // Force the toolbox to be 500px wide (min width is 465px);
+  yield pushPref("devtools.toolbox.sidebar.width", TOOLBOX_WIDTH);
+
+  let [,, doc] = yield createHost("side", TEST_URI);
+
+  info("Test a tooltip is not closed when clicking inside itself");
+
+  let tooltip = new HTMLTooltip({doc}, {useXulWrapper: false});
+  let div = doc.createElementNS(HTML_NS, "div");
+  div.textContent = "tooltip";
+  div.style.cssText = "box-sizing: border-box; border: 1px solid black";
+  tooltip.setContent(div, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
+
+  yield testRtlAnchors(doc, tooltip);
+  yield testLtrAnchors(doc, tooltip);
+  yield hideTooltip(tooltip);
+
+  tooltip.destroy();
+});
+
+function* testRtlAnchors(doc, tooltip) {
+  /*
+   * The layout of the test page is as follows:
+   *   _______________________________
+   *  | toolbox                       |
+   *  | _____   _____   _____   _____ |
+   *  ||     | |     | |     | |     ||
+   *  || box1| | box2| | box3| | box4||
+   *  ||_____| |_____| |_____| |_____||
+   *  |_______________________________|
+   *
+   * - box1 is aligned with the left edge of the toolbox
+   * - box2 is displayed right after box1
+   * - total toolbox width is 500px so each box is 125px wide
+  */
+
+  let box1 = doc.getElementById("box1");
+  let box2 = doc.getElementById("box2");
+
+  info("Display the tooltip on box1.");
+  yield showTooltip(tooltip, box1, {position: "bottom"});
+
+  let panelRect = tooltip.container.getBoundingClientRect();
+  let anchorRect = box1.getBoundingClientRect();
+
+  // box1 uses RTL direction, so the tooltip should be aligned with the right edge of the
+  // anchor, but it is shifted to the right to fit in the toolbox.
+  is(panelRect.left, 0, "Tooltip is aligned with left edge of the toolbox");
+  is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
+  is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
+
+  info("Display the tooltip on box2.");
+  yield showTooltip(tooltip, box2, {position: "bottom"});
+
+  panelRect = tooltip.container.getBoundingClientRect();
+  anchorRect = box2.getBoundingClientRect();
+
+  // box2 uses RTL direction, so the tooltip is aligned with the right edge of the anchor
+  is(panelRect.right, anchorRect.right, "Tooltip is aligned with right edge of anchor");
+  is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
+  is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
+}
+
+function* testLtrAnchors(doc, tooltip) {
+    /*
+   * The layout of the test page is as follows:
+   *   _______________________________
+   *  | toolbox                       |
+   *  | _____   _____   _____   _____ |
+   *  ||     | |     | |     | |     ||
+   *  || box1| | box2| | box3| | box4||
+   *  ||_____| |_____| |_____| |_____||
+   *  |_______________________________|
+   *
+   * - box3 is is displayed right after box2
+   * - box4 is aligned with the right edge of the toolbox
+   * - total toolbox width is 500px so each box is 125px wide
+  */
+
+  let box3 = doc.getElementById("box3");
+  let box4 = doc.getElementById("box4");
+
+  info("Display the tooltip on box3.");
+  yield showTooltip(tooltip, box3, {position: "bottom"});
+
+  let panelRect = tooltip.container.getBoundingClientRect();
+  let anchorRect = box3.getBoundingClientRect();
+
+  // box3 uses LTR direction, so the tooltip is aligned with the left edge of the anchor.
+  is(panelRect.left, anchorRect.left, "Tooltip is aligned with left edge of anchor");
+  is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
+  is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
+
+  info("Display the tooltip on box4.");
+  yield showTooltip(tooltip, box4, {position: "bottom"});
+
+  panelRect = tooltip.container.getBoundingClientRect();
+  anchorRect = box4.getBoundingClientRect();
+
+  // box4 uses LTR direction, so the tooltip should be aligned with the left edge of the
+  // anchor, but it is shifted to the left to fit in the toolbox.
+  is(panelRect.right, TOOLBOX_WIDTH, "Tooltip is aligned with right edge of toolbox");
+  is(panelRect.top, anchorRect.bottom, "Tooltip aligned with the anchor bottom edge");
+  is(panelRect.height, TOOLTIP_HEIGHT, "Tooltip height is at 100px as expected");
+}
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -109,50 +109,59 @@ function (anchorRect, viewportRect, heig
  *
  * @param {DOMRect} anchorRect
  *        Bounding rectangle for the anchor, relative to the tooltip document.
  * @param {DOMRect} viewportRect
  *        Bounding rectangle for the viewport. top/left can be different from 0 if some
  *        space should not be used by tooltips (for instance OS toolbars, taskbars etc.).
  * @param {Number} width
  *        Preferred width for the tooltip.
+ * @param {String} type
+ *        The tooltip type (e.g. "arrow").
+ * @param {Number} offset
+ *        Horizontal offset in pixels.
+ * @param {Boolean} isRtl
+ *        If the anchor is in RTL, the tooltip should be aligned to the right.
  * @return {Object}
  *         - {Number} left: the left offset for the tooltip.
  *         - {Number} width: the width to use for the tooltip container.
  *         - {Number} arrowLeft: the left offset to use for the arrow element.
  */
 const calculateHorizontalPosition =
-function (anchorRect, viewportRect, width, type, offset) {
-  let {left: anchorLeft, width: anchorWidth} = anchorRect;
+function (anchorRect, viewportRect, width, type, offset, isRtl) {
+  let anchorWidth = anchorRect.width;
+  let anchorStart = isRtl ? anchorRect.right : anchorRect.left;
 
   // Translate to the available viewport space before calculating dimensions and position.
-  anchorLeft -= viewportRect.left;
+  anchorStart -= viewportRect.left;
 
   // Calculate WIDTH.
   width = Math.min(width, viewportRect.width);
 
   // Calculate LEFT.
   // By default the tooltip is aligned with the anchor left edge. Unless this
   // makes it overflow the viewport, in which case is shifts to the left.
-  let left = Math.min(anchorLeft + offset, viewportRect.width - width);
+  let left = anchorStart + offset - (isRtl ? width : 0);
+  left = Math.min(left, viewportRect.width - width);
+  left = Math.max(0, left);
 
   // Calculate ARROW LEFT (tooltip's LEFT might be updated)
   let arrowLeft;
   // Arrow style tooltips may need to be shifted to the left
   if (type === TYPE.ARROW) {
     let arrowCenter = left + ARROW_OFFSET + ARROW_WIDTH / 2;
-    let anchorCenter = anchorLeft + anchorWidth / 2;
+    let anchorCenter = anchorStart + anchorWidth / 2;
     // If the anchor is too narrow, align the arrow and the anchor center.
     if (arrowCenter > anchorCenter) {
       left = Math.max(0, left - (arrowCenter - anchorCenter));
     }
     // Arrow's left offset relative to the anchor.
     arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0;
     // Translate the coordinate to tooltip container
-    arrowLeft += anchorLeft - left;
+    arrowLeft += anchorStart - left;
     // Make sure the arrow remains in the tooltip container.
     arrowLeft = Math.min(arrowLeft, width - ARROW_WIDTH);
     arrowLeft = Math.max(arrowLeft, 0);
   }
 
   // Translate back to absolute coordinates by re-including viewport left margin.
   left += viewportRect.left;
 
@@ -342,18 +351,20 @@ HTMLTooltip.prototype = {
     let preferredWidth;
     if (this.preferredWidth === "auto") {
       preferredWidth = this._measureContainerWidth();
     } else {
       let themeWidth = 2 * EXTRA_BORDER[this.type];
       preferredWidth = this.preferredWidth + themeWidth;
     }
 
-    let {left, width, arrowLeft} =
-      calculateHorizontalPosition(anchorRect, viewportRect, preferredWidth, this.type, x);
+    let anchorWin = anchor.ownerDocument.defaultView;
+    let isRtl = anchorWin.getComputedStyle(anchor).direction === "rtl";
+    let {left, width, arrowLeft} = calculateHorizontalPosition(
+      anchorRect, viewportRect, preferredWidth, this.type, x, isRtl);
 
     this.container.style.width = width + "px";
 
     if (this.type === TYPE.ARROW) {
       this.arrow.style.left = arrowLeft + "px";
     }
 
     if (this.useXulWrapper) {
@@ -486,17 +497,18 @@ HTMLTooltip.prototype = {
   },
 
   _onClick: function (e) {
     if (this._isInTooltipContainer(e.target)) {
       return;
     }
 
     this.hide();
-    if (this.consumeOutsideClicks) {
+    if (this.consumeOutsideClicks && e.button === 0) {
+      // Consume only left click events (button === 0).
       e.preventDefault();
       e.stopPropagation();
     }
   },
 
   _isInTooltipContainer: function (node) {
     // Check if the target is the tooltip arrow.
     if (this.arrow && this.arrow === node) {
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -67,16 +67,17 @@ support-files =
 [browser_styleeditor_fetch-from-cache.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_highlight-selector.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
 [browser_styleeditor_init.js]
 [browser_styleeditor_inline_friendly_names.js]
 [browser_styleeditor_loading.js]
+[browser_styleeditor_loading_with_containers.js]
 [browser_styleeditor_media_sidebar.js]
 [browser_styleeditor_media_sidebar_links.js]
 skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s
 [browser_styleeditor_media_sidebar_sourcemaps.js]
 [browser_styleeditor_missing_stylesheet.js]
 [browser_styleeditor_navigate.js]
 [browser_styleeditor_new.js]
 [browser_styleeditor_nostyle.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the stylesheets can be loaded correctly with containers
+// (bug 1282660).
+
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
+const EXPECTED_SHEETS = [
+  {
+    sheetIndex: 0,
+    name: /^simple.css$/,
+    rules: 1,
+    active: true
+  }, {
+    sheetIndex: 1,
+    name: /^<.*>$/,
+    rules: 3,
+    active: false
+  }
+];
+
+add_task(function* () {
+  // Using the personal container.
+  let userContextId = 1;
+  let { tab } = yield* openTabInUserContext(TESTCASE_URI, userContextId);
+  let { ui } = yield openStyleEditor(tab);
+
+  is(ui.editors.length, 2, "The UI contains two style sheets.");
+  checkSheet(ui.editors[0], EXPECTED_SHEETS[0]);
+  checkSheet(ui.editors[1], EXPECTED_SHEETS[1]);
+});
+
+function* openTabInUserContext(uri, userContextId) {
+  // Open the tab in the correct userContextId.
+  let tab = gBrowser.addTab(uri, {userContextId});
+
+  // Select tab and make sure its browser is focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerDocument.defaultView.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return {tab, browser};
+}
+
+function checkSheet(editor, expected) {
+  is(editor.styleSheet.styleSheetIndex, expected.sheetIndex,
+    "Style sheet has correct index.");
+
+  let summary = editor.summary;
+  let name = summary.querySelector(".stylesheet-name > label")
+                    .getAttribute("value");
+  ok(expected.name.test(name), "The name '" + name + "' is correct.");
+
+  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+  is(parseInt(ruleCount, 10), expected.rules, "the rule count is correct");
+
+  is(summary.classList.contains("splitview-active"), expected.active,
+    "The active status for this sheet is correct.");
+}
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -63,25 +63,23 @@
 .devtools-autocomplete-listbox {
   -moz-appearance: none !important;
   background-color: transparent;
   border-width: 0px !important;
   margin: 0;
   padding: 2px;
 }
 
-.devtools-autocomplete-listbox > scrollbox {
-  padding: 2px;
-}
-
 .devtools-autocomplete-listbox .autocomplete-item {
   width: 100%;
   background-color: transparent;
   border-radius: 4px;
   padding: 1px 0;
+  /* Force text-align even in RTL locales to ensure a correct display of the popup */
+  text-align: left;
 }
 
 .devtools-autocomplete-listbox .autocomplete-selected {
   background-color: rgba(0,0,0,0.2);
 }
 
 .devtools-autocomplete-listbox.dark-theme .autocomplete-selected,
 .devtools-autocomplete-listbox.dark-theme .autocomplete-item:hover {
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -14,16 +14,17 @@ const { EnvironmentActor } = require("de
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
 const { DebuggerServer } = require("devtools/server/main");
 const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const promise = require("promise");
+const PromiseDebugging = require("PromiseDebugging");
 const xpcInspector = require("xpcInspector");
 const ScriptStore = require("./utils/ScriptStore");
 const { DevToolsWorker } = require("devtools/shared/worker/worker");
 const object = require("sdk/util/object");
 const { threadSpec } = require("devtools/shared/specs/script");
 
 const { defer, resolve, reject, all } = promise;
 
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -447,20 +447,29 @@ var StyleSheetActor = protocol.ActorClas
       let content = this.ownerNode.textContent;
       this.text = content;
       return promise.resolve(content);
     }
 
     let options = {
       loadFromCache: true,
       policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET,
-      window: this.window,
       charset: this._getCSSCharset()
     };
 
+    // Bug 1282660 - We use the system principal to load the default internal
+    // stylesheets instead of the content principal since such stylesheets
+    // require system principal to load. At meanwhile, we strip the loadGroup
+    // for preventing the assertion of the userContextId mismatching.
+    // The default internal stylesheets load from the 'resource:' URL.
+    if (!/^resource:\/\//.test(this.href)) {
+      options.window = this.window;
+      options.principal = this.document.nodePrincipal;
+    }
+
     return fetch(this.href, options).then(({ content }) => {
       this.text = content;
       return content;
     });
   },
 
   /**
    * Protocol method to get the original source (actors) for this
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -312,16 +312,22 @@ function WorkerDebuggerLoader(options) {
 }
 
 this.WorkerDebuggerLoader = WorkerDebuggerLoader;
 
 // The following APIs rely on the use of Components, and the worker debugger
 // does not provide alternative definitions for them. Consequently, they are
 // stubbed out both on the main thread and worker threads.
 
+var PromiseDebugging = {
+  getState: function () {
+    throw new Error("PromiseDebugging is not available in workers!");
+  }
+};
+
 var chrome = {
   CC: undefined,
   Cc: undefined,
   ChromeWorker: undefined,
   Cm: undefined,
   Ci: undefined,
   Cu: undefined,
   Cr: undefined,
@@ -485,16 +491,17 @@ this.worker = new WorkerDebuggerLoader({
     "reportError": reportError,
     "rpc": rpc,
     "setImmediate": setImmediate,
     "URL": URL,
   },
   loadSubScript: loadSubScript,
   modules: {
     "Debugger": Debugger,
+    "PromiseDebugging": PromiseDebugging,
     "Services": Object.create(null),
     "chrome": chrome,
     "xpcInspector": xpcInspector
   },
   paths: {
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "": "resource://gre/modules/commonjs/",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -14231,17 +14231,20 @@ nsDocShell::SetOriginAttributes(const Do
 NS_IMETHODIMP
 nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes)
 {
   if (!aOriginAttributes.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   AutoJSAPI jsapi;
-  jsapi.Init(&aOriginAttributes.toObject());
+  if (!jsapi.Init(&aOriginAttributes.toObject())) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
   JSContext* cx = jsapi.cx();
   if (NS_WARN_IF(!cx)) {
     return NS_ERROR_FAILURE;
   }
 
   DocShellOriginAttributes attrs;
   if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
--- a/docshell/test/browser/browser_timelineMarkers-frame-04.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js
@@ -45,21 +45,17 @@ if (Services.prefs.getBoolPref("javascri
       markers = markers.filter(m => (m.name == "Javascript" &&
                                      m.causeName == "promise callback"));
       ok(markers.length > 0, "Found a Javascript marker");
 
       let frame = markers[0].stack;
       ok(frame.asyncParent !== null, "Parent frame has async parent");
       is(frame.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
-      let asyncFrame = frame.asyncParent;
-      // Skip over self-hosted parts of our Promise implementation.
-      while (asyncFrame.source === 'self-hosted')
-        asyncFrame = asyncFrame.parent;
-      is(asyncFrame.functionDisplayName, "do_promise",
+      is(frame.asyncParent.functionDisplayName, "do_promise",
          "Async parent has correct function name");
     }
   }, {
     desc: "Async stack trace on Javascript marker with script",
     searchFor: (markers) => {
       return markers.some(m => (m.name == "Javascript" &&
                                 m.causeName == "promise callback"));
     },
@@ -70,19 +66,15 @@ if (Services.prefs.getBoolPref("javascri
       markers = markers.filter(m => (m.name == "Javascript" &&
                                      m.causeName == "promise callback"));
       ok(markers.length > 0, "Found a Javascript marker");
 
       let frame = markers[0].stack;
       ok(frame.asyncParent !== null, "Parent frame has async parent");
       is(frame.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
-      let asyncFrame = frame.asyncParent;
-      // Skip over self-hosted parts of our Promise implementation.
-      while (asyncFrame.source === 'self-hosted')
-        asyncFrame = asyncFrame.parent;
-      is(asyncFrame.functionDisplayName, "do_promise_script",
+      is(frame.asyncParent.functionDisplayName, "do_promise_script",
          "Async parent has correct function name");
     }
   });
 }
 
 timelineContentTest(TESTS);
--- a/docshell/test/browser/browser_timelineMarkers-frame-05.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js
@@ -86,32 +86,20 @@ if (Services.prefs.getBoolPref("javascri
     searchFor: "ConsoleTime",
     setup: function(docShell) {
       let resolver = makePromise();
       resolvePromise(resolver);
     },
     check: function(markers) {
       markers = markers.filter(m => m.name == "ConsoleTime");
       ok(markers.length > 0, "Promise marker includes stack");
-      ok(markers[0].stack.functionDisplayName == "testConsoleTime",
-         "testConsoleTime is on the stack");
+
       let frame = markers[0].endStack;
-      ok(frame.functionDisplayName == "testConsoleTimeEnd",
-         "testConsoleTimeEnd is on the stack");
-
-      frame = frame.parent;
-      ok(frame.functionDisplayName == "makePromise/<",
-         "makePromise/< is on the stack");
-      let asyncFrame = frame.asyncParent;
-      ok(asyncFrame !== null, "Frame has async parent");
-      is(asyncFrame.asyncCause, "promise callback",
+      ok(frame.parent.asyncParent !== null, "Parent frame has async parent");
+      is(frame.parent.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
-      // Skip over self-hosted parts of our Promise implementation.
-      while (asyncFrame.source === 'self-hosted') {
-        asyncFrame = asyncFrame.parent;
-      }
-      is(asyncFrame.functionDisplayName, "makePromise",
+      is(frame.parent.asyncParent.functionDisplayName, "makePromise",
          "Async parent has correct function name");
     }
   });
 }
 
 timelineContentTest(TESTS);
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -220,39 +220,39 @@ public:
   // This uses the SafeJSContext (or worker equivalent), and enters the
   // compartment of aGlobalObject.
   // If aGlobalObject or its associated JS global are null then it returns
   // false and use of cx() will cause an assertion.
   //
   // If aGlobalObject represents a web-visible global, errors reported by this
   // AutoJSAPI as it comes off the stack will fire the relevant error events and
   // show up in the corresponding web console.
-  bool Init(nsIGlobalObject* aGlobalObject);
+  MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
 
   // This is a helper that grabs the native global associated with aObject and
   // invokes the above Init() with that.
-  bool Init(JSObject* aObject);
+  MOZ_MUST_USE bool Init(JSObject* aObject);
 
   // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
   // If aGlobalObject or its associated JS global are null then it returns
   // false and use of cx() will cause an assertion.
   // If aCx is null it will cause an assertion.
   //
   // If aGlobalObject represents a web-visible global, errors reported by this
   // AutoJSAPI as it comes off the stack will fire the relevant error events and
   // show up in the corresponding web console.
-  bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
+  MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
 
   // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*,
   // when it is more easily available than an nsIGlobalObject.
-  bool Init(nsPIDOMWindowInner* aWindow);
-  bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
+  MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
+  MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
 
-  bool Init(nsGlobalWindow* aWindow);
-  bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
+  MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow);
+  MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
 
   JSContext* cx() const {
     MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
     MOZ_ASSERT(IsStackTop());
     return mCx;
   }
 
 #ifdef DEBUG
@@ -268,25 +268,25 @@ public:
   };
 
   // Transfers ownership of the current exception from the JS engine to the
   // caller. Callers must ensure that HasException() is true, and that cx()
   // is in a non-null compartment.
   //
   // Note that this fails if and only if we OOM while wrapping the exception
   // into the current compartment.
-  bool StealException(JS::MutableHandle<JS::Value> aVal);
+  MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
 
   // Peek the current exception from the JS engine, without stealing it.
   // Callers must ensure that HasException() is true, and that cx() is in a
   // non-null compartment.
   //
   // Note that this fails if and only if we OOM while wrapping the exception
   // into the current compartment.
-  bool PeekException(JS::MutableHandle<JS::Value> aVal);
+  MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
 
   void ClearException() {
     MOZ_ASSERT(IsStackTop());
     JS_ClearPendingException(cx());
   }
 
 protected:
   // Protected constructor for subclasses.  This constructor initialises the
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -322,17 +322,19 @@ nsDOMClassInfo::GetNative(nsIXPConnectWr
 {
   return wrapper ? wrapper->Native() : static_cast<nsISupports*>(js::GetObjectPrivate(obj));
 }
 
 nsresult
 nsDOMClassInfo::DefineStaticJSVals()
 {
   AutoJSAPI jsapi;
-  jsapi.Init(xpc::UnprivilegedJunkScope());
+  if (!jsapi.Init(xpc::UnprivilegedJunkScope())) {
+    return NS_ERROR_UNEXPECTED;
+  }
   JSContext* cx = jsapi.cx();
 
 #define SET_JSID_TO_STRING(_id, _cx, _str)                              \
   if (JSString *str = ::JS_AtomizeAndPinString(_cx, _str))                             \
       _id = INTERNED_STRING_TO_JSID(_cx, str);                                \
   else                                                                        \
       return NS_ERROR_OUT_OF_MEMORY;
 
--- a/dom/base/nsHostObjectProtocolHandler.cpp
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -2,18 +2,21 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsHostObjectProtocolHandler.h"
 
 #include "DOMMediaStream.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/dom/MediaSource.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/ModuleUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsClassHashtable.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsHostObjectURI.h"
@@ -33,19 +36,89 @@ struct DataInfo
   // mObject is expected to be an BlobImpl, DOMMediaStream, or MediaSource
   nsCOMPtr<nsISupports> mObject;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCString mStack;
 };
 
 static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable;
 
+static DataInfo*
+GetDataInfo(const nsACString& aUri)
+{
+  if (!gDataTable) {
+    return nullptr;
+  }
+
+  DataInfo* res;
+
+  // Let's remove any fragment and query from this URI.
+  int32_t hasFragmentPos = aUri.FindChar('#');
+  int32_t hasQueryPos = aUri.FindChar('?');
+
+  int32_t pos = -1;
+  if (hasFragmentPos >= 0 && hasQueryPos >= 0) {
+    pos = std::min(hasFragmentPos, hasQueryPos);
+  } else if (hasFragmentPos >= 0) {
+    pos = hasFragmentPos;
+  } else {
+    pos = hasQueryPos;
+  }
+
+  if (pos < 0) {
+    gDataTable->Get(aUri, &res);
+  } else {
+    gDataTable->Get(StringHead(aUri, pos), &res);
+  }
+
+  return res;
+}
+
 // Memory reporting for the hash table.
 namespace mozilla {
 
+void
+BroadcastBlobURLRegistration(const nsACString& aURI,
+                             BlobImpl* aBlobImpl,
+                             nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aBlobImpl);
+
+  if (XRE_IsParentProcess()) {
+    ContentParent::BroadcastBlobURLRegistration(aURI, aBlobImpl,
+                                                aPrincipal);
+    return;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  BlobChild* actor = cc->GetOrCreateActorForBlobImpl(aBlobImpl);
+  if (NS_WARN_IF(!actor)) {
+    return;
+  }
+
+  NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration(nsCString(aURI), actor,
+                                                           IPC::Principal(aPrincipal)));
+}
+
+void
+BroadcastBlobURLUnregistration(const nsACString& aURI, DataInfo* aInfo)
+{
+  MOZ_ASSERT(aInfo);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (XRE_IsParentProcess()) {
+    ContentParent::BroadcastBlobURLUnregistration(aURI);
+    return;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  NS_WARN_IF(!cc->SendUnstoreAndBroadcastBlobURLUnregistration(nsCString(aURI)));
+}
+
 class HostObjectURLsReporter final : public nsIMemoryReporter
 {
   ~HostObjectURLsReporter() {}
 
  public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
@@ -316,57 +389,134 @@ nsHostObjectProtocolHandler::nsHostObjec
 
 nsresult
 nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aScheme,
                                           nsISupports* aObject,
                                           nsIPrincipal* aPrincipal,
                                           nsACString& aUri)
 {
 #ifdef DEBUG
-  nsCOMPtr<BlobImpl> blobImpl(do_QueryInterface(aObject));
-  nsCOMPtr<MediaSource> mediaSource(do_QueryInterface(aObject));
-  nsCOMPtr<DOMMediaStream> mediaStream(do_QueryInterface(aObject));
+  {
+    nsCOMPtr<BlobImpl> blobImpl(do_QueryInterface(aObject));
+    nsCOMPtr<MediaSource> mediaSource(do_QueryInterface(aObject));
+    nsCOMPtr<DOMMediaStream> mediaStream(do_QueryInterface(aObject));
 
-  // We support only these types.
-  MOZ_ASSERT(blobImpl || mediaSource || mediaStream);
+    // We support only these types.
+    MOZ_ASSERT(blobImpl || mediaSource || mediaStream);
+  }
 #endif
 
   Init();
 
   nsresult rv = GenerateURIString(aScheme, aPrincipal, aUri);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = AddDataEntry(aUri, aObject, aPrincipal);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(aObject);
+  if (blobImpl) {
+    BroadcastBlobURLRegistration(aUri, blobImpl, aPrincipal);
+  }
+
+  return NS_OK;
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aURI,
+                                          nsISupports* aObject,
+                                          nsIPrincipal* aPrincipal)
+{
   if (!gDataTable) {
     gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>;
   }
 
   DataInfo* info = new DataInfo;
 
   info->mObject = aObject;
   info->mPrincipal = aPrincipal;
   mozilla::BlobURLsReporter::GetJSStackForBlob(info);
 
-  gDataTable->Put(aUri, info);
+  gDataTable->Put(aURI, info);
   return NS_OK;
 }
 
+/* static */ bool
+nsHostObjectProtocolHandler::GetAllBlobURLEntries(nsTArray<BlobURLRegistrationData>& aRegistrations,
+                                                  ContentParent* aCP)
+{
+  MOZ_ASSERT(aCP);
+
+  if (!gDataTable) {
+    return true;
+  }
+
+  for (auto iter = gDataTable->ConstIter(); !iter.Done(); iter.Next()) {
+    DataInfo* info = iter.UserData();
+    MOZ_ASSERT(info);
+
+    nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(info->mObject);
+    if (!blobImpl) {
+      continue;
+    }
+
+    PBlobParent* blobParent = aCP->GetOrCreateActorForBlobImpl(blobImpl);
+    if (!blobParent) {
+      return false;
+    }
+
+    aRegistrations.AppendElement(
+      BlobURLRegistrationData(nsCString(iter.Key()), blobParent, nullptr,
+                              IPC::Principal(info->mPrincipal)));
+  }
+
+  return true;
+}
+
 void
-nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri)
+nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri,
+                                             bool aBroadcastToOtherProcesses)
 {
   if (!gDataTable) {
     return;
   }
 
+  DataInfo* info = GetDataInfo(aUri);
+  if (!info) {
+    return;
+  }
+
+  if (aBroadcastToOtherProcesses) {
+    nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(info->mObject);
+    if (blobImpl) {
+      BroadcastBlobURLUnregistration(aUri, info);
+    }
+  }
+
   gDataTable->Remove(aUri);
   if (gDataTable->Count() == 0) {
     delete gDataTable;
     gDataTable = nullptr;
   }
 }
 
+void
+nsHostObjectProtocolHandler::RemoveDataEntries()
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+
+  if (!gDataTable) {
+    return;
+  }
+
+  gDataTable->Clear();
+  delete gDataTable;
+  gDataTable = nullptr;
+}
+
 nsresult
 nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme,
                                                nsIPrincipal* aPrincipal,
                                                nsACString& aUri)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
@@ -393,47 +543,16 @@ nsHostObjectProtocolHandler::GenerateURI
     aUri.Append('/');
   }
 
   aUri += Substring(chars + 1, chars + NSID_LENGTH - 2);
 
   return NS_OK;
 }
 
-static DataInfo*
-GetDataInfo(const nsACString& aUri)
-{
-  if (!gDataTable) {
-    return nullptr;
-  }
-
-  DataInfo* res;
-
-  // Let's remove any fragment and query from this URI.
-  int32_t hasFragmentPos = aUri.FindChar('#');
-  int32_t hasQueryPos = aUri.FindChar('?');
-
-  int32_t pos = -1;
-  if (hasFragmentPos >= 0 && hasQueryPos >= 0) {
-    pos = std::min(hasFragmentPos, hasQueryPos);
-  } else if (hasFragmentPos >= 0) {
-    pos = hasFragmentPos;
-  } else {
-    pos = hasQueryPos;
-  }
-
-  if (pos < 0) {
-    gDataTable->Get(aUri, &res);
-  } else {
-    gDataTable->Get(StringHead(aUri, pos), &res);
-  }
-
-  return res;
-}
-
 nsIPrincipal*
 nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri)
 {
   if (!gDataTable) {
     return nullptr;
   }
 
   DataInfo* res = GetDataInfo(aUri);
@@ -504,18 +623,23 @@ nsHostObjectProtocolHandler::NewURI(cons
                                     nsIURI *aBaseURI,
                                     nsIURI **aResult)
 {
   *aResult = nullptr;
   nsresult rv;
 
   DataInfo* info = GetDataInfo(aSpec);
 
-  RefPtr<nsHostObjectURI> uri =
-    new nsHostObjectURI(info ? info->mPrincipal.get() : nullptr);
+  RefPtr<nsHostObjectURI> uri;
+  if (info) {
+    nsCOMPtr<BlobImpl> blob = do_QueryInterface(info->mObject);
+    uri = new nsHostObjectURI(info->mPrincipal, blob);
+  } else {
+    uri = new nsHostObjectURI(nullptr, nullptr);
+  }
 
   rv = uri->SetSpec(aSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_TryToSetImmutable(uri);
   uri.forget(aResult);
 
   return NS_OK;
@@ -523,67 +647,71 @@ nsHostObjectProtocolHandler::NewURI(cons
 
 NS_IMETHODIMP
 nsHostObjectProtocolHandler::NewChannel2(nsIURI* uri,
                                          nsILoadInfo* aLoadInfo,
                                          nsIChannel** result)
 {
   *result = nullptr;
 
+  nsCOMPtr<nsIURIWithBlobImpl> uriBlobImpl = do_QueryInterface(uri);
+  if (!uriBlobImpl) {
+    return NS_ERROR_DOM_BAD_URI;
+  }
+
+  nsCOMPtr<nsISupports> tmp;
+  MOZ_ALWAYS_SUCCEEDS(uriBlobImpl->GetBlobImpl(getter_AddRefs(tmp)));
+  nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(tmp);
+  if (!blobImpl) {
+    return NS_ERROR_DOM_BAD_URI;
+  }
+
+#ifdef DEBUG
   nsCString spec;
   uri->GetSpec(spec);
 
   DataInfo* info = GetDataInfo(spec);
 
-  if (!info) {
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-  nsCOMPtr<BlobImpl> blob = do_QueryInterface(info->mObject);
-  if (!blob) {
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-#ifdef DEBUG
-  {
+  // Info can be null, in case this blob URL has been revoked already.
+  if (info) {
     nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(uri);
     nsCOMPtr<nsIPrincipal> principal;
     uriPrinc->GetPrincipal(getter_AddRefs(principal));
     NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!");
   }
 #endif
 
   ErrorResult rv;
   nsCOMPtr<nsIInputStream> stream;
-  blob->GetInternalStream(getter_AddRefs(stream), rv);
+  blobImpl->GetInternalStream(getter_AddRefs(stream), rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   nsAutoString contentType;
-  blob->GetType(contentType);
+  blobImpl->GetType(contentType);
 
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
                                         uri,
                                         stream,
                                         NS_ConvertUTF16toUTF8(contentType),
                                         EmptyCString(), // aContentCharset
                                         aLoadInfo);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
-  if (blob->IsFile()) {
+  if (blobImpl->IsFile()) {
     nsString filename;
-    blob->GetName(filename);
+    blobImpl->GetName(filename);
     channel->SetContentDispositionFilename(filename);
   }
 
-  uint64_t size = blob->GetSize(rv);
+  uint64_t size = blobImpl->GetSize(rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   channel->SetOriginalURI(uri);
   channel->SetContentType(NS_ConvertUTF16toUTF8(contentType));
   channel->SetContentLength(size);
 
@@ -790,8 +918,9 @@ static const mozilla::Module::ContractID
 
 static const mozilla::Module kHostObjectProtocolHandlerModule = {
   mozilla::Module::kVersion,
   kHostObjectProtocolHandlerCIDs,
   kHostObjectProtocolHandlerContracts
 };
 
 NSMODULE_DEFN(HostObjectProtocolHandler) = &kHostObjectProtocolHandlerModule;
+
--- a/dom/base/nsHostObjectProtocolHandler.h
+++ b/dom/base/nsHostObjectProtocolHandler.h
@@ -7,29 +7,32 @@
 #ifndef nsHostObjectProtocolHandler_h
 #define nsHostObjectProtocolHandler_h
 
 #include "mozilla/Attributes.h"
 #include "nsIProtocolHandler.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
+#include "nsTArray.h"
 
 #define BLOBURI_SCHEME "blob"
 #define MEDIASTREAMURI_SCHEME "mediastream"
 #define MEDIASOURCEURI_SCHEME "mediasource"
 #define FONTTABLEURI_SCHEME "moz-fonttable"
 #define RTSPURI_SCHEME "rtsp"
 
 class nsIPrincipal;
 
 namespace mozilla {
 class DOMMediaStream;
 namespace dom {
 class BlobImpl;
+class BlobURLRegistrationData;
+class ContentParent;
 class MediaSource;
 } // namespace dom
 } // namespace mozilla
 
 class nsHostObjectProtocolHandler : public nsIProtocolHandler
 {
 public:
   nsHostObjectProtocolHandler();
@@ -50,20 +53,33 @@ public:
                                     nsACString &aUri);
 
   // Methods for managing uri->object mapping
   // AddDataEntry creates the URI with the given scheme and returns it in aUri
   static nsresult AddDataEntry(const nsACString& aScheme,
                                nsISupports* aObject,
                                nsIPrincipal* aPrincipal,
                                nsACString& aUri);
-  static void RemoveDataEntry(const nsACString& aUri);
+  static void RemoveDataEntry(const nsACString& aUri,
+                              bool aBroadcastToOTherProcesses = true);
+
+  // This is for IPC only.
+  static void RemoveDataEntries();
+
   static nsIPrincipal* GetDataEntryPrincipal(const nsACString& aUri);
   static void Traverse(const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback);
 
+  // IPC or internal use only
+  static nsresult AddDataEntry(const nsACString& aURI,
+                               nsISupports* aObject,
+                               nsIPrincipal* aPrincipal);
+  static bool
+  GetAllBlobURLEntries(nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations,
+                       mozilla::dom::ContentParent* aCP);
+
 protected:
   virtual ~nsHostObjectProtocolHandler() {}
 
 private:
   static void Init(void);
 };
 
 class nsBlobProtocolHandler : public nsHostObjectProtocolHandler
--- a/dom/base/nsHostObjectURI.cpp
+++ b/dom/base/nsHostObjectURI.cpp
@@ -16,29 +16,40 @@ static NS_DEFINE_CID(kHOSTOBJECTURICID, 
 
 static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
                      NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
 
 NS_IMPL_ADDREF_INHERITED(nsHostObjectURI, mozilla::net::nsSimpleURI)
 NS_IMPL_RELEASE_INHERITED(nsHostObjectURI, mozilla::net::nsSimpleURI)
 
 NS_INTERFACE_MAP_BEGIN(nsHostObjectURI)
+  NS_INTERFACE_MAP_ENTRY(nsIURIWithBlobImpl)
   NS_INTERFACE_MAP_ENTRY(nsIURIWithPrincipal)
   if (aIID.Equals(kHOSTOBJECTURICID))
     foundInterface = static_cast<nsIURI*>(this);
   else if (aIID.Equals(kThisSimpleURIImplementationCID)) {
     // Need to return explicitly here, because if we just set foundInterface
     // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into
     // nsSimplURI::QueryInterface and finding something for this CID.
     *aInstancePtr = nullptr;
     return NS_NOINTERFACE;
   }
   else
 NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI)
 
+// nsIURIWithBlobImpl methods:
+
+NS_IMETHODIMP
+nsHostObjectURI::GetBlobImpl(nsISupports** aBlobImpl)
+{
+  RefPtr<BlobImpl> blobImpl(mBlobImpl);
+  blobImpl.forget(aBlobImpl);
+  return NS_OK;
+}
+
 // nsIURIWithPrincipal methods:
 
 NS_IMETHODIMP
 nsHostObjectURI::GetPrincipal(nsIPrincipal** aPrincipal)
 {
   NS_IF_ADDREF(*aPrincipal = mPrincipal);
 
   return NS_OK;
@@ -121,16 +132,20 @@ nsHostObjectURI::Deserialize(const mozil
       return false;
   }
 
   const HostObjectURIParams& hostParams = aParams.get_HostObjectURIParams();
 
   if (!mozilla::net::nsSimpleURI::Deserialize(hostParams.simpleParams())) {
     return false;
   }
+
+  // XXXbaku: when we will have shared blobURL maps, we can populate mBlobImpl
+  // here asll well.
+
   if (hostParams.principal().type() == OptionalPrincipalInfo::Tvoid_t) {
     return true;
   }
 
   mPrincipal = PrincipalInfoToPrincipal(hostParams.principal().get_PrincipalInfo());
   return mPrincipal != nullptr;
 }
 
@@ -157,16 +172,17 @@ nsHostObjectURI::CloneInternal(mozilla::
   RefPtr<nsHostObjectURI> uriCheck;
   rv = simpleClone->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(uriCheck));
   MOZ_ASSERT(NS_SUCCEEDED(rv) && uriCheck);
 #endif
 
   nsHostObjectURI* u = static_cast<nsHostObjectURI*>(simpleClone.get());
 
   u->mPrincipal = mPrincipal;
+  u->mBlobImpl = mBlobImpl;
 
   simpleClone.forget(aClone);
   return NS_OK;
 }
 
 /* virtual */ nsresult
 nsHostObjectURI::EqualsInternal(nsIURI* aOther,
                                 mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode,
@@ -185,17 +201,20 @@ nsHostObjectURI::EqualsInternal(nsIURI* 
   }
 
   // Compare the member data that our base class knows about.
   if (!mozilla::net::nsSimpleURI::EqualsInternal(otherUri, aRefHandlingMode)) {
     *aResult = false;
     return NS_OK;
   }
 
-  // Compare the piece of additional member data that we add to base class.
+  // Compare the piece of additional member data that we add to base class,
+  // but we cannot compare BlobImpl. This should not be a problem, because we
+  // don't support changing the underlying mBlobImpl.
+
   if (mPrincipal && otherUri->mPrincipal) {
     // Both of us have mPrincipals. Compare them.
     return mPrincipal->Equals(otherUri->mPrincipal, aResult);
   }
   // else, at least one of us lacks a principal; only equal if *both* lack it.
   *aResult = (!mPrincipal && !otherUri->mPrincipal);
   return NS_OK;
 }
--- a/dom/base/nsHostObjectURI.h
+++ b/dom/base/nsHostObjectURI.h
@@ -3,41 +3,48 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsHostObjectURI_h
 #define nsHostObjectURI_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/File.h"
 #include "nsCOMPtr.h"
 #include "nsIClassInfo.h"
 #include "nsIPrincipal.h"
 #include "nsISerializable.h"
+#include "nsIURIWithBlobImpl.h"
 #include "nsIURIWithPrincipal.h"
 #include "nsSimpleURI.h"
 #include "nsIIPCSerializableURI.h"
 
 /**
  * These URIs refer to host objects: Blobs, with scheme "blob",
  * MediaStreams, with scheme "mediastream", and MediaSources, with scheme
  * "mediasource".
  */
-class nsHostObjectURI : public mozilla::net::nsSimpleURI,
-                        public nsIURIWithPrincipal
+class nsHostObjectURI : public mozilla::net::nsSimpleURI
+                      , public nsIURIWithPrincipal
+                      , public nsIURIWithBlobImpl
 {
 public:
-  explicit nsHostObjectURI(nsIPrincipal* aPrincipal) :
-      mozilla::net::nsSimpleURI(), mPrincipal(aPrincipal)
+  nsHostObjectURI(nsIPrincipal* aPrincipal,
+                  mozilla::dom::BlobImpl* aBlobImpl)
+    : mozilla::net::nsSimpleURI()
+    , mPrincipal(aPrincipal)
+    , mBlobImpl(aBlobImpl)
   {}
 
   // For use only from deserialization
   nsHostObjectURI() : mozilla::net::nsSimpleURI() {}
 
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIURIWITHBLOBIMPL
   NS_DECL_NSIURIWITHPRINCIPAL
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
   NS_DECL_NSIIPCSERIALIZABLEURI
 
   NS_IMETHOD SetScheme(const nsACString &aProtocol) override;
 
   // Override CloneInternal() and EqualsInternal()
@@ -47,16 +54,17 @@ public:
                                   RefHandlingEnum aRefHandlingMode,
                                   bool* aResult) override;
 
   // Override StartClone to hand back a nsHostObjectURI
   virtual mozilla::net::nsSimpleURI* StartClone(RefHandlingEnum /* unused */) override
   { return new nsHostObjectURI(); }
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
+  RefPtr<mozilla::dom::BlobImpl> mBlobImpl;
 
 protected:
   virtual ~nsHostObjectURI() {}
 };
 
 #define NS_HOSTOBJECTURI_CID \
 { 0xf5475c51, 0x59a7, 0x4757, \
   { 0xb3, 0xd9, 0xe2, 0x11, 0xa9, 0x41, 0x08, 0x72 } }
--- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -20,114 +20,105 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(exn.name, name,
        "Should have the right exception name in test " + testNumber);
     is("filename" in exn ? exn.filename : exn.fileName, filename,
        "Should have the right file name in test " + testNumber);
     is(exn.message, message,
        "Should have the right message in test " + testNumber);
     is(exn.code, code, "Should have the right .code in test " + testNumber);
     if (message === "") {
-      is(exn.name, "InternalError",
+      is(exn.name, "NS_ERROR_UNEXPECTED",
          "Should have one of our synthetic exceptions in test " + testNumber);
     }
     is(exn.stack, stack, "Should have the right stack in test " + testNumber);
   }
 
   function ensurePromiseFail(testNumber, value) {
     ok(false, "Test " + testNumber + " should not have a fulfilled promise");
   }
 
   function doTest() {
     var t = new TestInterfaceJS();
     /* Async parent frames from pushPrefEnv don't show up in e10s.  */
     var isE10S = !SpecialPowers.isMainProcess();
     var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
     var ourFile = location.href;
-    var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper.";
-    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:130:3
+    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:121:3
 ` : "";
 
     Promise.all([
       t.testPromiseWithThrowingChromePromiseInit().then(
           ensurePromiseFail.bind(null, 1),
-          checkExn.bind(null, 49, "InternalError", unwrapError,
-                        undefined, ourFile, 1,
-                        `doTest@${ourFile}:49:7
+          checkExn.bind(null, 48, "NS_ERROR_UNEXPECTED", "", undefined,
+                        ourFile, 1,
+                        `doTest@${ourFile}:48:7
 ` +
                         parentFrame)),
       t.testPromiseWithThrowingContentPromiseInit(function() {
           thereIsNoSuchContentFunction1();
         }).then(
           ensurePromiseFail.bind(null, 2),
-          checkExn.bind(null, 57, "ReferenceError",
+          checkExn.bind(null, 56, "ReferenceError",
                         "thereIsNoSuchContentFunction1 is not defined",
                         undefined, ourFile, 2,
-                        `doTest/<@${ourFile}:57:11
-doTest@${ourFile}:56:7
+                        `doTest/<@${ourFile}:56:11
+doTest@${ourFile}:55:7
 ` +
                         parentFrame)),
       t.testPromiseWithThrowingChromeThenFunction().then(
           ensurePromiseFail.bind(null, 3),
-          checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 3, asyncStack ? (`Async*doTest@${ourFile}:67:7
-` +
-                        parentFrame) : "")),
+          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
       t.testPromiseWithThrowingContentThenFunction(function() {
           thereIsNoSuchContentFunction2();
         }).then(
           ensurePromiseFail.bind(null, 4),
-          checkExn.bind(null, 73, "ReferenceError",
+          checkExn.bind(null, 70, "ReferenceError",
                         "thereIsNoSuchContentFunction2 is not defined",
                         undefined, ourFile, 4,
-                        `doTest/<@${ourFile}:73:11
+                        `doTest/<@${ourFile}:70:11
 ` +
-                        (asyncStack ? `Async*doTest@${ourFile}:72:7
+                        (asyncStack ? `Async*doTest@${ourFile}:69:7
 ` : "") +
                         parentFrame)),
       t.testPromiseWithThrowingChromeThenable().then(
           ensurePromiseFail.bind(null, 5),
-          checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 5, asyncStack ? (`Async*doTest@${ourFile}:84:7
-` +
-                        parentFrame) : "")),
+          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
       t.testPromiseWithThrowingContentThenable({
             then: function() { thereIsNoSuchContentFunction3(); }
         }).then(
           ensurePromiseFail.bind(null, 6),
-          checkExn.bind(null, 90, "ReferenceError",
+          checkExn.bind(null, 85, "ReferenceError",
                         "thereIsNoSuchContentFunction3 is not defined",
                         undefined, ourFile, 6,
-                        `doTest/<.then@${ourFile}:90:32
-` + (asyncStack ? `Async*doTest@${ourFile}:89:7\n` + parentFrame : ""))),
+                        `doTest/<.then@${ourFile}:85:32
+`)),
       t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
           ensurePromiseFail.bind(null, 7),
-          checkExn.bind(null, 98, "NotFoundError",
+          checkExn.bind(null, 93, "NotFoundError",
                         "We are a second DOMException",
                         DOMException.NOT_FOUND_ERR, ourFile, 7,
-                        `doTest@${ourFile}:98:7
+                        `doTest@${ourFile}:93:7
 ` +
                         parentFrame)),
       t.testPromiseWithDOMExceptionThrowingThenFunction().then(
           ensurePromiseFail.bind(null, 8),
-          checkExn.bind(null, asyncStack ? 106 : 0, "NetworkError",
+          checkExn.bind(null, asyncStack ? 101 : 0, "NetworkError",
                          "We are a third DOMException",
                         DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
-                        (asyncStack ? `Async*doTest@${ourFile}:106:7
+                        (asyncStack ? `Async*doTest@${ourFile}:101:7
 ` +
                          parentFrame : ""))),
       t.testPromiseWithDOMExceptionThrowingThenable().then(
           ensurePromiseFail.bind(null, 9),
-          checkExn.bind(null, asyncStack ? 114 : 0, "TypeMismatchError",
+          checkExn.bind(null, 0, "TypeMismatchError",
                         "We are a fourth DOMException",
-                        DOMException.TYPE_MISMATCH_ERR,
-                        asyncStack ? ourFile : "", 9,
-                        (asyncStack ? `Async*doTest@${ourFile}:114:7
-` +
-                         parentFrame : ""))),
+                         DOMException.TYPE_MISMATCH_ERR, "", 9, "")),
     ]).then(SimpleTest.finish,
-            function(err) {
-              ok(false, "One of our catch statements totally failed with err" + err + ', stack: ' + (err ? err.stack : ''));
+            function() {
+              ok(false, "One of our catch statements totally failed");
               SimpleTest.finish();
             });
   }
 
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             doTest);
   </script>
 </head>
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -186,64 +186,103 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     //////
 
     const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
                                                      : gl::OriginPos::BottomLeft);
     const auto dstOrigin = gl::OriginPos::BottomLeft;
     const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha;
 
     const auto pi = dstDUI->ToPacking();
-    const auto dstFormat = FormatForPackingInfo(pi);
 
     const auto dstBPP = webgl::BytesPerPixel(pi);
     const auto dstWidthBytes = CheckedUint32(dstBPP) * mWidth;
     const auto dstRowLengthBytes = CheckedUint32(dstBPP) * mRowLength;
 
     const auto dstAlignment = mAlignment;
     const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
 
     //////
 
     const auto dstTotalRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight;
+    const auto dstUsedSizeExceptLastRow = (dstTotalRows - 1) * dstStride;
 
-    const auto dstSize = skipBytes + (dstTotalRows - 1) * dstStride + dstWidthBytes;
+    const auto dstSize = skipBytes + dstUsedSizeExceptLastRow + dstWidthBytes;
     if (!dstSize.isValid()) {
         webgl->ErrorOutOfMemory("%s: Invalid dstSize calculation during conversion.",
                                 funcName);
         return false;
     }
 
     //////
 
-    bool needsConvert = (srcOrigin != dstOrigin ||
-                         srcFormat != dstFormat ||
-                         srcStride != dstStride.value());
+    const auto dstFormat = FormatForPackingInfo(pi);
 
-    if (UnpackFormatHasAlpha(dstDUI->unpackFormat)) {
-        needsConvert |= (mIsSrcPremult != isDstPremult);
+    bool premultMatches = (mIsSrcPremult == isDstPremult);
+    if (!UnpackFormatHasAlpha(dstDUI->unpackFormat)) {
+        premultMatches = true;
     }
 
-    if (!needsConvert)
-        return true;
+    const bool needsPixelConversion = (srcFormat != dstFormat || !premultMatches);
+    const bool originsMatch = (srcOrigin == dstOrigin);
+
+    MOZ_ASSERT_IF(!needsPixelConversion, srcBPP == dstBPP);
 
-    ////////////
-    // Ugh, ok, fine!
-
-    webgl->GenerateWarning("%s: Incurred CPU data conversion, which is slow.",
-                           funcName);
+    if (!needsPixelConversion &&
+        originsMatch &&
+        srcStride == dstStride.value())
+    {
+        // No conversion needed!
+        return true;
+    }
 
     //////
+    // We need some sort of conversion, so create the dest buffer.
 
     *out_anchoredBuffer = calloc(1, dstSize.value());
-    if (!out_anchoredBuffer->get()) {
+    *out_bytes = out_anchoredBuffer->get();
+    if (!*out_bytes) {
         webgl->ErrorOutOfMemory("%s: Unable to allocate buffer during conversion.",
                                 funcName);
         return false;
     }
-    const auto dstBegin = (uint8_t*)out_anchoredBuffer->get() + skipBytes;
+    const auto dstBegin = (uint8_t*)(*out_bytes) + skipBytes;
+
+    //////
+    // Row conversion
+
+    if (!needsPixelConversion) {
+        webgl->GenerateWarning("%s: Incurred CPU row conversion, which is slow.",
+                               funcName);
+
+        const uint8_t* srcRow = srcBegin;
+        uint8_t* dstRow = dstBegin;
+        const auto widthBytes = dstWidthBytes.value();
+        ptrdiff_t dstCopyStride = dstStride.value();
+
+        if (!originsMatch) {
+            dstRow += dstUsedSizeExceptLastRow.value();
+            dstCopyStride = -dstCopyStride;
+        }
+
+        for (uint32_t i = 0; i < dstTotalRows.value(); i++) {
+            memcpy(dstRow, srcRow, widthBytes);
+            srcRow += srcStride;
+            dstRow += dstCopyStride;
+        }
+        return true;
+    }
+
+    ////////////
+    // Pixel conversion.
+
+    MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
+    MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
+
+    webgl->GenerateWarning("%s: Incurred CPU pixel conversion, which is very slow.",
+                           funcName);
 
     //////
 
     // And go!:
     bool wasTrivial;
     if (!ConvertImage(mWidth, dstTotalRows.value(),
                       srcBegin, srcStride, srcOrigin, srcFormat, mIsSrcPremult,
                       dstBegin, dstStride.value(), dstOrigin, dstFormat, isDstPremult,
@@ -254,17 +293,16 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     }
 
     if (!wasTrivial) {
         webgl->GenerateWarning("%s: Chosen format/type incurred an expensive reformat:"
                                " 0x%04x/0x%04x",
                                funcName, dstDUI->unpackFormat, dstDUI->unpackType);
     }
 
-    *out_bytes = out_anchoredBuffer->get();
     return true;
 }
 
 static GLenum
 DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
                 const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
                 GLsizei width, GLsizei height, GLsizei depth, const void* data)
 {
@@ -292,25 +330,26 @@ bool
 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
     const auto pi = dui->ToPacking();
-    const auto format = FormatForPackingInfo(pi);
 
     const auto bytesPerPixel = webgl::BytesPerPixel(pi);
     const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
     const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
     if (!rowStride.isValid()) {
         MOZ_CRASH("Should be checked earlier.");
     }
 
+    const auto format = FormatForPackingInfo(pi);
+
     const void* uploadBytes;
     UniqueBuffer tempBuffer;
     if (!ConvertIfNeeded(webgl, funcName, mBytes, rowStride.value(), bytesPerPixel,
                          format, dui, &uploadBytes, &tempBuffer))
     {
         return false;
     }
 
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -50,17 +50,26 @@ private:
     template<typename BufferT>
     void GetBufferSubDataT(GLenum target, GLintptr offset, const BufferT& data);
 
 public:
     void GetBufferSubData(GLenum target, GLintptr offset,
                           const dom::Nullable<dom::ArrayBuffer>& maybeData);
     void GetBufferSubData(GLenum target, GLintptr offset,
                           const dom::SharedArrayBuffer& data);
+    void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
+                    GLenum type, WebGLsizeiptr offset, ErrorResult& out_error);
 
+    void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+                    GLenum format, GLenum type,
+                    const dom::Nullable<dom::ArrayBufferView>& pixels,
+                    ErrorResult& out_error)
+    {
+        WebGLContext::ReadPixels(x, y, width, height, format, type, pixels, out_error);
+    }
 
     // -------------------------------------------------------------------------
     // Framebuffer objects - WebGL2ContextFramebuffers.cpp
 
     void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                          GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                          GLbitfield mask, GLenum filter);
     void FramebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture* texture, GLint level, GLint layer);
@@ -137,121 +146,78 @@ public:
     GLint GetFragDataLocation(WebGLProgram* program, const nsAString& name);
 
 
     // -------------------------------------------------------------------------
     // Uniforms and attributes - WebGL2ContextUniforms.cpp
     void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset);
 
     // GL 3.0 & ES 3.0
-    void Uniform1ui(WebGLUniformLocation* location, GLuint v0);
-    void Uniform2ui(WebGLUniformLocation* location, GLuint v0, GLuint v1);
-    void Uniform3ui(WebGLUniformLocation* location, GLuint v0, GLuint v1, GLuint v2);
-    void Uniform4ui(WebGLUniformLocation* location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
+    void Uniform1ui(WebGLUniformLocation* loc, GLuint v0);
+    void Uniform2ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1);
+    void Uniform3ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2);
+    void Uniform4ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2,
+                    GLuint v3);
+
+    ////////////////
 
-private:
-    void Uniform1uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
-    void Uniform2uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
-    void Uniform3uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
-    void Uniform4uiv_base(WebGLUniformLocation* loc, size_t arrayLength, const GLuint* data);
+protected:
+    typedef Arr<GLuint, dom::Uint32Array> UintArr;
+
+    void UniformNuiv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                     const UintArr& arr);
+
+    //////
 
 public:
-    void Uniform1uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform1uiv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform2uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform2uiv_base(loc, arr.Length(), arr.Elements());
+    template<typename T>
+    void Uniform1uiv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNuiv("uniform1uiv", 1, loc, UintArr(arr));
     }
-    void Uniform3uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform3uiv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform4uiv(WebGLUniformLocation* loc, const dom::Sequence<GLuint>& arr) {
-        Uniform4uiv_base(loc, arr.Length(), arr.Elements());
+    template<typename T>
+    void Uniform2uiv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNuiv("uniform2uiv", 2, loc, UintArr(arr));
     }
-    void Uniform1uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform1uiv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform2uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform2uiv_base(loc, arr.Length(), arr.Data());
+    template<typename T>
+    void Uniform3uiv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNuiv("uniform3uiv", 3, loc, UintArr(arr));
     }
-    void Uniform3uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform3uiv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform4uiv(WebGLUniformLocation* loc, const dom::Uint32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform4uiv_base(loc, arr.Length(), arr.Data());
+    template<typename T>
+    void Uniform4uiv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNuiv("uniform4uiv", 4, loc, UintArr(arr));
     }
 
-private:
-    void UniformMatrix2x3fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
-    void UniformMatrix3x2fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
-    void UniformMatrix2x4fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
-    void UniformMatrix4x2fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
-    void UniformMatrix3x4fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
-    void UniformMatrix4x3fv_base(WebGLUniformLocation* loc, bool transpose,
-                                 size_t arrayLength, const GLfloat* data);
+    //////
 
-public:
-    // GL 2.1 & ES 3.0
-    void UniformMatrix2x3fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix2x3fv_base(loc, transpose, value.Length(), value.Elements());
+    template<typename T>
+    void UniformMatrix2x3fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix2x3fv", 2, 3, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix2x4fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix2x4fv_base(loc, transpose, value.Length(), value.Elements());
+    template<typename T>
+    void UniformMatrix2x4fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix2x4fv", 2, 4, loc, transpose, FloatArr(arr));
+    }
+    template<typename T>
+    void UniformMatrix3x2fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix3x2fv", 3, 2, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix3x2fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix3x2fv_base(loc, transpose, value.Length(), value.Elements());
-    }
-    void UniformMatrix3x4fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix3x4fv_base(loc, transpose, value.Length(), value.Elements());
+    template<typename T>
+    void UniformMatrix3x4fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix3x4fv", 3, 4, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix4x2fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix4x2fv_base(loc, transpose, value.Length(), value.Elements());
+    template<typename T>
+    void UniformMatrix4x2fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix4x2fv", 4, 2, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix4x3fv(WebGLUniformLocation* loc, bool transpose, const dom::Sequence<GLfloat>& value){
-        UniformMatrix4x3fv_base(loc, transpose, value.Length(), value.Elements());
+    template<typename T>
+    void UniformMatrix4x3fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix4x3fv", 4, 3, loc, transpose, FloatArr(arr));
     }
 
-    void UniformMatrix2x3fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix2x3fv_base(loc, transpose, value.Length(), value.Data());
-    }
-
-    void UniformMatrix2x4fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix2x4fv_base(loc, transpose, value.Length(), value.Data());
-    }
-
-    void UniformMatrix3x2fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix3x2fv_base(loc, transpose, value.Length(), value.Data());
-    }
-
-    void UniformMatrix3x4fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix3x4fv_base(loc, transpose, value.Length(), value.Data());
-    }
-
-    void UniformMatrix4x2fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix4x2fv_base(loc, transpose, value.Length(), value.Data());
-    }
-
-    void UniformMatrix4x3fv(WebGLUniformLocation* loc, bool transpose, const dom::Float32Array& value){
-        value.ComputeLengthAndData();
-        UniformMatrix4x3fv_base(loc, transpose, value.Length(), value.Data());
-    }
+    ////////////////
 
 private:
     void VertexAttribI4iv(GLuint index, size_t length, const GLint* v);
     void VertexAttribI4uiv(GLuint index, size_t length, const GLuint* v);
 
 public:
     // GL 3.0 & ES 3.0
     void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w);
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -7,36 +7,36 @@
 
 #include "GLContext.h"
 #include "WebGLBuffer.h"
 #include "WebGLTransformFeedback.h"
 
 namespace mozilla {
 
 bool
-WebGL2Context::ValidateBufferTarget(GLenum target, const char* info)
+WebGL2Context::ValidateBufferTarget(GLenum target, const char* funcName)
 {
     switch (target) {
     case LOCAL_GL_ARRAY_BUFFER:
     case LOCAL_GL_COPY_READ_BUFFER:
     case LOCAL_GL_COPY_WRITE_BUFFER:
     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+    case LOCAL_GL_PIXEL_PACK_BUFFER:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
     case LOCAL_GL_UNIFORM_BUFFER:
         return true;
 
-    case LOCAL_GL_PIXEL_PACK_BUFFER:
     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
         ErrorInvalidOperation("%s: PBOs are still under development, and are currently"
                               " disabled.",
-                              info);
+                              funcName);
         return false;
 
     default:
-        ErrorInvalidEnumInfo(info, target);
+        ErrorInvalidEnumInfo(funcName, target);
         return false;
     }
 }
 
 bool
 WebGL2Context::ValidateBufferIndexedTarget(GLenum target, const char* info)
 {
     switch (target) {
@@ -75,66 +75,68 @@ WebGL2Context::ValidateBufferUsageEnum(G
 // -------------------------------------------------------------------------
 // Buffer objects
 
 void
 WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
                                  GLintptr readOffset, GLintptr writeOffset,
                                  GLsizeiptr size)
 {
+    const char funcName[] = "copyBufferSubData";
     if (IsContextLost())
         return;
 
-    if (!ValidateBufferTarget(readTarget, "copyBufferSubData") ||
-        !ValidateBufferTarget(writeTarget, "copyBufferSubData"))
+    if (!ValidateBufferTarget(readTarget, funcName) ||
+        !ValidateBufferTarget(writeTarget, funcName))
     {
         return;
     }
 
     const WebGLRefPtr<WebGLBuffer>& readBufferSlot = GetBufferSlotByTarget(readTarget);
     const WebGLRefPtr<WebGLBuffer>& writeBufferSlot = GetBufferSlotByTarget(writeTarget);
     if (!readBufferSlot || !writeBufferSlot)
         return;
 
     const WebGLBuffer* readBuffer = readBufferSlot.get();
-    if (!readBuffer)
-        return ErrorInvalidOperation("copyBufferSubData: No buffer bound to readTarget");
-
-    WebGLBuffer* writeBuffer = writeBufferSlot.get();
-    if (!writeBuffer)
-        return ErrorInvalidOperation("copyBufferSubData: No buffer bound to writeTarget");
-
-    if (!ValidateDataOffsetSize(readOffset, size, readBuffer->ByteLength(),
-        "copyBufferSubData"))
-    {
+    if (!readBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound to readTarget.", funcName);
         return;
     }
 
-    if (!ValidateDataOffsetSize(writeOffset, size, writeBuffer->ByteLength(),
-        "copyBufferSubData"))
-    {
+    WebGLBuffer* writeBuffer = writeBufferSlot.get();
+    if (!writeBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound to writeTarget.", funcName);
         return;
     }
 
+    if (!ValidateDataOffsetSize(readOffset, size, readBuffer->ByteLength(), funcName))
+        return;
+
+    if (!ValidateDataOffsetSize(writeOffset, size, writeBuffer->ByteLength(), funcName))
+        return;
+
     if (readTarget == writeTarget &&
-        !ValidateDataRanges(readOffset, writeOffset, size, "copyBufferSubData"))
+        !ValidateDataRanges(readOffset, writeOffset, size, funcName))
     {
         return;
     }
 
     WebGLBuffer::Kind readType = readBuffer->Content();
     WebGLBuffer::Kind writeType = writeBuffer->Content();
 
     if (readType != WebGLBuffer::Kind::Undefined &&
         writeType != WebGLBuffer::Kind::Undefined &&
         writeType != readType)
     {
-        ErrorInvalidOperation("copyBufferSubData: Can't copy %s data to %s data",
-                              (readType == WebGLBuffer::Kind::OtherData) ? "other" : "element",
-                              (writeType == WebGLBuffer::Kind::OtherData) ? "other" : "element");
+        ErrorInvalidOperation("%s: Can't copy %s data to %s data.",
+                              funcName,
+                              (readType == WebGLBuffer::Kind::OtherData) ? "other"
+                                                                         : "element",
+                              (writeType == WebGLBuffer::Kind::OtherData) ? "other"
+                                                                          : "element");
         return;
     }
 
     WebGLContextUnchecked::CopyBufferSubData(readTarget, writeTarget, readOffset,
                                              writeOffset, size);
 
     if (writeType == WebGLBuffer::Kind::Undefined) {
         writeBuffer->BindTo(
@@ -145,64 +147,71 @@ WebGL2Context::CopyBufferSubData(GLenum 
 
 // BufferT may be one of
 // const dom::ArrayBuffer&
 // const dom::SharedArrayBuffer&
 template<typename BufferT>
 void
 WebGL2Context::GetBufferSubDataT(GLenum target, GLintptr offset, const BufferT& data)
 {
+    const char funcName[] = "getBufferSubData";
     if (IsContextLost())
         return;
 
     // For the WebGLBuffer bound to the passed target, read
     // returnedData.byteLength bytes from the buffer starting at byte
     // offset offset and write them to returnedData.
 
     // If zero is bound to target, an INVALID_OPERATION error is
     // generated.
-    if (!ValidateBufferTarget(target, "getBufferSubData"))
+    if (!ValidateBufferTarget(target, funcName))
         return;
 
     // If offset is less than zero, an INVALID_VALUE error is
     // generated.
-    if (offset < 0)
-        return ErrorInvalidValue("getBufferSubData: negative offset");
+    if (offset < 0) {
+        ErrorInvalidValue("%s: Offset must be non-negative.", funcName);
+        return;
+    }
 
     WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
     WebGLBuffer* boundBuffer = bufferSlot.get();
-    if (!boundBuffer)
-        return ErrorInvalidOperation("getBufferSubData: no buffer bound");
+    if (!boundBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound.", funcName);
+        return;
+    }
 
     // If offset + returnedData.byteLength would extend beyond the end
     // of the buffer an INVALID_VALUE error is generated.
     data.ComputeLengthAndData();
 
     CheckedInt<WebGLsizeiptr> neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + data.LengthAllowShared();
     if (!neededByteLength.isValid()) {
-        ErrorInvalidValue("getBufferSubData: Integer overflow computing the needed"
-                          " byte length.");
+        ErrorInvalidValue("%s: Integer overflow computing the needed byte length.",
+                          funcName);
         return;
     }
 
     if (neededByteLength.value() > boundBuffer->ByteLength()) {
-        ErrorInvalidValue("getBufferSubData: Not enough data. Operation requires"
-                          " %d bytes, but buffer only has %d bytes.",
-                          neededByteLength.value(), boundBuffer->ByteLength());
+        ErrorInvalidValue("%s: Not enough data. Operation requires %d bytes, but buffer"
+                          " only has %d bytes.",
+                          funcName, neededByteLength.value(), boundBuffer->ByteLength());
         return;
     }
 
     // If target is TRANSFORM_FEEDBACK_BUFFER, and any transform
     // feedback object is currently active, an INVALID_OPERATION error
     // is generated.
     WebGLTransformFeedback* currentTF = mBoundTransformFeedback;
     if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
-        if (currentTF->mIsActive)
-            return ErrorInvalidOperation("getBufferSubData: Currently bound transform"
-                                         " feedback is active");
+        if (currentTF->mIsActive) {
+            ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
+                                  funcName);
+            return;
+        }
 
         // https://github.com/NVIDIA/WebGL/commit/63aff5e58c1d79825a596f0f4aa46174b9a5f72c
         // Performing reads and writes on a buffer that is currently
         // bound for transform feedback causes undefined results in
         // GLES3.0 and OpenGL 4.5. In practice results of reads and
         // writes might be consistent as long as transform feedback
         // objects are not active, but neither GLES3.0 nor OpenGL 4.5
         // spec guarantees this - just being bound for transform
@@ -213,37 +222,44 @@ WebGL2Context::GetBufferSubDataT(GLenum 
 
     /* If the buffer is written and read sequentially by other
      * operations and getBufferSubData, it is the responsibility of
      * the WebGL API to ensure that data are access
      * consistently. This applies even if the buffer is currently
      * bound to a transform feedback binding point.
      */
 
-    void* ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(), LOCAL_GL_MAP_READ_BIT);
+    void* ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(),
+                                    LOCAL_GL_MAP_READ_BIT);
     // Warning: Possibly shared memory.  See bug 1225033.
     memcpy(data.DataAllowShared(), ptr, data.LengthAllowShared());
     gl->fUnmapBuffer(target);
 
+    ////
+
     if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
         BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, currentTF);
     }
 }
 
-void WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
-                                     const dom::Nullable<dom::ArrayBuffer>& maybeData)
+void
+WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
+                                const dom::Nullable<dom::ArrayBuffer>& maybeData)
 {
     // If returnedData is null then an INVALID_VALUE error is
     // generated.
-    if (maybeData.IsNull())
-        return ErrorInvalidValue("getBufferSubData: returnedData is null");
+    if (maybeData.IsNull()) {
+        ErrorInvalidValue("getBufferSubData: returnedData is null");
+        return;
+    }
 
     const dom::ArrayBuffer& data = maybeData.Value();
     GetBufferSubDataT(target, offset, data);
 }
 
-void WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
-                                     const dom::SharedArrayBuffer& data)
+void
+WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
+                                const dom::SharedArrayBuffer& data)
 {
     GetBufferSubDataT(target, offset, data);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -6,16 +6,17 @@
 #include "WebGL2Context.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "mozilla/RefPtr.h"
 #include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLProgram.h"
+#include "WebGLUniformLocation.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 namespace mozilla {
 
 bool
 WebGL2Context::ValidateUniformMatrixTranspose(bool /*transpose*/, const char* /*info*/)
 {
@@ -23,230 +24,52 @@ WebGL2Context::ValidateUniformMatrixTran
 }
 
 // -------------------------------------------------------------------------
 // Uniforms
 
 void
 WebGL2Context::Uniform1ui(WebGLUniformLocation* loc, GLuint v0)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_UNSIGNED_INT, "uniform1ui", &rawLoc))
+    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_UNSIGNED_INT, "uniform1ui"))
         return;
 
     MakeContextCurrent();
-    gl->fUniform1ui(rawLoc, v0);
+    gl->fUniform1ui(loc->mLoc, v0);
 }
 
 void
 WebGL2Context::Uniform2ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_UNSIGNED_INT, "uniform2ui", &rawLoc))
+    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_UNSIGNED_INT, "uniform2ui"))
         return;
 
     MakeContextCurrent();
-    gl->fUniform2ui(rawLoc, v0, v1);
+    gl->fUniform2ui(loc->mLoc, v0, v1);
 }
 
 void
 WebGL2Context::Uniform3ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_UNSIGNED_INT, "uniform3ui", &rawLoc))
-        return;
-
-    MakeContextCurrent();
-    gl->fUniform3ui(rawLoc, v0, v1, v2);
-}
-
-void
-WebGL2Context::Uniform4ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2, GLuint v3)
-{
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_UNSIGNED_INT, "uniform4ui", &rawLoc))
+    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_UNSIGNED_INT, "uniform3ui"))
         return;
 
     MakeContextCurrent();
-    gl->fUniform4ui(rawLoc, v0, v1, v2, v3);
-}
-
-void
-WebGL2Context::Uniform1uiv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                                const GLuint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformArraySetter(loc, 1, LOCAL_GL_UNSIGNED_INT, arrayLength,
-                                    "uniform1uiv", &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform1uiv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGL2Context::Uniform2uiv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                                const GLuint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformArraySetter(loc, 2, LOCAL_GL_UNSIGNED_INT, arrayLength,
-                                    "uniform2uiv", &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform2uiv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGL2Context::Uniform3uiv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                                const GLuint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformArraySetter(loc, 3, LOCAL_GL_UNSIGNED_INT, arrayLength,
-                                    "uniform3uiv", &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform1uiv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGL2Context::Uniform4uiv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                                const GLuint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformArraySetter(loc, 4, LOCAL_GL_UNSIGNED_INT, arrayLength,
-                                    "uniform4uiv", &rawLoc, &numElementsToUpload)) {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform4uiv(rawLoc, numElementsToUpload, data);
+    gl->fUniform3ui(loc->mLoc, v0, v1, v2);
 }
 
 void
-WebGL2Context::UniformMatrix2x3fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 2, 3, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix2x3fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix2x3fv(rawLoc, numElementsToUpload, transpose, data);
-}
-
-void
-WebGL2Context::UniformMatrix2x4fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
+WebGL2Context::Uniform4ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2,
+                          GLuint v3)
 {
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 2, 4, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix2x4fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
+    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_UNSIGNED_INT, "uniform4ui"))
         return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix2x4fv(rawLoc, numElementsToUpload, transpose, data);
-}
-
-void
-WebGL2Context::UniformMatrix3x2fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 3, 2, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix3x2fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
 
     MakeContextCurrent();
-    gl->fUniformMatrix3x2fv(rawLoc, numElementsToUpload, transpose, data);
-}
-
-void
-WebGL2Context::UniformMatrix3x4fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 3, 4, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix3x4fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix3x4fv(rawLoc, numElementsToUpload, transpose, data);
-}
-
-void
-WebGL2Context::UniformMatrix4x2fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 4, 2, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix4x2fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix4x2fv(rawLoc, numElementsToUpload, transpose, data);
-}
-
-void
-WebGL2Context::UniformMatrix4x3fv_base(WebGLUniformLocation* loc, bool transpose,
-                                       size_t arrayLength, const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-
-    if (!ValidateUniformMatrixArraySetter(loc, 4, 3, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix4x3fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix4x3fv(rawLoc, numElementsToUpload, transpose, data);
+    gl->fUniform4ui(loc->mLoc, v0, v1, v2, v3);
 }
 
 
 // -------------------------------------------------------------------------
 // Uniform Buffer Objects and Transform Feedback Buffers
 // TODO(djg): Implemented in WebGLContext
 /*
     void BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer);
--- a/dom/canvas/WebGLActiveInfo.cpp
+++ b/dom/canvas/WebGLActiveInfo.cpp
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLActiveInfo.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 
 namespace mozilla {
 
-uint8_t
+static uint8_t
 ElemSizeFromType(GLenum elemType)
 {
     switch (elemType) {
     case LOCAL_GL_BOOL:
     case LOCAL_GL_FLOAT:
     case LOCAL_GL_INT:
     case LOCAL_GL_UNSIGNED_INT:
     case LOCAL_GL_SAMPLER_2D:
@@ -71,28 +71,56 @@ ElemSizeFromType(GLenum elemType)
     case LOCAL_GL_FLOAT_MAT4:
         return 16;
 
     default:
         MOZ_CRASH("GFX: Bad `elemType`.");
     }
 }
 
+////////////////////
+
 WebGLActiveInfo::WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType,
                                  bool isArray, const nsACString& baseUserName,
                                  const nsACString& baseMappedName)
     : mWebGL(webgl)
     , mElemCount(elemCount)
     , mElemType(elemType)
     , mBaseUserName(baseUserName)
     , mIsArray(isArray)
     , mElemSize(ElemSizeFromType(elemType))
     , mBaseMappedName(baseMappedName)
 { }
 
+bool
+WebGLActiveInfo::IsSampler() const
+{
+    switch (mElemType) {
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_3D:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+        return true;
+
+    default:
+        return false;
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 JSObject*
 WebGLActiveInfo::WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLActiveInfoBinding::Wrap(js, this, givenProto);
 }
 
--- a/dom/canvas/WebGLActiveInfo.h
+++ b/dom/canvas/WebGLActiveInfo.h
@@ -25,29 +25,30 @@ public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLActiveInfo)
 
     virtual JSObject* WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto) override;
 
     WebGLContext* GetParentObject() const {
         return mWebGL;
     }
 
-
     WebGLContext* const mWebGL;
 
     // ActiveInfo state:
-    const GLint mElemCount; // `size`
+    const uint32_t mElemCount; // `size`
     const GLenum mElemType; // `type`
     const nsCString mBaseUserName; // `name`, but ASCII, and without any final "[0]".
 
     // Not actually part of ActiveInfo:
     const bool mIsArray;
     const uint8_t mElemSize;
     const nsCString mBaseMappedName; // Without any final "[0]".
 
+    bool IsSampler() const;
+
     WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType, bool isArray,
                     const nsACString& baseUserName, const nsACString& baseMappedName);
 
     /* GLES 2.0.25, p33:
      *   This command will return as much information about active
      *   attributes as possible. If no information is available, length will
      *   be set to zero and name will be an empty string. This situation
      *   could arise if GetActiveAttrib is issued after a failed link.
@@ -85,13 +86,13 @@ private:
     { }
 
     // Private destructor, to discourage deletion outside of Release():
     ~WebGLActiveInfo() { }
 };
 
 //////////
 
-uint8_t ElemSizeFromType(GLenum elemType);
+bool IsElemTypeSampler(GLenum elemType);
 
 } // namespace mozilla
 
 #endif // WEBGL_ACTIVE_INFO_H_
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -117,16 +117,17 @@ template<typename> struct Nullable;
 namespace gfx {
 class SourceSurface;
 } // namespace gfx
 
 namespace webgl {
 struct LinkedProgramInfo;
 class ShaderValidator;
 class TexUnpackBlob;
+struct UniformInfo;
 } // namespace webgl
 
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
 
 void AssertUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint shadow);
 
 struct WebGLContextOptions
 {
@@ -534,19 +535,23 @@ public:
     bool IsRenderbuffer(WebGLRenderbuffer* rb);
     bool IsShader(WebGLShader* shader);
     bool IsVertexArray(WebGLVertexArray* vao);
     void LineWidth(GLfloat width);
     void LinkProgram(WebGLProgram* prog);
     void PixelStorei(GLenum pname, GLint param);
     void PolygonOffset(GLfloat factor, GLfloat units);
 protected:
-    bool DoReadPixelsAndConvert(GLint x, GLint y, GLsizei width, GLsizei height,
-                                GLenum destFormat, GLenum destType, void* destBytes,
-                                GLenum auxReadFormat, GLenum auxReadType);
+    bool ReadPixels_SharedPrecheck(ErrorResult* const out_error);
+    void ReadPixelsImpl(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
+                        GLenum type, void* data, uint32_t dataLen);
+    bool DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
+                                GLsizei width, GLsizei height, GLenum format,
+                                GLenum destType, void* dest, uint32_t dataLen,
+                                uint32_t rowStride);
 public:
     void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                     GLenum format, GLenum type,
                     const dom::Nullable<dom::ArrayBufferView>& pixels,
                     ErrorResult& rv);
     void RenderbufferStorage(GLenum target, GLenum internalFormat,
                              GLsizei width, GLsizei height);
 protected:
@@ -560,193 +565,138 @@ public:
     void StencilFunc(GLenum func, GLint ref, GLuint mask);
     void StencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
     void StencilMask(GLuint mask);
     void StencilMaskSeparate(GLenum face, GLuint mask);
     void StencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
     void StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail,
                            GLenum dppass);
 
+    //////
+
     void Uniform1i(WebGLUniformLocation* loc, GLint x);
     void Uniform2i(WebGLUniformLocation* loc, GLint x, GLint y);
     void Uniform3i(WebGLUniformLocation* loc, GLint x, GLint y, GLint z);
-    void Uniform4i(WebGLUniformLocation* loc, GLint x, GLint y, GLint z,
-                   GLint w);
+    void Uniform4i(WebGLUniformLocation* loc, GLint x, GLint y, GLint z, GLint w);
 
     void Uniform1f(WebGLUniformLocation* loc, GLfloat x);
     void Uniform2f(WebGLUniformLocation* loc, GLfloat x, GLfloat y);
     void Uniform3f(WebGLUniformLocation* loc, GLfloat x, GLfloat y, GLfloat z);
-    void Uniform4f(WebGLUniformLocation* loc, GLfloat x, GLfloat y, GLfloat z,
-                   GLfloat w);
+    void Uniform4f(WebGLUniformLocation* loc, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+
+    //////////////////////////
+
+protected:
+    template<typename elemT, typename arrT>
+    struct Arr {
+        size_t dataCount;
+        const elemT* data;
 
-    // Int array
-    void Uniform1iv(WebGLUniformLocation* loc, const dom::Int32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform1iv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform1iv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLint>& arr)
-    {
-        Uniform1iv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform1iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLint* data);
+        explicit Arr(const arrT& arr) {
+            arr.ComputeLengthAndData();
+            dataCount = arr.Length();
+            data = arr.Data();
+        }
 
-    void Uniform2iv(WebGLUniformLocation* loc, const dom::Int32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform2iv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform2iv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLint>& arr)
-    {
-        Uniform2iv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform2iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLint* data);
+        explicit Arr(const dom::Sequence<elemT>& arr) {
+            dataCount = arr.Length();
+            data = arr.Elements();
+        }
+    };
 
-    void Uniform3iv(WebGLUniformLocation* loc, const dom::Int32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform3iv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform3iv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLint>& arr)
-    {
-        Uniform3iv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform3iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLint* data);
+    typedef Arr<GLint, dom::Int32Array> IntArr;
+    typedef Arr<GLfloat, dom::Float32Array> FloatArr;
+
+    ////////////////
+
+    void UniformNiv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                    const IntArr& arr);
+
+    void UniformNfv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                    const FloatArr& arr);
 
-    void Uniform4iv(WebGLUniformLocation* loc, const dom::Int32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform4iv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform4iv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLint>& arr)
-    {
-        Uniform4iv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform4iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLint* data);
+    void UniformMatrixAxBfv(const char* funcName, uint8_t A, uint8_t B,
+                            WebGLUniformLocation* loc, bool transpose,
+                            const FloatArr& arr);
 
-    // Float array
-    void Uniform1fv(WebGLUniformLocation* loc, const dom::Float32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform1fv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform1fv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLfloat>& arr)
-    {
-        Uniform1fv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform1fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLfloat* data);
+    ////////////////
 
-    void Uniform2fv(WebGLUniformLocation* loc, const dom::Float32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform2fv_base(loc, arr.Length(), arr.Data());
+public:
+    template<typename T>
+    void Uniform1iv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNiv("uniform1iv", 1, loc, IntArr(arr));
     }
-    void Uniform2fv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLfloat>& arr)
-    {
-        Uniform2fv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform2fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLfloat* data);
-
-    void Uniform3fv(WebGLUniformLocation* loc, const dom::Float32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform3fv_base(loc, arr.Length(), arr.Data());
+    template<typename T>
+    void Uniform2iv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNiv("uniform2iv", 2, loc, IntArr(arr));
     }
-    void Uniform3fv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLfloat>& arr)
-    {
-        Uniform3fv_base(loc, arr.Length(), arr.Elements());
+    template<typename T>
+    void Uniform3iv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNiv("uniform3iv", 3, loc, IntArr(arr));
     }
-    void Uniform3fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLfloat* data);
+    template<typename T>
+    void Uniform4iv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNiv("uniform4iv", 4, loc, IntArr(arr));
+    }
 
-    void Uniform4fv(WebGLUniformLocation* loc, const dom::Float32Array& arr) {
-        arr.ComputeLengthAndData();
-        Uniform4fv_base(loc, arr.Length(), arr.Data());
-    }
-    void Uniform4fv(WebGLUniformLocation* loc,
-                    const dom::Sequence<GLfloat>& arr)
-    {
-        Uniform4fv_base(loc, arr.Length(), arr.Elements());
-    }
-    void Uniform4fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                         const GLfloat* data);
+    //////
 
-    // Matrix
-    void UniformMatrix2fv(WebGLUniformLocation* loc, WebGLboolean transpose,
-                          const dom::Float32Array& value)
-    {
-        value.ComputeLengthAndData();
-        UniformMatrix2fv_base(loc, transpose, value.Length(), value.Data());
+    template<typename T>
+    void Uniform1fv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNfv("uniform1fv", 1, loc, FloatArr(arr));
+    }
+    template<typename T>
+    void Uniform2fv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNfv("uniform2fv", 2, loc, FloatArr(arr));
     }
-    void UniformMatrix2fv(WebGLUniformLocation* loc, WebGLboolean transpose,
-                          const dom::Sequence<float>& value)
-    {
-        UniformMatrix2fv_base(loc, transpose, value.Length(),
-                              value.Elements());
+    template<typename T>
+    void Uniform3fv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNfv("uniform3fv", 3, loc, FloatArr(arr));
     }
-    void UniformMatrix2fv_base(WebGLUniformLocation* loc, bool transpose,
-                               size_t arrayLength, const float* data);
-
-    void UniformMatrix3fv(WebGLUniformLocation* loc, WebGLboolean transpose,
-                          const dom::Float32Array& value)
-    {
-        value.ComputeLengthAndData();
-        UniformMatrix3fv_base(loc, transpose, value.Length(), value.Data());
+    template<typename T>
+    void Uniform4fv(WebGLUniformLocation* loc, const T& arr) {
+        UniformNfv("uniform4fv", 4, loc, FloatArr(arr));
     }
-    void UniformMatrix3fv(WebGLUniformLocation* loc, WebGLboolean transpose,
-                          const dom::Sequence<float>& value)
-    {
-        UniformMatrix3fv_base(loc, transpose, value.Length(), value.Elements());
-    }
-    void UniformMatrix3fv_base(WebGLUniformLocation* loc, bool transpose,
-                               size_t arrayLength, const float* data);
+
+    //////
 
-    void UniformMatrix4fv(WebGLUniformLocation* loc, WebGLboolean transpose,
-                          const dom::Float32Array& value)
-    {
-        value.ComputeLengthAndData();
-        UniformMatrix4fv_base(loc, transpose, value.Length(), value.Data());
+    template<typename T>
+    void UniformMatrix2fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix2fv", 2, 2, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix4fv(WebGLUniformLocation* loc, bool transpose,
-                          const dom::Sequence<float>& value)
-    {
-        UniformMatrix4fv_base(loc, transpose, value.Length(),
-                              value.Elements());
+    template<typename T>
+    void UniformMatrix3fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix3fv", 3, 3, loc, transpose, FloatArr(arr));
     }
-    void UniformMatrix4fv_base(WebGLUniformLocation* loc, bool transpose,
-                               size_t arrayLength, const float* data);
+    template<typename T>
+    void UniformMatrix4fv(WebGLUniformLocation* loc, bool transpose, const T& arr) {
+        UniformMatrixAxBfv("uniformMatrix4fv", 4, 4, loc, transpose, FloatArr(arr));
+    }
+
+    ////////////////////////////////////
 
     void UseProgram(WebGLProgram* prog);
 
     bool ValidateAttribArraySetter(const char* name, uint32_t count,
                                    uint32_t arrayLength);
     bool ValidateUniformLocation(WebGLUniformLocation* loc, const char* funcName);
     bool ValidateUniformSetter(WebGLUniformLocation* loc, uint8_t setterSize,
-                               GLenum setterType, const char* info,
-                               GLuint* out_rawLoc);
+                               GLenum setterType, const char* funcName);
     bool ValidateUniformArraySetter(WebGLUniformLocation* loc,
                                     uint8_t setterElemSize, GLenum setterType,
-                                    size_t setterArraySize, const char* info,
-                                    GLuint* out_rawLoc,
-                                    GLsizei* out_numElementsToUpload);
+                                    uint32_t setterArraySize, const char* funcName,
+                                    uint32_t* out_numElementsToUpload);
     bool ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
                                           uint8_t setterCols,
                                           uint8_t setterRows,
                                           GLenum setterType,
-                                          size_t setterArraySize,
+                                          uint32_t setterArraySize,
                                           bool setterTranspose,
-                                          const char* info,
-                                          GLuint* out_rawLoc,
-                                          GLsizei* out_numElementsToUpload);
+                                          const char* funcName,
+                                          uint32_t* out_numElementsToUpload);
     void ValidateProgram(WebGLProgram* prog);
     bool ValidateUniformLocation(const char* info, WebGLUniformLocation* loc);
     bool ValidateSamplerUniformSetter(const char* info,
                                       WebGLUniformLocation* loc, GLint value);
     void Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
 // -----------------------------------------------------------------------------
 // WEBGL_lose_context
 public:
@@ -1598,24 +1548,28 @@ public:
     void GenerateWarning(const char* fmt, va_list ap);
 
 public:
     UniquePtr<webgl::FormatUsageAuthority> mFormatUsage;
 
     virtual UniquePtr<webgl::FormatUsageAuthority>
     CreateFormatUsage(gl::GLContext* gl) const = 0;
 
+
+    const decltype(mBound2DTextures)* TexListForElemType(GLenum elemType) const;
+
     // Friend list
     friend class ScopedCopyTexImageSource;
     friend class ScopedResolveTexturesForDraw;
     friend class ScopedUnpackReset;
     friend class webgl::TexUnpackBlob;
     friend class webgl::TexUnpackBytes;
     friend class webgl::TexUnpackImage;
     friend class webgl::TexUnpackSurface;
+    friend struct webgl::UniformInfo;
     friend class WebGLTexture;
     friend class WebGLFBAttachPoint;
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLQuery;
     friend class WebGLBuffer;
     friend class WebGLSampler;
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -37,58 +37,115 @@ class ScopedResolveTexturesForDraw
     std::vector<TexRebindRequest> mRebindRequests;
 
 public:
     ScopedResolveTexturesForDraw(WebGLContext* webgl, const char* funcName,
                                  bool* const out_error);
     ~ScopedResolveTexturesForDraw();
 };
 
+bool
+WebGLTexture::IsFeedback(WebGLContext* webgl, const char* funcName, uint32_t texUnit,
+                         const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const
+{
+    auto itr = fbAttachments.cbegin();
+    for (; itr != fbAttachments.cend(); ++itr) {
+        const auto& attach = *itr;
+        if (attach->Texture() == this)
+            break;
+    }
+
+    if (itr == fbAttachments.cend())
+        return false;
+
+    ////
+
+    const auto minLevel = mBaseMipmapLevel;
+    uint32_t maxLevel;
+    if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) {
+        // No valid mips. Will need fake-black.
+        return false;
+    }
+
+    ////
+
+    for (; itr != fbAttachments.cend(); ++itr) {
+        const auto& attach = *itr;
+        if (attach->Texture() != this)
+            continue;
+
+        const auto dstLevel = attach->MipLevel();
+
+        if (minLevel <= dstLevel && dstLevel <= maxLevel) {
+            webgl->ErrorInvalidOperation("%s: Feedback loop detected between tex target"
+                                         " 0x%04x, tex unit %u, levels %u-%u; and"
+                                         " framebuffer attachment 0x%04x, level %u.",
+                                         funcName, mTarget.get(), texUnit, minLevel,
+                                         maxLevel, attach->mAttachmentPoint, dstLevel);
+            return true;
+        }
+    }
+
+    return false;
+}
+
 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
                                                            const char* funcName,
                                                            bool* const out_error)
     : mWebGL(webgl)
 {
-    MOZ_ASSERT(webgl->gl->IsCurrent());
+    MOZ_ASSERT(mWebGL->gl->IsCurrent());
 
-    typedef decltype(WebGLContext::mBound2DTextures) TexturesT;
+    if (!mWebGL->mActiveProgramLinkInfo) {
+        mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
+        *out_error = true;
+        return;
+    }
 
-    const auto fnResolveAll = [this, funcName](const TexturesT& textures)
-    {
-        const auto len = textures.Length();
-        for (uint32_t texUnit = 0; texUnit < len; ++texUnit) {
-            WebGLTexture* tex = textures[texUnit];
+    std::vector<const WebGLFBAttachPoint*> fbAttachments;
+    if (mWebGL->mBoundDrawFramebuffer) {
+        const auto& fb = mWebGL->mBoundDrawFramebuffer;
+        fb->GatherAttachments(&fbAttachments);
+    }
+
+    MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
+    const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
+    for (const auto& uniform : uniformSamplers) {
+        const auto& texList = *(uniform->mSamplerTexList);
+
+        for (const auto& texUnit : uniform->mSamplerValues) {
+            if (texUnit >= texList.Length())
+                continue;
+
+            const auto& tex = texList[texUnit];
             if (!tex)
                 continue;
 
+            if (tex->IsFeedback(mWebGL, funcName, texUnit, fbAttachments)) {
+                *out_error = true;
+                return;
+            }
+
             FakeBlackType fakeBlack;
-            if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack))
-                return false;
+            if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
+                mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
+                                         funcName);
+                *out_error = true;
+                return;
+            }
 
             if (fakeBlack == FakeBlackType::None)
                 continue;
 
             mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack);
             mRebindRequests.push_back({texUnit, tex});
         }
-
-        return true;
-    };
-
-    bool ok = true;
-    ok &= fnResolveAll(mWebGL->mBound2DTextures);
-    ok &= fnResolveAll(mWebGL->mBoundCubeMapTextures);
-    ok &= fnResolveAll(mWebGL->mBound3DTextures);
-    ok &= fnResolveAll(mWebGL->mBound2DArrayTextures);
-
-    if (!ok) {
-        mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.", funcName);
     }
 
-    *out_error = !ok;
+    *out_error = false;
 }
 
 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw()
 {
     if (!mRebindRequests.size())
         return;
 
     gl::GLContext* gl = mWebGL->gl;
@@ -190,22 +247,16 @@ WebGLContext::DrawArrays_check(GLint fir
         return false;
     }
 
     // If count is 0, there's nothing to do.
     if (count == 0 || primcount == 0) {
         return false;
     }
 
-    // Any checks below this depend on a program being available.
-    if (!mCurrentProgram) {
-        ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info);
-        return false;
-    }
-
     if (!ValidateBufferFetching(info)) {
         return false;
     }
 
     CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count;
 
     if (!checked_firstPlusCount.isValid()) {
         ErrorInvalidOperation("%s: overflow in first+count", info);
@@ -355,24 +406,16 @@ WebGLContext::DrawElements_check(GLsizei
     const GLsizei first = byteOffset / bytesPerElem;
     const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(count);
 
     if (!checked_byteCount.isValid()) {
         ErrorInvalidValue("%s: overflow in byteCount", info);
         return false;
     }
 
-    // Any checks below this depend on mActiveProgramLinkInfo being available.
-    if (!mActiveProgramLinkInfo) {
-        // Technically, this will only be null iff CURRENT_PROGRAM is null.
-        // But it's better to branch on what we actually care about.
-        ErrorInvalidOperation("%s: null CURRENT_PROGRAM", info);
-        return false;
-    }
-
     if (!mBoundVertexArray->mElementArrayBuffer) {
         ErrorInvalidOperation("%s: must have element array buffer binding", info);
         return false;
     }
 
     WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
 
     if (!elemArrayBuffer.ByteLength()) {
@@ -598,18 +641,18 @@ WebGLContext::ValidateBufferFetching(con
             return false;
         }
 
         ++i;
     }
 
     mBufferFetch_IsAttrib0Active = false;
 
-    for (const auto& pair : mActiveProgramLinkInfo->activeAttribLocs) {
-        const uint32_t attribLoc = pair.second;
+    for (const auto& attrib : mActiveProgramLinkInfo->attribs) {
+        const auto& attribLoc = attrib.mLoc;
 
         if (attribLoc >= attribCount)
             continue;
 
         if (attribLoc == 0) {
             mBufferFetch_IsAttrib0Active = true;
         }
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLContext.h"
+#include "WebGL2Context.h"
 
 #include "WebGLActiveInfo.h"
 #include "WebGLContextUtils.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
 #include "WebGLShader.h"
 #include "WebGLProgram.h"
 #include "WebGLUniformLocation.h"
@@ -965,20 +966,20 @@ WebGLContext::GetUniform(JSContext* js, 
         return JS::NullValue();
 
     if (!ValidateObject("getUniform: `program`", prog))
         return JS::NullValue();
 
     if (!ValidateObject("getUniform: `location`", loc))
         return JS::NullValue();
 
-    if (!loc->ValidateForProgram(prog, this, "getUniform"))
+    if (!loc->ValidateForProgram(prog, "getUniform"))
         return JS::NullValue();
 
-    return loc->GetUniform(js, this);
+    return loc->GetUniform(js);
 }
 
 already_AddRefed<WebGLUniformLocation>
 WebGLContext::GetUniformLocation(WebGLProgram* prog, const nsAString& name)
 {
     if (IsContextLost())
         return nullptr;
 
@@ -1209,38 +1210,49 @@ WebGLContext::PixelStorei(GLenum pname, 
 
     default:
         break;
     }
 
     ErrorInvalidEnumInfo("pixelStorei: parameter", pname);
 }
 
+static bool
+IsNeedsANGLEWorkAround(const webgl::FormatInfo* format)
+{
+    switch (format->effectiveFormat) {
+    case webgl::EffectiveFormat::RGB16F:
+    case webgl::EffectiveFormat::RGBA16F:
+        return true;
+
+    default:
+        return false;
+    }
+}
+
 bool
-WebGLContext::DoReadPixelsAndConvert(GLint x, GLint y, GLsizei width, GLsizei height,
-                                     GLenum destFormat, GLenum destType, void* destBytes,
-                                     GLenum auxReadFormat, GLenum auxReadType)
+WebGLContext::DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
+                                     GLsizei width, GLsizei height, GLenum format,
+                                     GLenum destType, void* dest, uint32_t destSize,
+                                     uint32_t rowStride)
 {
-    GLenum readFormat = destFormat;
-    GLenum readType = destType;
-
     if (gl->WorkAroundDriverBugs() &&
         gl->IsANGLE() &&
         gl->Version() < 300 && // ANGLE ES2 doesn't support HALF_FLOAT reads properly.
-        readType == LOCAL_GL_FLOAT &&
-        auxReadFormat == destFormat &&
-        auxReadType == LOCAL_GL_HALF_FLOAT)
+        IsNeedsANGLEWorkAround(srcFormat))
     {
         MOZ_RELEASE_ASSERT(!IsWebGL2()); // No SKIP_PIXELS, etc.
-
-        readType = auxReadType;
+        MOZ_ASSERT(!mBoundPixelPackBuffer); // Let's be real clear.
+
+        // You'd think ANGLE would want HALF_FLOAT_OES, but it rejects that.
+        const GLenum readType = LOCAL_GL_HALF_FLOAT;
 
         const char funcName[] = "readPixels";
-        const auto readBytesPerPixel = webgl::BytesPerPixel({readFormat, readType});
-        const auto destBytesPerPixel = webgl::BytesPerPixel({destFormat, destType});
+        const auto readBytesPerPixel = webgl::BytesPerPixel({format, readType});
+        const auto destBytesPerPixel = webgl::BytesPerPixel({format, destType});
 
         uint32_t readStride;
         uint32_t readByteCount;
         uint32_t destStride;
         uint32_t destByteCount;
         if (!ValidatePackSize(funcName, width, height, readBytesPerPixel, &readStride,
                               &readByteCount) ||
             !ValidatePackSize(funcName, width, height, destBytesPerPixel, &destStride,
@@ -1253,34 +1265,34 @@ WebGLContext::DoReadPixelsAndConvert(GLi
         UniqueBuffer readBuffer = malloc(readByteCount);
         if (!readBuffer) {
             ErrorOutOfMemory("readPixels: Failed to alloc temp buffer for conversion.");
             return false;
         }
 
         gl::GLContext::LocalErrorScope errorScope(*gl);
 
-        gl->fReadPixels(x, y, width, height, readFormat, readType, readBuffer.get());
+        gl->fReadPixels(x, y, width, height, format, readType, readBuffer.get());
 
         const GLenum error = errorScope.GetError();
         if (error == LOCAL_GL_OUT_OF_MEMORY) {
             ErrorOutOfMemory("readPixels: Driver ran out of memory.");
             return false;
         }
 
         if (error) {
             MOZ_RELEASE_ASSERT(false, "GFX: Unexpected driver error.");
             return false;
         }
 
         size_t channelsPerRow = std::min(readStride / sizeof(uint16_t),
                                          destStride / sizeof(float));
 
         const uint8_t* srcRow = (uint8_t*)readBuffer.get();
-        uint8_t* dstRow = (uint8_t*)destBytes;
+        uint8_t* dstRow = (uint8_t*)dest;
 
         for (size_t j = 0; j < (size_t)height; j++) {
             auto src = (const uint16_t*)srcRow;
             auto dst = (float*)dstRow;
 
             const auto srcEnd = src + channelsPerRow;
             while (src != srcEnd) {
                 *dst = unpackFromFloat16(*src);
@@ -1290,115 +1302,112 @@ WebGLContext::DoReadPixelsAndConvert(GLi
 
             srcRow += readStride;
             dstRow += destStride;
         }
 
         return true;
     }
 
-    gl->fReadPixels(x, y, width, height, destFormat, destType, destBytes);
+    // On at least Win+NV, we'll get PBO errors if we don't have at least
+    // `rowStride * height` bytes available to read into.
+    const auto naiveBytesNeeded = CheckedUint32(rowStride) * height;
+    const bool isDangerCloseToEdge = (!naiveBytesNeeded.isValid() ||
+                                      naiveBytesNeeded.value() > destSize);
+    const bool useParanoidHandling = (gl->WorkAroundDriverBugs() &&
+                                      isDangerCloseToEdge &&
+                                      mBoundPixelPackBuffer);
+    if (!useParanoidHandling) {
+        gl->fReadPixels(x, y, width, height, format, destType, dest);
+        return true;
+    }
+
+    // Read everything but the last row.
+    const auto bodyHeight = height - 1;
+    if (bodyHeight) {
+        gl->fReadPixels(x, y, width, bodyHeight, format, destType, dest);
+    }
+
+    // Now read the last row.
+    gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
+    gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
+    gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+
+    const auto tailRowOffset = (char*)dest + rowStride * bodyHeight;
+    gl->fReadPixels(x, y+bodyHeight, width, 1, format, destType, tailRowOffset);
+
+    gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, mPixelStore_PackAlignment);
+    gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, mPixelStore_PackRowLength);
+    gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows);
     return true;
 }
 
 static bool
-IsFormatAndTypeUnpackable(GLenum format, GLenum type, bool isWebGL2)
+GetJSScalarFromGLType(GLenum type, js::Scalar::Type* const out_scalarType)
 {
     switch (type) {
+    case LOCAL_GL_BYTE:
+        *out_scalarType = js::Scalar::Int8;
+        return true;
+
     case LOCAL_GL_UNSIGNED_BYTE:
-        switch (format) {
-        case LOCAL_GL_LUMINANCE:
-        case LOCAL_GL_LUMINANCE_ALPHA:
-            return isWebGL2;
-        case LOCAL_GL_ALPHA:
-        case LOCAL_GL_RED:
-        case LOCAL_GL_RED_INTEGER:
-        case LOCAL_GL_RG:
-        case LOCAL_GL_RG_INTEGER:
-        case LOCAL_GL_RGB:
-        case LOCAL_GL_RGB_INTEGER:
-        case LOCAL_GL_RGBA:
-        case LOCAL_GL_RGBA_INTEGER:
-            return true;
-        default:
-            return false;
-        }
-
-    case LOCAL_GL_BYTE:
-        switch (format) {
-        case LOCAL_GL_RED:
-        case LOCAL_GL_RED_INTEGER:
-        case LOCAL_GL_RG:
-        case LOCAL_GL_RG_INTEGER:
-        case LOCAL_GL_RGB:
-        case LOCAL_GL_RGB_INTEGER:
-        case LOCAL_GL_RGBA:
-        case LOCAL_GL_RGBA_INTEGER:
-            return true;
-        default:
-            return false;
-        }
+        *out_scalarType = js::Scalar::Uint8;
+        return true;
+
+    case LOCAL_GL_SHORT:
+        *out_scalarType = js::Scalar::Int16;
+        return true;
+
+    case LOCAL_GL_HALF_FLOAT:
+    case LOCAL_GL_HALF_FLOAT_OES:
+    case LOCAL_GL_UNSIGNED_SHORT:
+    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
+    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
+    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
+        *out_scalarType = js::Scalar::Uint16;
+        return true;
+
+    case LOCAL_GL_UNSIGNED_INT:
+    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
+    case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
+    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
+    case LOCAL_GL_UNSIGNED_INT_24_8:
+        *out_scalarType = js::Scalar::Uint32;
+        return true;
+    case LOCAL_GL_INT:
+        *out_scalarType = js::Scalar::Int32;
+        return true;
 
     case LOCAL_GL_FLOAT:
-    case LOCAL_GL_HALF_FLOAT:
-    case LOCAL_GL_HALF_FLOAT_OES:
-        switch (format) {
-        case LOCAL_GL_RED:
-        case LOCAL_GL_RG:
-        case LOCAL_GL_RGB:
-        case LOCAL_GL_RGBA:
-            return true;
-        default:
-            return false;
-        }
-
-    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
-    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
-        return format == LOCAL_GL_RGBA;
-
-    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
-        return format == LOCAL_GL_RGB;
+        *out_scalarType = js::Scalar::Float32;
+        return true;
 
     default:
         return false;
     }
 }
 
-static bool
-IsIntegerFormatAndTypeUnpackable(GLenum format, GLenum type)
+bool
+WebGLContext::ReadPixels_SharedPrecheck(ErrorResult* const out_error)
 {
-    switch (type) {
-    case LOCAL_GL_UNSIGNED_SHORT:
-    case LOCAL_GL_SHORT:
-    case LOCAL_GL_UNSIGNED_INT:
-    case LOCAL_GL_INT:
-        switch (format) {
-        case LOCAL_GL_RED_INTEGER:
-        case LOCAL_GL_RG_INTEGER:
-        case LOCAL_GL_RGB_INTEGER:
-        case LOCAL_GL_RGBA_INTEGER:
-            return true;
-        default:
-            return false;
-        }
-
-    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
-        return format == LOCAL_GL_RGBA ||
-               format == LOCAL_GL_RGBA_INTEGER;
-
-    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
-    case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
-        return format == LOCAL_GL_RGB;
-
-    default:
+    if (IsContextLost())
+        return false;
+
+    if (mCanvasElement &&
+        mCanvasElement->IsWriteOnly() &&
+        !nsContentUtils::IsCallerChrome())
+    {
+        GenerateWarning("readPixels: Not allowed");
+        out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
         return false;
     }
+
+    return true;
 }
 
-
 bool
 WebGLContext::ValidatePackSize(const char* funcName, uint32_t width, uint32_t height,
                                uint8_t bytesPerPixel, uint32_t* const out_rowStride,
                                uint32_t* const out_endOffset)
 {
     if (!width || !height) {
         *out_rowStride = 0;
         *out_endOffset = 0;
@@ -1438,237 +1447,271 @@ WebGLContext::ValidatePackSize(const cha
 }
 
 void
 WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                          GLenum type,
                          const dom::Nullable<dom::ArrayBufferView>& pixels,
                          ErrorResult& out_error)
 {
-    const char funcName[] = "readPixels";
-    if (IsContextLost())
-        return;
-
-    if (mCanvasElement &&
-        mCanvasElement->IsWriteOnly() &&
-        !nsContentUtils::IsCallerChrome())
-    {
-        GenerateWarning("readPixels: Not allowed");
-        out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    if (!ReadPixels_SharedPrecheck(&out_error))
         return;
-    }
-
-    if (width < 0 || height < 0)
-        return ErrorInvalidValue("readPixels: negative size passed");
-
-    if (pixels.IsNull())
-        return ErrorInvalidValue("readPixels: null destination buffer");
-
-    if (!(IsWebGL2() && IsIntegerFormatAndTypeUnpackable(format, type)) &&
-        !IsFormatAndTypeUnpackable(format, type, IsWebGL2())) {
-        return ErrorInvalidEnum("readPixels: Bad format or type.");
-    }
-
-    int channels = 0;
-
-    // Check the format param
-    switch (format) {
-    case LOCAL_GL_ALPHA:
-    case LOCAL_GL_LUMINANCE:
-    case LOCAL_GL_RED:
-    case LOCAL_GL_RED_INTEGER:
-        channels = 1;
-        break;
-    case LOCAL_GL_LUMINANCE_ALPHA:
-    case LOCAL_GL_RG:
-    case LOCAL_GL_RG_INTEGER:
-        channels = 2;
-        break;
-    case LOCAL_GL_RGB:
-    case LOCAL_GL_RGB_INTEGER:
-        channels = 3;
-        break;
-    case LOCAL_GL_RGBA:
-    case LOCAL_GL_RGBA_INTEGER:
-        channels = 4;
-        break;
-    default:
-        MOZ_CRASH("GFX: bad `format`");
-    }
-
-
-    // Check the type param
-    int bytesPerPixel;
-    int requiredDataType;
-    switch (type) {
-    case LOCAL_GL_BYTE:
-        bytesPerPixel = 1*channels;
-        requiredDataType = js::Scalar::Int8;
-        break;
-
-    case LOCAL_GL_UNSIGNED_BYTE:
-        bytesPerPixel = 1*channels;
-        requiredDataType = js::Scalar::Uint8;
-        break;
-
-    case LOCAL_GL_SHORT:
-        bytesPerPixel = 2*channels;
-        requiredDataType = js::Scalar::Int16;
-        break;
-
-    case LOCAL_GL_UNSIGNED_SHORT:
-    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
-    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
-    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
-        bytesPerPixel = 2;
-        requiredDataType = js::Scalar::Uint16;
-        break;
-
-    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
-    case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
-    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
-    case LOCAL_GL_UNSIGNED_INT_24_8:
-        bytesPerPixel = 4;
-        requiredDataType = js::Scalar::Uint32;
-        break;
-
-    case LOCAL_GL_UNSIGNED_INT:
-        bytesPerPixel = 4*channels;
-        requiredDataType = js::Scalar::Uint32;
-        break;
-
-    case LOCAL_GL_INT:
-        bytesPerPixel = 4*channels;
-        requiredDataType = js::Scalar::Int32;
-        break;
-
-    case LOCAL_GL_FLOAT:
-        bytesPerPixel = 4*channels;
-        requiredDataType = js::Scalar::Float32;
-        break;
-
-    case LOCAL_GL_HALF_FLOAT:
-    case LOCAL_GL_HALF_FLOAT_OES:
-        bytesPerPixel = 2*channels;
-        requiredDataType = js::Scalar::Uint16;
-        break;
-
-    default:
-        MOZ_CRASH("GFX: bad `type`");
+
+    if (mBoundPixelPackBuffer) {
+        ErrorInvalidOperation("readPixels: PIXEL_PACK_BUFFER must be null.");
+        return;
     }
 
     //////
 
+    if (pixels.IsNull()) {
+        ErrorInvalidValue("readPixels: null destination buffer");
+        return;
+    }
+
     const auto& view = pixels.Value();
 
+    //////
+
+    js::Scalar::Type reqScalarType;
+    if (!GetJSScalarFromGLType(type, &reqScalarType)) {
+        ErrorInvalidEnum("readPixels: Bad `type`.");
+        return;
+    }
+
+    const js::Scalar::Type dataScalarType = JS_GetArrayBufferViewType(view.Obj());
+    if (dataScalarType != reqScalarType) {
+        ErrorInvalidOperation("readPixels: `pixels` type does not match `type`.");
+        return;
+    }
+
+    //////
+
     // Compute length and data.  Don't reenter after this point, lest the
     // precomputed go out of sync with the instant length/data.
     view.ComputeLengthAndData();
     void* data = view.DataAllowShared();
-    size_t bytesAvailable = view.LengthAllowShared();
-    js::Scalar::Type dataType = JS_GetArrayBufferViewType(view.Obj());
-
-    // Check the pixels param type
-    if (dataType != requiredDataType)
-        return ErrorInvalidOperation("readPixels: Mismatched type/pixels types");
+    const auto dataLen = view.LengthAllowShared();
 
     if (!data) {
         ErrorOutOfMemory("readPixels: buffer storage is null. Did we run out of memory?");
         out_error.Throw(NS_ERROR_OUT_OF_MEMORY);
         return;
     }
 
+    ReadPixelsImpl(x, y, width, height, format, type, data, dataLen);
+}
+
+void
+WebGL2Context::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
+                          GLenum type, WebGLsizeiptr offset, ErrorResult& out_error)
+{
+    if (!ReadPixels_SharedPrecheck(&out_error))
+        return;
+
+    if (!mBoundPixelPackBuffer) {
+        ErrorInvalidOperation("readPixels: PIXEL_PACK_BUFFER must not be null.");
+        return;
+    }
+
     //////
 
-    uint32_t rowStride;
-    uint32_t bytesNeeded;
-    if (!ValidatePackSize(funcName, width, height, bytesPerPixel, &rowStride,
-                          &bytesNeeded))
-    {
+    if (offset < 0) {
+        ErrorInvalidValue("readPixels: offset must not be negative.");
         return;
     }
 
-    if (bytesNeeded > bytesAvailable) {
-        ErrorInvalidOperation("readPixels: buffer too small");
+    {
+        const auto bytesPerType = webgl::BytesPerPixel({LOCAL_GL_RED, type});
+
+        if (offset % bytesPerType != 0) {
+            ErrorInvalidOperation("readPixels: `offset` must be divisible by the size"
+                                  " a `type` in bytes.");
+            return;
+        }
+    }
+
+    //////
+
+    const auto bytesAvailable = mBoundPixelPackBuffer->ByteLength();
+    const auto checkedBytesAfterOffset = CheckedUint32(bytesAvailable) - offset;
+
+    uint32_t bytesAfterOffset = 0;
+    if (checkedBytesAfterOffset.isValid()) {
+        bytesAfterOffset = checkedBytesAfterOffset.value();
+    }
+
+    ReadPixelsImpl(x, y, width, height, format, type, (void*)offset, bytesAfterOffset);
+}
+
+static bool
+ValidateReadPixelsFormatAndType(const webgl::FormatInfo* srcFormat,
+                                const webgl::PackingInfo& pi, gl::GLContext* gl,
+                                WebGLContext* webgl)
+{
+    // Check the format and type params to assure they are an acceptable pair (as per spec)
+    GLenum mainFormat;
+    GLenum mainType;
+
+    switch (srcFormat->componentType) {
+    case webgl::ComponentType::Float:
+        mainFormat = LOCAL_GL_RGBA;
+        mainType = LOCAL_GL_FLOAT;
+        break;
+
+    case webgl::ComponentType::UInt:
+        mainFormat = LOCAL_GL_RGBA_INTEGER;
+        mainType = LOCAL_GL_UNSIGNED_INT;
+        break;
+
+    case webgl::ComponentType::Int:
+        mainFormat = LOCAL_GL_RGBA_INTEGER;
+        mainType = LOCAL_GL_INT;
+        break;
+
+    case webgl::ComponentType::NormInt:
+    case webgl::ComponentType::NormUInt:
+        mainFormat = LOCAL_GL_RGBA;
+        mainType = LOCAL_GL_UNSIGNED_BYTE;
+        break;
+
+    default:
+        gfxCriticalNote << "Unhandled srcFormat->componentType: "
+                        << (uint32_t)srcFormat->componentType;
+        webgl->ErrorInvalidOperation("readPixels: Unhandled srcFormat->componentType."
+                                     " Please file a bug!");
+        return false;
+    }
+
+    if (pi.format == mainFormat && pi.type == mainType)
+        return true;
+
+    //////
+    // OpenGL ES 3.0.4 p194 - When the internal format of the rendering surface is
+    // RGB10_A2, a third combination of format RGBA and type UNSIGNED_INT_2_10_10_10_REV
+    // is accepted.
+
+    if (webgl->IsWebGL2() &&
+        srcFormat->effectiveFormat == webgl::EffectiveFormat::RGB10_A2 &&
+        pi.format == LOCAL_GL_RGBA &&
+        pi.type == LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV)
+    {
+        return true;
+    }
+
+    //////
+    // OpenGL ES 2.0 $4.3.1 - IMPLEMENTATION_COLOR_READ_{TYPE/FORMAT} is a valid
+    // combination for glReadPixels()...
+
+    // So yeah, we are actually checking that these are valid as /unpack/ formats, instead
+    // of /pack/ formats here, but it should cover the INVALID_ENUM cases.
+    if (!webgl->mFormatUsage->AreUnpackEnumsValid(pi.format, pi.type)) {
+        webgl->ErrorInvalidEnum("readPixels: Bad format and/or type.");
+        return false;
+    }
+
+    // Only valid when pulled from:
+    // * GLES 2.0.25 p105:
+    //   "table 3.4, excluding formats LUMINANCE and LUMINANCE_ALPHA."
+    // * GLES 3.0.4 p193:
+    //   "table 3.2, excluding formats DEPTH_COMPONENT and DEPTH_STENCIL."
+    switch (pi.format) {
+    case LOCAL_GL_LUMINANCE:
+    case LOCAL_GL_LUMINANCE_ALPHA:
+    case LOCAL_GL_DEPTH_COMPONENT:
+    case LOCAL_GL_DEPTH_STENCIL:
+        webgl->ErrorInvalidEnum("readPixels: Invalid format: 0x%04x", pi.format);
+        return false;
+    }
+
+    if (pi.type == LOCAL_GL_UNSIGNED_INT_24_8) {
+        webgl->ErrorInvalidEnum("readPixels: Invalid type: 0x%04x", pi.type);
+        return false;
+    }
+
+    MOZ_ASSERT(gl->IsCurrent());
+    if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
+        const auto auxFormat = gl->GetIntAs<GLenum>(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT);
+        const auto auxType = gl->GetIntAs<GLenum>(LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE);
+
+        if (auxFormat && auxType &&
+            pi.format == auxFormat && pi.type == auxType)
+        {
+            return true;
+        }
+    }
+
+    //////
+
+    webgl->ErrorInvalidOperation("readPixels: Invalid format or type.");
+    return false;
+}
+
+void
+WebGLContext::ReadPixelsImpl(GLint x, GLint y, GLsizei rawWidth, GLsizei rawHeight,
+                             GLenum packFormat, GLenum packType, void* dest,
+                             uint32_t dataLen)
+{
+    if (rawWidth < 0 || rawHeight < 0) {
+        ErrorInvalidValue("readPixels: negative size passed");
         return;
     }
 
+    const uint32_t width(rawWidth);
+    const uint32_t height(rawHeight);
+
     //////
 
     MakeContextCurrent();
 
     const webgl::FormatUsageInfo* srcFormat;
     uint32_t srcWidth;
     uint32_t srcHeight;
     if (!ValidateCurFBForRead("readPixels", &srcFormat, &srcWidth, &srcHeight))
         return;
 
-    // Check the format and type params to assure they are an acceptable pair (as per spec)
-    auto srcType = srcFormat->format->componentType;
-    GLenum mainReadFormat;
-    GLenum mainReadType;
-    switch (srcType) {
-        case webgl::ComponentType::Float:
-            mainReadFormat = LOCAL_GL_RGBA;
-            mainReadType = LOCAL_GL_FLOAT;
-            break;
-        case webgl::ComponentType::UInt:
-            mainReadFormat = LOCAL_GL_RGBA_INTEGER;
-            mainReadType = LOCAL_GL_UNSIGNED_INT;
-            break;
-        case webgl::ComponentType::Int:
-            mainReadFormat = LOCAL_GL_RGBA_INTEGER;
-            mainReadType = LOCAL_GL_INT;
-            break;
-        default:
-            mainReadFormat = LOCAL_GL_RGBA;
-            mainReadType = LOCAL_GL_UNSIGNED_BYTE;
-            break;
+    //////
+
+    const webgl::PackingInfo pi = {packFormat, packType};
+    if (!ValidateReadPixelsFormatAndType(srcFormat->format, pi, gl, this))
+        return;
+
+    uint8_t bytesPerPixel;
+    if (!webgl::GetBytesPerPixel(pi, &bytesPerPixel)) {
+        ErrorInvalidOperation("readPixels: Unsupported format and type.");
+        return;
     }
 
-    GLenum auxReadFormat = mainReadFormat;
-    GLenum auxReadType = mainReadType;
-
-    // OpenGL ES 2.0 $4.3.1 - IMPLEMENTATION_COLOR_READ_{TYPE/FORMAT} is a valid
-    // combination for glReadPixels().
-    if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
-        gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT,
-                         reinterpret_cast<GLint*>(&auxReadFormat));
-        gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE,
-                         reinterpret_cast<GLint*>(&auxReadType));
+    //////
+
+    uint32_t rowStride;
+    uint32_t bytesNeeded;
+    if (!ValidatePackSize("readPixels", width, height, bytesPerPixel, &rowStride,
+                          &bytesNeeded))
+    {
+        return;
     }
 
-    const bool mainMatches = (format == mainReadFormat && type == mainReadType);
-    const bool auxMatches = (format == auxReadFormat && type == auxReadType);
-    bool isValid = mainMatches || auxMatches;
-
-    // OpenGL ES 3.0.4 p194 - When the internal format of the rendering surface is
-    // RGB10_A2, a third combination of format RGBA and type UNSIGNED_INT_2_10_10_10_REV
-    // is accepted.
-    if (srcFormat->format->effectiveFormat == webgl::EffectiveFormat::RGB10_A2 &&
-        format == LOCAL_GL_RGBA &&
-        type == LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV)
-    {
-        isValid = true;
+    if (bytesNeeded > dataLen) {
+        ErrorInvalidOperation("readPixels: buffer too small");
+        return;
     }
 
-    if (!isValid)
-        return ErrorInvalidOperation("readPixels: Invalid format/type pair");
-
+    ////////////////
     // Now that the errors are out of the way, on to actually reading!
 
     uint32_t readX, readY;
     uint32_t writeX, writeY;
     uint32_t rwWidth, rwHeight;
     Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
     Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
 
     if (rwWidth == uint32_t(width) && rwHeight == uint32_t(height)) {
-        DoReadPixelsAndConvert(x, y, width, height, format, type, data, auxReadFormat,
-                               auxReadType);
+        DoReadPixelsAndConvert(srcFormat->format, x, y, width, height, packFormat,
+                               packType, dest, dataLen, rowStride);
         return;
     }
 
     // Read request contains out-of-bounds pixels. Unfortunately:
     // GLES 3.0.4 p194 "Obtaining Pixels from the Framebuffer":
     // "If any of these pixels lies outside of the window allocated to the current GL
     //  context, or outside of the image attached to the currently bound framebuffer
     //  object, then the values obtained for those pixels are undefined."
@@ -1683,35 +1726,36 @@ WebGLContext::ReadPixels(GLint x, GLint 
     if (!rwWidth || !rwHeight) {
         // There aren't any, so we're 'done'.
         DummyReadFramebufferOperation("readPixels");
         return;
     }
 
     if (IsWebGL2()) {
         if (!mPixelStore_PackRowLength) {
-            gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, mPixelStore_PackSkipPixels + width);
+            gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
+                             mPixelStore_PackSkipPixels + width);
         }
         gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, mPixelStore_PackSkipPixels + writeX);
         gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows + writeY);
 
-        DoReadPixelsAndConvert(readX, readY, rwWidth, rwHeight, format, type, data,
-                               auxReadFormat, auxReadType);
+        DoReadPixelsAndConvert(srcFormat->format, readX, readY, rwWidth, rwHeight,
+                               packFormat, packType, dest, dataLen, rowStride);
 
         gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, mPixelStore_PackRowLength);
         gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, mPixelStore_PackSkipPixels);
         gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows);
     } else {
         // I *did* say "hilariously slow".
 
-        uint8_t* row = (uint8_t*)data + writeX * bytesPerPixel;
+        uint8_t* row = (uint8_t*)dest + writeX * bytesPerPixel;
         row += writeY * rowStride;
         for (uint32_t j = 0; j < rwHeight; j++) {
-            DoReadPixelsAndConvert(readX, readY+j, rwWidth, 1, format, type, row,
-                                   auxReadFormat, auxReadType);
+            DoReadPixelsAndConvert(srcFormat->format, readX, readY+j, rwWidth, 1,
+                                   packFormat, packType, row, dataLen, rowStride);
             row += rowStride;
         }
     }
 }
 
 void
 WebGLContext::RenderbufferStorage_base(const char* funcName, GLenum target,
                                        GLsizei samples, GLenum internalFormat,
@@ -1842,326 +1886,278 @@ WebGLContext::StencilOpSeparate(GLenum f
 
     MakeContextCurrent();
     gl->fStencilOpSeparate(face, sfail, dpfail, dppass);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Uniform setters.
 
+class ValidateIfSampler
+{
+    const WebGLUniformLocation* const mLoc;
+    const size_t mDataCount;
+    const GLint* const mData;
+    bool mIsValidatedSampler;
+
+public:
+    ValidateIfSampler(WebGLContext* webgl, const char* funcName,
+                      WebGLUniformLocation* loc, size_t dataCount, const GLint* data,
+                      bool* const out_error)
+        : mLoc(loc)
+        , mDataCount(dataCount)
+        , mData(data)
+        , mIsValidatedSampler(false)
+    {
+        if (!mLoc->mInfo->mSamplerTexList) {
+            *out_error = false;
+            return;
+        }
+
+        for (size_t i = 0; i < mDataCount; i++) {
+            const auto& val = mData[i];
+            if (val < 0 || uint32_t(val) >= webgl->GLMaxTextureUnits()) {
+                webgl->ErrorInvalidValue("%s: This uniform location is a sampler, but %d"
+                                         " is not a valid texture unit.",
+                                         funcName, val);
+                *out_error = true;
+                return;
+            }
+        }
+
+        mIsValidatedSampler = true;
+        *out_error = false;
+    }
+
+    ~ValidateIfSampler() {
+        if (!mIsValidatedSampler)
+            return;
+
+        auto& samplerValues = mLoc->mInfo->mSamplerValues;
+
+        for (size_t i = 0; i < mDataCount; i++) {
+            const size_t curIndex = mLoc->mArrayIndex + i;
+            if (curIndex >= samplerValues.size())
+                break;
+
+            samplerValues[curIndex] = mData[i];
+        }
+    }
+};
+
+////////////////////
+
 void
 WebGLContext::Uniform1i(WebGLUniformLocation* loc, GLint a1)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_INT, "uniform1i", &rawLoc))
+    const char funcName[] = "uniform1i";
+    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_INT, funcName))
         return;
 
-    // Only uniform1i can take sampler settings.
-    if (!loc->ValidateSamplerSetter(a1, this, "uniform1i"))
+    bool error;
+    const ValidateIfSampler validate(this, funcName, loc, 1, &a1, &error);
+    if (error)
         return;
 
     MakeContextCurrent();
-    gl->fUniform1i(rawLoc, a1);
+    gl->fUniform1i(loc->mLoc, a1);
 }
 
 void
 WebGLContext::Uniform2i(WebGLUniformLocation* loc, GLint a1, GLint a2)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_INT, "uniform2i", &rawLoc))
+    const char funcName[] = "uniform2i";
+    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_INT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform2i(rawLoc, a1, a2);
+    gl->fUniform2i(loc->mLoc, a1, a2);
 }
 
 void
 WebGLContext::Uniform3i(WebGLUniformLocation* loc, GLint a1, GLint a2, GLint a3)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_INT, "uniform3i", &rawLoc))
+    const char funcName[] = "uniform3i";
+    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_INT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform3i(rawLoc, a1, a2, a3);
+    gl->fUniform3i(loc->mLoc, a1, a2, a3);
 }
 
 void
 WebGLContext::Uniform4i(WebGLUniformLocation* loc, GLint a1, GLint a2, GLint a3,
                         GLint a4)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_INT, "uniform4i", &rawLoc))
+    const char funcName[] = "uniform4i";
+    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_INT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform4i(rawLoc, a1, a2, a3, a4);
+    gl->fUniform4i(loc->mLoc, a1, a2, a3, a4);
 }
 
+//////////
+
 void
 WebGLContext::Uniform1f(WebGLUniformLocation* loc, GLfloat a1)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_FLOAT, "uniform1f", &rawLoc))
+    const char funcName[] = "uniform1f";
+    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_FLOAT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform1f(rawLoc, a1);
+    gl->fUniform1f(loc->mLoc, a1);
 }
 
 void
 WebGLContext::Uniform2f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_FLOAT, "uniform2f", &rawLoc))
+    const char funcName[] = "uniform2f";
+    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_FLOAT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform2f(rawLoc, a1, a2);
+    gl->fUniform2f(loc->mLoc, a1, a2);
 }
 
 void
 WebGLContext::Uniform3f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2,
                         GLfloat a3)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_FLOAT, "uniform3f", &rawLoc))
+    const char funcName[] = "uniform3f";
+    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_FLOAT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform3f(rawLoc, a1, a2, a3);
+    gl->fUniform3f(loc->mLoc, a1, a2, a3);
 }
 
 void
 WebGLContext::Uniform4f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2,
                         GLfloat a3, GLfloat a4)
 {
-    GLuint rawLoc;
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_FLOAT, "uniform4f", &rawLoc))
+    const char funcName[] = "uniform4f";
+    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_FLOAT, funcName))
         return;
 
     MakeContextCurrent();
-    gl->fUniform4f(rawLoc, a1, a2, a3, a4);
+    gl->fUniform4f(loc->mLoc, a1, a2, a3, a4);
 }
 
 ////////////////////////////////////////
 // Array
 
 void
-WebGLContext::Uniform1iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 1, LOCAL_GL_INT, arrayLength,
-                                    "uniform1iv", &rawLoc,
-                                    &numElementsToUpload))
-    {
-        return;
-    }
-
-    if (!loc->ValidateSamplerSetter(data[0], this, "uniform1iv"))
-        return;
-
-    MakeContextCurrent();
-    gl->fUniform1iv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGLContext::Uniform2iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 2, LOCAL_GL_INT, arrayLength,
-                                    "uniform2iv", &rawLoc,
-                                    &numElementsToUpload))
-    {
-        return;
-    }
-
-    if (!loc->ValidateSamplerSetter(data[0], this, "uniform2iv") ||
-        !loc->ValidateSamplerSetter(data[1], this, "uniform2iv"))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform2iv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGLContext::Uniform3iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLint* data)
+WebGLContext::UniformNiv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                         const IntArr& arr)
 {
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 3, LOCAL_GL_INT, arrayLength,
-                                    "uniform3iv", &rawLoc,
-                                    &numElementsToUpload))
-    {
-        return;
-    }
-
-    if (!loc->ValidateSamplerSetter(data[0], this, "uniform3iv") ||
-        !loc->ValidateSamplerSetter(data[1], this, "uniform3iv") ||
-        !loc->ValidateSamplerSetter(data[2], this, "uniform3iv"))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform3iv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGLContext::Uniform4iv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLint* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 4, LOCAL_GL_INT, arrayLength,
-                                    "uniform4iv", &rawLoc,
-                                    &numElementsToUpload))
-    {
-        return;
-    }
-
-    if (!loc->ValidateSamplerSetter(data[0], this, "uniform4iv") ||
-        !loc->ValidateSamplerSetter(data[1], this, "uniform4iv") ||
-        !loc->ValidateSamplerSetter(data[2], this, "uniform4iv") ||
-        !loc->ValidateSamplerSetter(data[3], this, "uniform4iv"))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniform4iv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGLContext::Uniform1fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 1, LOCAL_GL_FLOAT, arrayLength,
-                                    "uniform1fv", &rawLoc,
+    uint32_t numElementsToUpload;
+    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_INT, arr.dataCount, funcName,
                                     &numElementsToUpload))
     {
         return;
     }
 
-    MakeContextCurrent();
-    gl->fUniform1fv(rawLoc, numElementsToUpload, data);
-}
-
-void
-WebGLContext::Uniform2fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLfloat* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 2, LOCAL_GL_FLOAT, arrayLength,
-                                    "uniform2fv", &rawLoc,
-                                    &numElementsToUpload))
-    {
+    bool error;
+    const ValidateIfSampler samplerValidator(this, funcName, loc, numElementsToUpload,
+                                             arr.data, &error);
+    if (error)
         return;
-    }
+
+    static const decltype(&gl::GLContext::fUniform1iv) kFuncList[] = {
+        &gl::GLContext::fUniform1iv,
+        &gl::GLContext::fUniform2iv,
+        &gl::GLContext::fUniform3iv,
+        &gl::GLContext::fUniform4iv
+    };
+    const auto func = kFuncList[N-1];
 
     MakeContextCurrent();
-    gl->fUniform2fv(rawLoc, numElementsToUpload, data);
+    (gl->*func)(loc->mLoc, numElementsToUpload, arr.data);
 }
 
 void
-WebGLContext::Uniform3fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLfloat* data)
+WebGL2Context::UniformNuiv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                           const UintArr& arr)
 {
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 3, LOCAL_GL_FLOAT, arrayLength,
-                                    "uniform3fv", &rawLoc,
-                                    &numElementsToUpload))
+    uint32_t numElementsToUpload;
+    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_UNSIGNED_INT, arr.dataCount,
+                                    funcName, &numElementsToUpload))
     {
         return;
     }
+    MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
+
+    static const decltype(&gl::GLContext::fUniform1uiv) kFuncList[] = {
+        &gl::GLContext::fUniform1uiv,
+        &gl::GLContext::fUniform2uiv,
+        &gl::GLContext::fUniform3uiv,
+        &gl::GLContext::fUniform4uiv
+    };
+    const auto func = kFuncList[N-1];
 
     MakeContextCurrent();
-    gl->fUniform3fv(rawLoc, numElementsToUpload, data);
+    (gl->*func)(loc->mLoc, numElementsToUpload, arr.data);
 }
 
 void
-WebGLContext::Uniform4fv_base(WebGLUniformLocation* loc, size_t arrayLength,
-                              const GLfloat* data)
+WebGLContext::UniformNfv(const char* funcName, uint8_t N, WebGLUniformLocation* loc,
+                         const FloatArr& arr)
 {
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, 4, LOCAL_GL_FLOAT, arrayLength,
-                                    "uniform4fv", &rawLoc,
+    uint32_t numElementsToUpload;
+    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_FLOAT, arr.dataCount, funcName,
                                     &numElementsToUpload))
     {
         return;
     }
+    MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
+
+    static const decltype(&gl::GLContext::fUniform1fv) kFuncList[] = {
+        &gl::GLContext::fUniform1fv,
+        &gl::GLContext::fUniform2fv,
+        &gl::GLContext::fUniform3fv,
+        &gl::GLContext::fUniform4fv
+    };
+    const auto func = kFuncList[N-1];
 
     MakeContextCurrent();
-    gl->fUniform4fv(rawLoc, numElementsToUpload, data);
-}
-
-////////////////////////////////////////
-// Matrix
-
-void
-WebGLContext::UniformMatrix2fv_base(WebGLUniformLocation* loc, bool transpose,
-                                    size_t arrayLength, const float* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformMatrixArraySetter(loc, 2, 2, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix2fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix2fv(rawLoc, numElementsToUpload, false, data);
+    (gl->*func)(loc->mLoc, numElementsToUpload, arr.data);
 }
 
 void
-WebGLContext::UniformMatrix3fv_base(WebGLUniformLocation* loc, bool transpose,
-                                    size_t arrayLength, const float* data)
+WebGLContext::UniformMatrixAxBfv(const char* funcName, uint8_t A, uint8_t B,
+                                 WebGLUniformLocation* loc, bool transpose,
+                                 const FloatArr& arr)
 {
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformMatrixArraySetter(loc, 3, 3, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix3fv",
-                                          &rawLoc, &numElementsToUpload))
+    uint32_t numElementsToUpload;
+    if (!ValidateUniformMatrixArraySetter(loc, A, B, LOCAL_GL_FLOAT, arr.dataCount,
+                                          transpose, funcName, &numElementsToUpload))
     {
         return;
     }
+    MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
+
+    static const decltype(&gl::GLContext::fUniformMatrix2fv) kFuncList[] = {
+        &gl::GLContext::fUniformMatrix2fv,
+        &gl::GLContext::fUniformMatrix2x3fv,
+        &gl::GLContext::fUniformMatrix2x4fv,
+
+        &gl::GLContext::fUniformMatrix3x2fv,
+        &gl::GLContext::fUniformMatrix3fv,
+        &gl::GLContext::fUniformMatrix3x4fv,
+
+        &gl::GLContext::fUniformMatrix4x2fv,
+        &gl::GLContext::fUniformMatrix4x3fv,
+        &gl::GLContext::fUniformMatrix4fv
+    };
+    const auto func = kFuncList[3*(A-2) + (B-2)];
 
     MakeContextCurrent();
-    gl->fUniformMatrix3fv(rawLoc, numElementsToUpload, false, data);
-}
-
-void
-WebGLContext::UniformMatrix4fv_base(WebGLUniformLocation* loc, bool transpose,
-                                    size_t arrayLength, const float* data)
-{
-    GLuint rawLoc;
-    GLsizei numElementsToUpload;
-    if (!ValidateUniformMatrixArraySetter(loc, 4, 4, LOCAL_GL_FLOAT, arrayLength,
-                                          transpose, "uniformMatrix4fv",
-                                          &rawLoc, &numElementsToUpload))
-    {
-        return;
-    }
-
-    MakeContextCurrent();
-    gl->fUniformMatrix4fv(rawLoc, numElementsToUpload, false, data);
+    (gl->*func)(loc->mLoc, numElementsToUpload, false, arr.data);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 void
 WebGLContext::UseProgram(WebGLProgram* prog)
 {
     if (IsContextLost())
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -432,17 +432,17 @@ WebGLContext::ValidateUniformLocation(We
     if (!ValidateObject(funcName, loc))
         return false;
 
     if (!mCurrentProgram) {
         ErrorInvalidOperation("%s: No program is currently bound.", funcName);
         return false;
     }
 
-    return loc->ValidateForProgram(mCurrentProgram, this, funcName);
+    return loc->ValidateForProgram(mCurrentProgram, funcName);
 }
 
 bool
 WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t setterElemSize,
                                         uint32_t arrayLength)
 {
     if (IsContextLost())
         return false;
@@ -454,92 +454,92 @@ WebGLContext::ValidateAttribArraySetter(
     }
 
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformSetter(WebGLUniformLocation* loc,
                                     uint8_t setterElemSize, GLenum setterType,
-                                    const char* funcName, GLuint* out_rawLoc)
+                                    const char* funcName)
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc, funcName))
         return false;
 
-    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, funcName))
         return false;
 
-    *out_rawLoc = loc->mLoc;
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformArraySetter(WebGLUniformLocation* loc,
                                          uint8_t setterElemSize,
                                          GLenum setterType,
-                                         size_t setterArraySize,
+                                         uint32_t setterArraySize,
                                          const char* funcName,
-                                         GLuint* const out_rawLoc,
-                                         GLsizei* const out_numElementsToUpload)
+                                         uint32_t* const out_numElementsToUpload)
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc, funcName))
         return false;
 
-    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, funcName))
+        return false;
+
+    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, funcName))
         return false;
 
-    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
-        return false;
+    const auto& elemCount = loc->mInfo->mActiveInfo->mElemCount;
+    MOZ_ASSERT(elemCount > loc->mArrayIndex);
+    const uint32_t uniformElemCount = elemCount - loc->mArrayIndex;
 
-    MOZ_ASSERT((size_t)loc->mActiveInfo->mElemCount > loc->mArrayIndex);
-    size_t uniformElemCount = loc->mActiveInfo->mElemCount - loc->mArrayIndex;
-    *out_rawLoc = loc->mLoc;
-    *out_numElementsToUpload = std::min(uniformElemCount, setterArraySize / setterElemSize);
+    *out_numElementsToUpload = std::min(uniformElemCount,
+                                        setterArraySize / setterElemSize);
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
                                                uint8_t setterCols,
                                                uint8_t setterRows,
                                                GLenum setterType,
-                                               size_t setterArraySize,
+                                               uint32_t setterArraySize,
                                                bool setterTranspose,
                                                const char* funcName,
-                                               GLuint* const out_rawLoc,
-                                               GLsizei* const out_numElementsToUpload)
+                                               uint32_t* const out_numElementsToUpload)
 {
-    uint8_t setterElemSize = setterCols * setterRows;
+    const uint8_t setterElemSize = setterCols * setterRows;
 
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc, funcName))
         return false;
 
-    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, funcName))
         return false;
 
-    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
+    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, funcName))
         return false;
 
     if (!ValidateUniformMatrixTranspose(setterTranspose, funcName))
         return false;
 
-    MOZ_ASSERT((size_t)loc->mActiveInfo->mElemCount > loc->mArrayIndex);
-    size_t uniformElemCount = loc->mActiveInfo->mElemCount - loc->mArrayIndex;
-    *out_rawLoc = loc->mLoc;
-    *out_numElementsToUpload = std::min(uniformElemCount, setterArraySize / setterElemSize);
+    const auto& elemCount = loc->mInfo->mActiveInfo->mElemCount;
+    MOZ_ASSERT(elemCount > loc->mArrayIndex);
+    const uint32_t uniformElemCount = elemCount - loc->mArrayIndex;
 
+    *out_numElementsToUpload = std::min(uniformElemCount,
+                                        setterArraySize / setterElemSize);
     return true;
 }
 
 bool
 WebGLContext::ValidateAttribIndex(GLuint index, const char* info)
 {
     bool valid = (index < MaxVertexAttribs());
 
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -435,34 +435,38 @@ GetFormat(EffectiveFormat format)
 //////////////////////////////////////////////////////////////////////////////////////////
 
 const FormatInfo*
 FormatInfo::GetCopyDecayFormat(UnsizedFormat uf) const
 {
     return FindOrNull(this->copyDecayFormats, uf);
 }
 
-uint8_t
-BytesPerPixel(const PackingInfo& packing)
+bool
+GetBytesPerPixel(const PackingInfo& packing, uint8_t* const out_bytes)
 {
     uint8_t bytesPerChannel;
+
     switch (packing.type) {
     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
     case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
-        return 2;
+        *out_bytes = 2;
+        return true;
 
     case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
     case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
     case LOCAL_GL_UNSIGNED_INT_24_8:
     case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
-        return 4;
+        *out_bytes = 4;
+        return true;
 
     case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
-        return 8;
+        *out_bytes = 8;
+        return true;
 
     // Alright, that's all the fixed-size unpackTypes.
 
     case LOCAL_GL_BYTE:
     case LOCAL_GL_UNSIGNED_BYTE:
         bytesPerChannel = 1;
         break;
 
@@ -475,46 +479,68 @@ BytesPerPixel(const PackingInfo& packing
 
     case LOCAL_GL_INT:
     case LOCAL_GL_UNSIGNED_INT:
     case LOCAL_GL_FLOAT:
         bytesPerChannel = 4;
         break;
 
     default:
-        MOZ_CRASH("GFX: invalid PackingInfo");
+        return false;
     }
 
     uint8_t channels;
+
     switch (packing.format) {
+    case LOCAL_GL_RED:
+    case LOCAL_GL_RED_INTEGER:
+    case LOCAL_GL_LUMINANCE:
+    case LOCAL_GL_ALPHA:
+    case LOCAL_GL_DEPTH_COMPONENT:
+        channels = 1;
+        break;
+
     case LOCAL_GL_RG:
     case LOCAL_GL_RG_INTEGER:
     case LOCAL_GL_LUMINANCE_ALPHA:
         channels = 2;
         break;
 
     case LOCAL_GL_RGB:
     case LOCAL_GL_RGB_INTEGER:
+    case LOCAL_GL_SRGB:
         channels = 3;
         break;
 
+    case LOCAL_GL_BGRA:
     case LOCAL_GL_RGBA:
     case LOCAL_GL_RGBA_INTEGER:
+    case LOCAL_GL_SRGB_ALPHA:
         channels = 4;
         break;
 
     default:
-        channels = 1;
-        break;
+        return false;
     }
 
-    return bytesPerChannel * channels;
+    *out_bytes = bytesPerChannel * channels;
+    return true;
 }
 
+uint8_t
+BytesPerPixel(const PackingInfo& packing)
+{
+    uint8_t ret;
+    if (MOZ_LIKELY(GetBytesPerPixel(packing, &ret)))
+        return ret;
 
+    gfxCriticalError() << "Bad `packing`: " << gfx::hexa(packing.format) << ", "
+                       << gfx::hexa(packing.type);
+    MOZ_CRASH("Bad `packing`.");
+}
 
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
--- a/dom/canvas/WebGLFormats.h
+++ b/dom/canvas/WebGLFormats.h
@@ -249,16 +249,17 @@ struct DriverUnpackInfo
         return {unpackFormat, unpackType};
     }
 };
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 const FormatInfo* GetFormat(EffectiveFormat format);
 uint8_t BytesPerPixel(const PackingInfo& packing);
+bool GetBytesPerPixel(const PackingInfo& packing, uint8_t* const out_bytes);
 /*
 GLint ComponentSize(const FormatInfo* format, GLenum component);
 GLenum ComponentType(const FormatInfo* format);
 */
 ////////////////////////////////////////
 
 struct FormatUsageInfo
 {
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1276,16 +1276,44 @@ WebGLFramebuffer::GetAttachmentParameter
     }
 
     FinalizeAttachments();
 
     return attachPoint->GetParameter(funcName, mContext, cx, target, attachment, pname,
                                      out_error);
 }
 
+
+void
+WebGLFramebuffer::GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const
+{
+    auto itr = mDrawBuffers.cbegin();
+    if (itr != mDrawBuffers.cend() &&
+        *itr != LOCAL_GL_NONE)
+    {
+        out->push_back(&mColorAttachment0);
+        ++itr;
+    }
+
+    size_t i = 0;
+    for (; itr != mDrawBuffers.cend(); ++itr) {
+        if (i >= mMoreColorAttachments.Size())
+            break;
+
+        if (*itr != LOCAL_GL_NONE) {
+            out->push_back(&mMoreColorAttachments[i]);
+        }
+        ++i;
+    }
+
+    out->push_back(&mDepthAttachment);
+    out->push_back(&mStencilAttachment);
+    out->push_back(&mDepthStencilAttachment);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLFramebufferBinding::Wrap(cx, this, givenProto);
 }
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -33,17 +33,17 @@ class WebGLFBAttachPoint
 public:
     WebGLFramebuffer* const mFB;
     const GLenum mAttachmentPoint;
 private:
     WebGLRefPtr<WebGLTexture> mTexturePtr;
     WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
     TexImageTarget mTexImageTarget;
     GLint mTexImageLayer;
-    GLint mTexImageLevel;
+    uint32_t mTexImageLevel;
 
     // PlacementArray needs a default constructor.
     template<typename T>
     friend class PlacementArray;
 
     WebGLFBAttachPoint()
         : mFB(nullptr)
         , mAttachmentPoint(0)
@@ -84,17 +84,17 @@ public:
         return mRenderbufferPtr;
     }
     TexImageTarget ImageTarget() const {
         return mTexImageTarget;
     }
     GLint Layer() const {
         return mTexImageLayer;
     }
-    GLint MipLevel() const {
+    uint32_t MipLevel() const {
         return mTexImageLevel;
     }
     void AttachmentName(nsCString* out) const;
 
     bool HasUninitializedImageData() const;
     void SetImageDataStatus(WebGLImageDataStatus x);
 
     void Size(uint32_t* const out_width, uint32_t* const out_height) const;
@@ -254,16 +254,18 @@ public:
     }
 
     void SetReadBufferMode(GLenum readBufferMode) {
         mReadBufferMode = readBufferMode;
     }
 
     GLenum ReadBufferMode() const { return mReadBufferMode; }
 
+    void GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const;
+
 protected:
     WebGLFBAttachPoint* GetAttachPoint(GLenum attachment); // Fallible
 
 public:
     void DetachTexture(const WebGLTexture* tex);
 
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -64,46 +64,70 @@ ParseName(const nsCString& name, nsCStri
         return false;
 
     *out_baseName = StringHead(name, indexOpenBracket);
     *out_isArray = true;
     *out_arrayIndex = indexNum;
     return true;
 }
 
-static WebGLActiveInfo*
-AddActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType, bool isArray,
-              const nsACString& baseUserName, const nsACString& baseMappedName,
-              std::vector<RefPtr<WebGLActiveInfo>>* activeInfoList,
-              std::map<nsCString, const WebGLActiveInfo*>* infoLocMap)
+//////////
+
+/*static*/ const webgl::UniformInfo::TexListT*
+webgl::UniformInfo::GetTexList(WebGLActiveInfo* activeInfo)
 {
-    RefPtr<WebGLActiveInfo> info = new WebGLActiveInfo(webgl, elemCount, elemType,
-                                                       isArray, baseUserName,
-                                                       baseMappedName);
-    activeInfoList->push_back(info);
+    const auto& webgl = activeInfo->mWebGL;
+
+    switch (activeInfo->mElemType) {
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+        return &webgl->mBound2DTextures;
+
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+        return &webgl->mBoundCubeMapTextures;
 
-    infoLocMap->insert(std::make_pair(info->mBaseUserName, info.get()));
-    return info.get();
+    case LOCAL_GL_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+        return &webgl->mBound3DTextures;
+
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+        return &webgl->mBound2DArrayTextures;
+
+    default:
+        return nullptr;
+    }
 }
 
-static void
-AddActiveBlockInfo(const nsACString& baseUserName,
-                   const nsACString& baseMappedName,
-                   std::vector<RefPtr<webgl::UniformBlockInfo>>* activeInfoList)
+webgl::UniformInfo::UniformInfo(WebGLActiveInfo* activeInfo)
+    : mActiveInfo(activeInfo)
+    , mSamplerTexList(GetTexList(activeInfo))
 {
-    RefPtr<webgl::UniformBlockInfo> info = new webgl::UniformBlockInfo(baseUserName, baseMappedName);
+    if (mSamplerTexList) {
+        mSamplerValues.assign(mActiveInfo->mElemCount, 0);
+    }
+}
 
-    activeInfoList->push_back(info);
-}
+//////////
 
 //#define DUMP_SHADERVAR_MAPPINGS
 
 static already_AddRefed<const webgl::LinkedProgramInfo>
 QueryProgramInfo(WebGLProgram* prog, gl::GLContext* gl)
 {
+    WebGLContext* const webgl = prog->mContext;
+
     RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog));
 
     GLuint maxAttribLenWithNull = 0;
     gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
                       (GLint*)&maxAttribLenWithNull);
     if (maxAttribLenWithNull < 1)
         maxAttribLenWithNull = 1;
 
@@ -149,43 +173,47 @@ QueryProgramInfo(WebGLProgram* prog, gl:
         GLsizei lengthWithoutNull = 0;
         GLint elemCount = 0; // `size`
         GLenum elemType = 0; // `type`
         gl->fGetActiveAttrib(prog->mGLName, i, mappedName.Length()+1, &lengthWithoutNull,
                              &elemCount, &elemType, mappedName.BeginWriting());
 
         mappedName.SetLength(lengthWithoutNull);
 
-        // Collect ActiveInfos:
-
         // Attribs can't be arrays, so we can skip some of the mess we have in the Uniform
         // path.
         nsDependentCString userName;
         if (!prog->FindAttribUserNameByMappedName(mappedName, &userName))
             userName.Rebind(mappedName, 0);
 
+        ///////
+
+        const GLint loc = gl->fGetAttribLocation(prog->mGLName,
+                                                 mappedName.BeginReading());
+        if (loc == -1) {
+            MOZ_ASSERT(mappedName == "gl_InstanceID",
+                       "Active attrib should have a location.");
+            continue;
+        }
+
 #ifdef DUMP_SHADERVAR_MAPPINGS
-        printf_stderr("[attrib %i] %s/%s\n", i, mappedName.BeginReading(),
+        printf_stderr("[attrib %i: %i] %s/%s\n", i, loc, mappedName.BeginReading(),
                       userName.BeginReading());
         printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
 #endif
 
-        const bool isArray = false;
-        const auto attrib = AddActiveInfo(prog->mContext, elemCount, elemType, isArray,
-                                          userName, mappedName, &info->activeAttribs,
-                                          &info->attribMap);
+        ///////
 
-        // Collect active locations:
-        GLint loc = gl->fGetAttribLocation(prog->mGLName, mappedName.BeginReading());
-        if (loc == -1) {
-            if (mappedName != "gl_InstanceID")
-                MOZ_CRASH("GFX: Active attrib has no location.");
-        } else {
-            info->activeAttribLocs.insert({attrib, (GLuint)loc});
-        }
+        const bool isArray = false;
+        const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(webgl, elemCount,
+                                                                       elemType, isArray,
+                                                                       userName,
+                                                                       mappedName);
+        const webgl::AttribInfo attrib = {activeInfo, uint32_t(loc)};
+        info->attribs.push_back(attrib);
     }
 
     // Uniforms
 
     const bool needsCheckForArrays = gl->WorkAroundDriverBugs();
 
     GLuint numActiveUniforms = 0;
     gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORMS,
@@ -198,55 +226,74 @@ QueryProgramInfo(WebGLProgram* prog, gl:
         GLsizei lengthWithoutNull = 0;
         GLint elemCount = 0; // `size`
         GLenum elemType = 0; // `type`
         gl->fGetActiveUniform(prog->mGLName, i, mappedName.Length()+1, &lengthWithoutNull,
                               &elemCount, &elemType, mappedName.BeginWriting());
 
         mappedName.SetLength(lengthWithoutNull);
 
+        ///////
+
         nsAutoCString baseMappedName;
         bool isArray;
         size_t arrayIndex;
         if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
             MOZ_CRASH("GFX: Failed to parse `mappedName` received from driver.");
 
         // Note that for good drivers, `isArray` should already be correct.
         // However, if FindUniform succeeds, it will be validator-guaranteed correct.
 
+        ///////
+
         nsAutoCString baseUserName;
         if (!prog->FindUniformByMappedName(baseMappedName, &baseUserName, &isArray)) {
+            // Validator likely missing.
             baseUserName = baseMappedName;
 
             if (needsCheckForArrays && !isArray) {
                 // By GLES 3, GetUniformLocation("foo[0]") should return -1 if `foo` is
                 // not an array. Our current linux Try slaves return the location of `foo`
                 // anyways, though.
                 std::string mappedNameStr = baseMappedName.BeginReading();
                 mappedNameStr += "[0]";
 
                 GLint loc = gl->fGetUniformLocation(prog->mGLName, mappedNameStr.c_str());
                 if (loc != -1)
                     isArray = true;
             }
         }
 
+        ///////
+
 #ifdef DUMP_SHADERVAR_MAPPINGS
         printf_stderr("[uniform %i] %s/%i/%s/%s\n", i, mappedName.BeginReading(),
                       (int)isArray, baseMappedName.BeginReading(),
                       baseUserName.BeginReading());
         printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
         printf_stderr("    isArray: %d\n", (int)isArray);
 #endif
 
-        AddActiveInfo(prog->mContext, elemCount, elemType, isArray, baseUserName,
-                      baseMappedName, &info->activeUniforms, &info->uniformMap);
+        ///////
+
+        const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(webgl, elemCount,
+                                                                       elemType, isArray,
+                                                                       baseUserName,
+                                                                       baseMappedName);
+
+        auto* uniform = new webgl::UniformInfo(activeInfo);
+        info->uniforms.push_back(uniform);
+
+        if (uniform->mSamplerTexList) {
+            info->uniformSamplers.push_back(uniform);
+        }
     }
 
     // Uniform Blocks
+    // (no sampler types allowed!)
 
     if (gl->IsSupported(gl::GLFeature::uniform_buffer_object)) {
         GLuint numActiveUniformBlocks = 0;
         gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORM_BLOCKS,
                           (GLint*)&numActiveUniformBlocks);
 
         for (GLuint i = 0; i < numActiveUniformBlocks; i++) {
             nsAutoCString mappedName;
@@ -276,82 +323,103 @@ QueryProgramInfo(WebGLProgram* prog, gl:
                     GLuint loc = gl->fGetUniformBlockIndex(prog->mGLName,
                                                            mappedNameStr.c_str());
                     if (loc != LOCAL_GL_INVALID_INDEX)
                         isArray = true;
                 }
             }
 
 #ifdef DUMP_SHADERVAR_MAPPINGS
-            printf_stderr("[uniform block %i] %s/%i/%s/%s\n", i, mappedName.BeginReading(),
-                          (int)isArray, baseMappedName.BeginReading(),
-                          baseUserName.BeginReading());
+            printf_stderr("[uniform block %i] %s/%i/%s/%s\n", i,
+                          mappedName.BeginReading(), (int)isArray,
+                          baseMappedName.BeginReading(), baseUserName.BeginReading());
             printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
             printf_stderr("    isArray: %d\n", (int)isArray);
 #endif
 
-            AddActiveBlockInfo(baseUserName, baseMappedName, &info->uniformBlocks);
+            const auto* block = new webgl::UniformBlockInfo(baseUserName, baseMappedName);
+            info->uniformBlocks.push_back(block);
         }
     }
 
     // Transform feedback varyings
 
     if (gl->IsSupported(gl::GLFeature::transform_feedback2)) {
         GLuint numTransformFeedbackVaryings = 0;
         gl->fGetProgramiv(prog->mGLName, LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS,
                           (GLint*)&numTransformFeedbackVaryings);
 
         for (GLuint i = 0; i < numTransformFeedbackVaryings; i++) {
             nsAutoCString mappedName;
             mappedName.SetLength(maxTransformFeedbackVaryingLenWithNull - 1);
 
             GLint lengthWithoutNull;
-            GLsizei size;
-            GLenum type;
-            gl->fGetTransformFeedbackVarying(prog->mGLName, i, maxTransformFeedbackVaryingLenWithNull,
-                                             &lengthWithoutNull, &size, &type,
+            GLsizei elemCount;
+            GLenum elemType;
+            gl->fGetTransformFeedbackVarying(prog->mGLName, i,
+                                             maxTransformFeedbackVaryingLenWithNull,
+                                             &lengthWithoutNull, &elemCount, &elemType,
                                              mappedName.BeginWriting());
             mappedName.SetLength(lengthWithoutNull);
 
+            ////
+
             nsAutoCString baseMappedName;
             bool isArray;
             size_t arrayIndex;
             if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
                 MOZ_CRASH("GFX: Failed to parse `mappedName` received from driver.");
 
+
             nsAutoCString baseUserName;
             if (!prog->FindVaryingByMappedName(mappedName, &baseUserName, &isArray)) {
                 baseUserName = baseMappedName;
 
                 if (needsCheckForArrays && !isArray) {
                     std::string mappedNameStr = baseMappedName.BeginReading();
                     mappedNameStr += "[0]";
 
                     GLuint loc = gl->fGetUniformBlockIndex(prog->mGLName,
                                                            mappedNameStr.c_str());
                     if (loc != LOCAL_GL_INVALID_INDEX)
                         isArray = true;
                 }
             }
 
-            AddActiveInfo(prog->mContext, size, type, isArray, baseUserName, mappedName,
-                          &info->transformFeedbackVaryings,
-                          &info->transformFeedbackVaryingsMap);
+            ////
+
+            const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(webgl,
+                                                                           elemCount,
+                                                                           elemType,
+                                                                           isArray,
+                                                                           baseUserName,
+                                                                           mappedName);
+            info->transformFeedbackVaryings.push_back(activeInfo);
         }
     }
 
     return info.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
     : prog(prog)
 { }
 
+webgl::LinkedProgramInfo::~LinkedProgramInfo()
+{
+    for (auto& cur : uniforms) {
+        delete cur;
+    }
+    for (auto& cur : uniformBlocks) {
+        delete cur;
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLProgram
 
 static GLuint
 CreateProgram(gl::GLContext* gl)
 {
     gl->MakeCurrent();
     return gl->fCreateProgram();
@@ -482,46 +550,46 @@ WebGLProgram::DetachShader(WebGLShader* 
 already_AddRefed<WebGLActiveInfo>
 WebGLProgram::GetActiveAttrib(GLuint index) const
 {
     if (!mMostRecentLinkInfo) {
         RefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
         return ret.forget();
     }
 
-    const auto& activeList = mMostRecentLinkInfo->activeAttribs;
+    const auto& attribs = mMostRecentLinkInfo->attribs;
 
-    if (index >= activeList.size()) {
+    if (index >= attribs.size()) {
         mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%i).",
-                                    index, "ACTIVE_ATTRIBS", activeList.size());
+                                    index, "ACTIVE_ATTRIBS", attribs.size());
         return nullptr;
     }
 
-    RefPtr<WebGLActiveInfo> ret = activeList[index];
+    RefPtr<WebGLActiveInfo> ret = attribs[index].mActiveInfo;
     return ret.forget();
 }
 
 already_AddRefed<WebGLActiveInfo>
 WebGLProgram::GetActiveUniform(GLuint index) const
 {
     if (!mMostRecentLinkInfo) {
         // According to the spec, this can return null.
         RefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
         return ret.forget();
     }
 
-    const auto& activeList = mMostRecentLinkInfo->activeUniforms;
+    const auto& uniforms = mMostRecentLinkInfo->uniforms;
 
-    if (index >= activeList.size()) {
+    if (index >= uniforms.size()) {
         mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%i).",
-                                    index, "ACTIVE_UNIFORMS", activeList.size());
+                                    index, "ACTIVE_UNIFORMS", uniforms.size());
         return nullptr;
     }
 
-    RefPtr<WebGLActiveInfo> ret = activeList[index];
+    RefPtr<WebGLActiveInfo> ret = uniforms[index]->mActiveInfo;
     return ret.forget();
 }
 
 void
 WebGLProgram::GetAttachedShaders(nsTArray<RefPtr<WebGLShader>>* const out) const
 {
     out->TruncateLength(0);
 
@@ -540,26 +608,21 @@ WebGLProgram::GetAttribLocation(const ns
 
     if (!IsLinked()) {
         mContext->ErrorInvalidOperation("getAttribLocation: `program` must be linked.");
         return -1;
     }
 
     const NS_LossyConvertUTF16toASCII userName(userName_wide);
 
-    const WebGLActiveInfo* info;
+    const webgl::AttribInfo* info;
     if (!LinkInfo()->FindAttrib(userName, &info))
         return -1;
 
-    const nsCString& mappedName = info->mBaseMappedName;
-
-    gl::GLContext* gl = mContext->GL();
-    gl->MakeCurrent();
-
-    return gl->fGetAttribLocation(mGLName, mappedName.BeginReading());
+    return GLint(info->mLoc);
 }
 
 GLint
 WebGLProgram::GetFragDataLocation(const nsAString& userName_wide) const
 {
     if (!ValidateGLSLVariableName(userName_wide, mContext, "getFragDataLocation"))
         return -1;
 
@@ -653,23 +716,22 @@ WebGLProgram::GetUniformBlockIndex(const
     const NS_LossyConvertUTF16toASCII userName(userName_wide);
 
     nsDependentCString baseUserName;
     bool isArray;
     size_t arrayIndex;
     if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
         return LOCAL_GL_INVALID_INDEX;
 
-    RefPtr<const webgl::UniformBlockInfo> info;
+    const webgl::UniformBlockInfo* info;
     if (!LinkInfo()->FindUniformBlock(baseUserName, &info)) {
         return LOCAL_GL_INVALID_INDEX;
     }
 
-    const nsCString& baseMappedName = info->mBaseMappedName;
-    nsAutoCString mappedName(baseMappedName);
+    nsAutoCString mappedName(info->mBaseMappedName);
     if (isArray) {
         mappedName.AppendLiteral("[");
         mappedName.AppendInt(uint32_t(arrayIndex));
         mappedName.AppendLiteral("]");
     }
 
     gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
@@ -794,39 +856,36 @@ WebGLProgram::GetUniformLocation(const n
     // element of that array can be retrieved by either using the name of the
     // uniform array, or the name of the uniform array appended with "[0]".
     // The ParseName() can't recognize this rule. So always initialize
     // arrayIndex with 0.
     size_t arrayIndex = 0;
     if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
         return nullptr;
 
-    const WebGLActiveInfo* activeInfo;
-    if (!LinkInfo()->FindUniform(baseUserName, &activeInfo))
+    webgl::UniformInfo* info;
+    if (!LinkInfo()->FindUniform(baseUserName, &info))
         return nullptr;
 
-    const nsCString& baseMappedName = activeInfo->mBaseMappedName;
-
-    nsAutoCString mappedName(baseMappedName);
+    nsAutoCString mappedName(info->mActiveInfo->mBaseMappedName);
     if (isArray) {
         mappedName.AppendLiteral("[");
         mappedName.AppendInt(uint32_t(arrayIndex));
         mappedName.AppendLiteral("]");
     }
 
     gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
 
     GLint loc = gl->fGetUniformLocation(mGLName, mappedName.BeginReading());
     if (loc == -1)
         return nullptr;
 
     RefPtr<WebGLUniformLocation> locObj = new WebGLUniformLocation(mContext, LinkInfo(),
-                                                                   loc, arrayIndex,
-                                                                   activeInfo);
+                                                                   info, loc, arrayIndex);
     return locObj.forget();
 }
 
 void
 WebGLProgram::GetUniformIndices(const dom::Sequence<nsString>& uniformNames,
                                 dom::Nullable< nsTArray<GLuint> >& retval) const
 {
     size_t count = uniformNames.Length();
@@ -841,25 +900,23 @@ WebGLProgram::GetUniformIndices(const do
         nsDependentCString baseUserName;
         bool isArray;
         size_t arrayIndex;
         if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex)) {
             arr.AppendElement(LOCAL_GL_INVALID_INDEX);
             continue;
         }
 
-        const WebGLActiveInfo* activeInfo;
-        if (!LinkInfo()->FindUniform(baseUserName, &activeInfo)) {
+        webgl::UniformInfo* info;
+        if (!LinkInfo()->FindUniform(baseUserName, &info)) {
             arr.AppendElement(LOCAL_GL_INVALID_INDEX);
             continue;
         }
 
-        const nsCString& baseMappedName = activeInfo->mBaseMappedName;
-
-        nsAutoCString mappedName(baseMappedName);
+        nsAutoCString mappedName(info->mActiveInfo->mBaseMappedName);
         if (isArray) {
             mappedName.AppendLiteral("[");
             mappedName.AppendInt(uint32_t(arrayIndex));
             mappedName.AppendLiteral("]");
         }
 
         const GLchar* mappedNameBytes = mappedName.BeginReading();
 
@@ -874,18 +931,17 @@ void
 WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const
 {
     if (!IsLinked()) {
         mContext->ErrorInvalidOperation("getActiveUniformBlockName: `program` must be linked.");
         return;
     }
 
     const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
-    GLuint uniformBlockCount = (GLuint)linkInfo->uniformBlocks.size();
-    if (uniformBlockIndex >= uniformBlockCount) {
+    if (uniformBlockIndex >= linkInfo->uniformBlocks.size()) {
         mContext->ErrorInvalidValue("getActiveUniformBlockName: index %u invalid.", uniformBlockIndex);
         return;
     }
 
     if (uniformBlockBinding > mContext->mGLMaxUniformBufferBindings) {
         mContext->ErrorInvalidEnum("getActiveUniformBlockName: binding %u invalid.", uniformBlockBinding);
         return;
     }
@@ -985,59 +1041,80 @@ WebGLProgram::LinkProgram()
         if (!mLinkLog.IsEmpty()) {
             mContext->GenerateWarning("linkProgram: Failed to link, leaving the following"
                                       " log:\n%s\n",
                                       mLinkLog.BeginReading());
         }
     }
 }
 
+static uint8_t
+NumUsedLocationsByElemType(GLenum elemType)
+{
+    // GLES 3.0.4 p55
+
+    switch (elemType) {
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_MAT2x3:
+    case LOCAL_GL_FLOAT_MAT2x4:
+        return 2;
+
+    case LOCAL_GL_FLOAT_MAT3x2:
+    case LOCAL_GL_FLOAT_MAT3:
+    case LOCAL_GL_FLOAT_MAT3x4:
+        return 3;
+
+    case LOCAL_GL_FLOAT_MAT4x2:
+    case LOCAL_GL_FLOAT_MAT4x3:
+    case LOCAL_GL_FLOAT_MAT4:
+        return 4;
+
+    default:
+        return 1;
+    }
+}
+
 bool
 WebGLProgram::ValidateAfterTentativeLink(nsCString* const out_linkLog) const
 {
     const auto& linkInfo = mMostRecentLinkInfo;
 
     // Check if the attrib name conflicting to uniform name
-    for (const auto& uniform : linkInfo->uniformMap) {
-        if (linkInfo->attribMap.find(uniform.first) != linkInfo->attribMap.end()) {
-            *out_linkLog = nsPrintfCString("The uniform name (%s) conflicts with"
-                                           " attribute name.",
-                                           uniform.first.get());
-            return false;
+    for (const auto& attrib : linkInfo->attribs) {
+        const auto& attribName = attrib.mActiveInfo->mBaseUserName;
+
+        for (const auto& uniform : linkInfo->uniforms) {
+            const auto& uniformName = uniform->mActiveInfo->mBaseUserName;
+            if (attribName == uniformName) {
+                *out_linkLog = nsPrintfCString("Attrib name conflicts with uniform name:"
+                                               " %s",
+                                               attribName.BeginReading());
+                return false;
+            }
         }
     }
 
-    std::map<GLuint, const WebGLActiveInfo*> attribsByLoc;
-    for (const auto& pair : linkInfo->activeAttribLocs) {
-        const auto dupe = attribsByLoc.find(pair.second);
-        if (dupe != attribsByLoc.end()) {
-            *out_linkLog = nsPrintfCString("Aliased location between active attribs"
-                                           " \"%s\" and \"%s\".",
-                                           dupe->second->mBaseUserName.BeginReading(),
-                                           pair.first->mBaseUserName.BeginReading());
-            return false;
-        }
-    }
+    std::map<uint32_t, const webgl::AttribInfo*> attribsByLoc;
+    for (const auto& attrib : linkInfo->attribs) {
+        const auto& elemType = attrib.mActiveInfo->mElemType;
+        const auto numUsedLocs = NumUsedLocationsByElemType(elemType);
+        for (uint32_t i = 0; i < numUsedLocs; i++) {
+            const uint32_t usedLoc = attrib.mLoc + i;
 
-    for (const auto& pair : attribsByLoc) {
-        const GLuint attribLoc = pair.first;
-        const auto attrib = pair.second;
-
-        const auto elemSize = ElemSizeFromType(attrib->mElemType);
-        const GLuint locationsUsed = (elemSize + 3) / 4;
-        for (GLuint i = 1; i < locationsUsed; i++) {
-            const GLuint usedLoc = attribLoc + i;
-
-            const auto dupe = attribsByLoc.find(usedLoc);
-            if (dupe != attribsByLoc.end()) {
-                *out_linkLog = nsPrintfCString("Attrib \"%s\" of type \"0x%04x\" aliases"
-                                               " \"%s\" by overhanging its location.",
-                                               attrib->mBaseUserName.BeginReading(),
-                                               attrib->mElemType,
-                                               dupe->second->mBaseUserName.BeginReading());
+            const auto res = attribsByLoc.insert({usedLoc, &attrib});
+            const bool& didInsert = res.second;
+            if (!didInsert) {
+                const auto& aliasingName = attrib.mActiveInfo->mBaseUserName;
+                const auto& itrExisting = res.first;
+                const auto& existingInfo = itrExisting->second;
+                const auto& existingName = existingInfo->mActiveInfo->mBaseUserName;
+                *out_linkLog = nsPrintfCString("Attrib \"%s\" aliases locations used by"
+                                               " attrib \"%s\".",
+                                               aliasingName.BeginReading(),
+                                               existingName.BeginReading());
                 return false;
             }
         }
     }
 
     return true;
 }
 
@@ -1229,16 +1306,60 @@ WebGLProgram::FindUniformBlockByMappedNa
     if (mFragShader->FindUniformBlockByMappedName(mappedName, out_userName, out_isArray))
         return true;
 
     return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
+bool
+webgl::LinkedProgramInfo::FindAttrib(const nsCString& baseUserName,
+                                     const webgl::AttribInfo** const out) const
+{
+    for (const auto& attrib : attribs) {
+        if (attrib.mActiveInfo->mBaseUserName == baseUserName) {
+            *out = &attrib;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+webgl::LinkedProgramInfo::FindUniform(const nsCString& baseUserName,
+                                      webgl::UniformInfo** const out) const
+{
+    for (const auto& uniform : uniforms) {
+        if (uniform->mActiveInfo->mBaseUserName == baseUserName) {
+            *out = uniform;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+webgl::LinkedProgramInfo::FindUniformBlock(const nsCString& baseUserName,
+                                           const webgl::UniformBlockInfo** const out) const
+{
+    for (const auto& block : uniformBlocks) {
+        if (block->mBaseUserName == baseUserName) {
+            *out = block;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 JSObject*
 WebGLProgram::WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLProgramBinding::Wrap(js, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mVertShader, mFragShader)
 
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -30,21 +30,40 @@ class WebGLUniformLocation;
 namespace dom {
 template<typename> struct Nullable;
 class OwningUnsignedLongOrUint32ArrayOrBoolean;
 template<typename> class Sequence;
 } // namespace dom
 
 namespace webgl {
 
-struct UniformBlockInfo final
-    : public RefCounted<UniformBlockInfo>
+struct AttribInfo final
+{
+    const RefPtr<WebGLActiveInfo> mActiveInfo;
+    uint32_t mLoc;
+};
+
+struct UniformInfo final
 {
-    MOZ_DECLARE_REFCOUNTED_TYPENAME(UniformBlockInfo);
+    typedef decltype(WebGLContext::mBound2DTextures) TexListT;
+
+    const RefPtr<WebGLActiveInfo> mActiveInfo;
+    const TexListT* const mSamplerTexList;
+    std::vector<uint32_t> mSamplerValues;
 
+protected:
+    static const TexListT*
+    GetTexList(WebGLActiveInfo* activeInfo);
+
+public:
+    explicit UniformInfo(WebGLActiveInfo* activeInfo);
+};
+
+struct UniformBlockInfo final
+{
     const nsCString mBaseUserName;
     const nsCString mBaseMappedName;
 
     UniformBlockInfo(const nsACString& baseUserName,
                      const nsACString& baseMappedName)
         : mBaseUserName(baseUserName)
         , mBaseMappedName(baseMappedName)
     {}
@@ -52,71 +71,37 @@ struct UniformBlockInfo final
 
 struct LinkedProgramInfo final
     : public RefCounted<LinkedProgramInfo>
     , public SupportsWeakPtr<LinkedProgramInfo>
 {
     MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(LinkedProgramInfo)
 
+    //////
+
     WebGLProgram* const prog;
-    std::vector<RefPtr<WebGLActiveInfo>> activeAttribs;
-    std::vector<RefPtr<WebGLActiveInfo>> activeUniforms;
+
+    std::vector<AttribInfo> attribs;
+    std::vector<UniformInfo*> uniforms; // Owns its contents.
+    std::vector<const UniformBlockInfo*> uniformBlocks; // Owns its contents.
     std::vector<RefPtr<WebGLActiveInfo>> transformFeedbackVaryings;
 
-    // Needed for Get{Attrib,Uniform}Location. The keys for these are non-mapped
-    // user-facing `GLActiveInfo::name`s, without any final "[0]".
-    std::map<nsCString, const WebGLActiveInfo*> attribMap;
-    std::map<nsCString, const WebGLActiveInfo*> uniformMap;
-    std::map<nsCString, const WebGLActiveInfo*> transformFeedbackVaryingsMap;
-
-    std::vector<RefPtr<UniformBlockInfo>> uniformBlocks;
-
     // Needed for draw call validation.
-    std::map<const WebGLActiveInfo*, GLuint> activeAttribLocs;
+    std::vector<UniformInfo*> uniformSamplers;
 
     //////
 
     explicit LinkedProgramInfo(WebGLProgram* prog);
-
-    bool FindAttrib(const nsCString& baseUserName,
-                    const WebGLActiveInfo** const out_activeInfo) const
-    {
-        auto itr = attribMap.find(baseUserName);
-        if (itr == attribMap.end())
-            return false;
-
-        *out_activeInfo = itr->second;
-        return true;
-    }
+    ~LinkedProgramInfo();
 
-    bool FindUniform(const nsCString& baseUserName,
-                     const WebGLActiveInfo** const out_activeInfo) const
-    {
-        auto itr = uniformMap.find(baseUserName);
-        if (itr == uniformMap.end())
-            return false;
-
-        *out_activeInfo = itr->second;
-        return true;
-    }
-
+    bool FindAttrib(const nsCString& baseUserName, const AttribInfo** const out) const;
+    bool FindUniform(const nsCString& baseUserName, UniformInfo** const out) const;
     bool FindUniformBlock(const nsCString& baseUserName,
-                          RefPtr<const UniformBlockInfo>* const out_info) const
-    {
-        const size_t count = uniformBlocks.size();
-        for (size_t i = 0; i < count; i++) {
-            if (baseUserName == uniformBlocks[i]->mBaseUserName) {
-                *out_info = uniformBlocks[i].get();
-                return true;
-            }
-        }
-
-        return false;
-    }
+                          const UniformBlockInfo** const out) const;
 };
 
 } // namespace webgl
 
 class WebGLProgram final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLProgram>
     , public LinkedListElement<WebGLProgram>
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -190,26 +190,26 @@ WebGLTexture::SetImageInfosAtLevel(uint3
 }
 
 bool
 WebGLTexture::IsMipmapComplete(uint32_t texUnit) const
 {
     MOZ_ASSERT(DoesMinFilterRequireMipmap());
     // GLES 3.0.4, p161
 
-    const uint32_t maxLevel = MaxEffectiveMipmapLevel(texUnit);
+    uint32_t maxLevel;
+    if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel))
+        return false;
 
     // "* `level_base <= level_max`"
     if (mBaseMipmapLevel > maxLevel)
         return false;
 
     // Make a copy so we can modify it.
     const ImageInfo& baseImageInfo = BaseImageInfo();
-    if (!baseImageInfo.IsDefined())
-        return false;
 
     // Reference dimensions based on the current level.
     uint32_t refWidth = baseImageInfo.mWidth;
     uint32_t refHeight = baseImageInfo.mHeight;
     uint32_t refDepth = baseImageInfo.mDepth;
     MOZ_ASSERT(refWidth && refHeight && refDepth);
 
     for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) {
@@ -420,34 +420,36 @@ WebGLTexture::IsComplete(uint32_t texUni
         //    image is not cube complete, or TEXTURE_MIN_FILTER is one that requires a
         //    mipmap and the texture is not mipmap cube complete."
         // (already covered)
     }
 
     return true;
 }
 
-
-uint32_t
-WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit) const
+bool
+WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const
 {
     WebGLSampler* sampler = mContext->mBoundSamplers[texUnit];
     TexMinFilter minFilter = sampler ? sampler->mMinFilter : mMinFilter;
     if (minFilter == LOCAL_GL_NEAREST ||
         minFilter == LOCAL_GL_LINEAR)
     {
-        // No mips used.
-        return mBaseMipmapLevel;
+        // No extra mips used.
+        *out = mBaseMipmapLevel;
+        return true;
     }
 
     const auto& imageInfo = BaseImageInfo();
-    MOZ_ASSERT(imageInfo.IsDefined());
+    if (!imageInfo.IsDefined())
+        return false;
 
-    uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.MaxMipmapLevels() - 1;
-    return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
+    uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.PossibleMipmapLevels() - 1;
+    *out = std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
+    return true;
 }
 
 bool
 WebGLTexture::GetFakeBlackType(const char* funcName, uint32_t texUnit,
                                FakeBlackType* const out_fakeBlack)
 {
     const char* incompleteReason;
     if (!IsComplete(texUnit, &incompleteReason)) {
@@ -461,17 +463,19 @@ WebGLTexture::GetFakeBlackType(const cha
         *out_fakeBlack = FakeBlackType::RGBA0001;
         return true;
     }
 
     // We may still want FakeBlack as an optimization for uninitialized image data.
     bool hasUninitializedData = false;
     bool hasInitializedData = false;
 
-    const auto maxLevel = MaxEffectiveMipmapLevel(texUnit);
+    uint32_t maxLevel;
+    MOZ_ALWAYS_TRUE( MaxEffectiveMipmapLevel(texUnit, &maxLevel) );
+
     MOZ_ASSERT(mBaseMipmapLevel <= maxLevel);
     for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) {
         for (uint8_t face = 0; face < mFaceCount; face++) {
             const auto& cur = ImageInfoAtFace(face, level);
             if (cur.IsDataInitialized())
                 hasInitializedData = true;
             else
                 hasUninitializedData = true;
@@ -785,18 +789,18 @@ WebGLTexture::GenerateMipmap(TexTarget t
                            mMinFilter.get());
     } else {
         gl->fGenerateMipmap(texTarget.get());
     }
 
     // Record the results.
     // Note that we don't use MaxEffectiveMipmapLevel() here, since that returns
     // mBaseMipmapLevel if the min filter doesn't require mipmaps.
-    const uint32_t lastLevel = mBaseMipmapLevel + baseImageInfo.MaxMipmapLevels() - 1;
-    PopulateMipChain(mBaseMipmapLevel, lastLevel);
+    const uint32_t maxLevel = mBaseMipmapLevel + baseImageInfo.PossibleMipmapLevels() - 1;
+    PopulateMipChain(mBaseMipmapLevel, maxLevel);
 }
 
 JS::Value
 WebGLTexture::GetTexParameter(TexTarget texTarget, GLenum pname)
 {
     mContext->MakeContextCurrent();
 
     GLint i = 0;
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -149,19 +149,20 @@ public:
             if (!IsDefined())
                 Clear();
         }
 
     protected:
         ImageInfo& operator =(const ImageInfo& a);
 
     public:
-        uint32_t MaxMipmapLevels() const {
+        uint32_t PossibleMipmapLevels() const {
             // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
-            uint32_t largest = std::max(std::max(mWidth, mHeight), mDepth);
+            const uint32_t largest = std::max(std::max(mWidth, mHeight), mDepth);
+            MOZ_ASSERT(largest != 0);
             return FloorLog2Size(largest) + 1;
         }
 
         bool IsPowerOfTwo() const;
 
         void AddAttachPoint(WebGLFBAttachPoint* attachPoint);
         void RemoveAttachPoint(WebGLFBAttachPoint* attachPoint);
         void OnRespecify() const;
@@ -283,17 +284,17 @@ public:
 
     ////////////////////////////////////
 
 protected:
     void ClampLevelBaseAndMax();
 
     void PopulateMipChain(uint32_t baseLevel, uint32_t maxLevel);
 
-    uint32_t MaxEffectiveMipmapLevel(uint32_t texUnit) const;
+    bool MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const;
 
     static uint8_t FaceForTarget(TexImageTarget texImageTarget) {
         GLenum rawTexImageTarget = texImageTarget.get();
         switch (rawTexImageTarget) {
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
@@ -383,16 +384,19 @@ public:
 
     bool IsCubeMap() const { return (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP); }
 
     // Resolve cache optimizations
 protected:
     bool GetFakeBlackType(const char* funcName, uint32_t texUnit,
                           FakeBlackType* const out_fakeBlack);
 public:
+    bool IsFeedback(WebGLContext* webgl, const char* funcName, uint32_t texUnit,
+                    const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const;
+
     bool ResolveForDraw(const char* funcName, uint32_t texUnit,
                         FakeBlackType* const out_fakeBlack);
 
     void InvalidateResolveCache() { mIsResolved = false; }
 };
 
 inline TexImageTarget
 TexImageTargetForTargetAndFace(TexTarget target, uint8_t face)
--- a/dom/canvas/WebGLUniformLocation.cpp
+++ b/dom/canvas/WebGLUniformLocation.cpp
@@ -11,44 +11,42 @@
 #include "WebGLActiveInfo.h"
 #include "WebGLContext.h"
 #include "WebGLProgram.h"
 
 namespace mozilla {
 
 WebGLUniformLocation::WebGLUniformLocation(WebGLContext* webgl,
                                            const webgl::LinkedProgramInfo* linkInfo,
-                                           GLuint loc,
-                                           size_t arrayIndex,
-                                           const WebGLActiveInfo* activeInfo)
+                                           webgl::UniformInfo* info, GLuint loc,
+                                           size_t arrayIndex)
     : WebGLContextBoundObject(webgl)
     , mLinkInfo(linkInfo)
+    , mInfo(info)
     , mLoc(loc)
     , mArrayIndex(arrayIndex)
-    , mActiveInfo(activeInfo)
 { }
 
 WebGLUniformLocation::~WebGLUniformLocation()
 { }
 
 bool
-WebGLUniformLocation::ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
-                                         const char* funcName) const
+WebGLUniformLocation::ValidateForProgram(WebGLProgram* prog, const char* funcName) const
 {
     // Check the weak-pointer.
     if (!mLinkInfo) {
-        webgl->ErrorInvalidOperation("%s: This uniform location is obsolete because its"
-                                     " program has been successfully relinked.",
-                                     funcName);
+        mContext->ErrorInvalidOperation("%s: This uniform location is obsolete because"
+                                        " its program has been successfully relinked.",
+                                        funcName);
         return false;
     }
 
     if (mLinkInfo->prog != prog) {
-        webgl->ErrorInvalidOperation("%s: This uniform location corresponds to a"
-                                     " different program.", funcName);
+        mContext->ErrorInvalidOperation("%s: This uniform location corresponds to a"
+                                        " different program.", funcName);
         return false;
     }
 
     return true;
 }
 
 static bool
 IsUniformSetterTypeValid(GLenum setterType, GLenum uniformType)
@@ -116,106 +114,88 @@ IsUniformSetterTypeValid(GLenum setterTy
 
     default:
         MOZ_CRASH("GFX: Bad `uniformType`.");
     }
 }
 
 bool
 WebGLUniformLocation::ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType,
-                                          WebGLContext* webgl, const char* funcName) const
+                                          const char* funcName) const
 {
     MOZ_ASSERT(mLinkInfo);
 
-    if (setterElemSize != mActiveInfo->mElemSize) {
-        webgl->ErrorInvalidOperation("%s: Bad uniform size: %i", funcName,
-                                     mActiveInfo->mElemSize);
+    const auto& uniformElemSize = mInfo->mActiveInfo->mElemSize;
+    if (setterElemSize != uniformElemSize) {
+        mContext->ErrorInvalidOperation("%s: Function used differs from uniform size: %i",
+                                        funcName, uniformElemSize);
         return false;
     }
 
-    if (!IsUniformSetterTypeValid(setterType, mActiveInfo->mElemType)) {
-        webgl->ErrorInvalidOperation("%s: Bad uniform type: %i", funcName,
-                                     mActiveInfo->mElemSize);
+    const auto& uniformElemType = mInfo->mActiveInfo->mElemType;
+    if (!IsUniformSetterTypeValid(setterType, uniformElemType)) {
+        mContext->ErrorInvalidOperation("%s: Function used is incompatible with uniform"
+                                        " type: %i",
+                                        funcName, uniformElemType);
         return false;
     }
 
     return true;
 }
 
 bool
 WebGLUniformLocation::ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize,
-                                          WebGLContext* webgl, const char* funcName) const
+                                          const char* funcName) const
 {
     MOZ_ASSERT(mLinkInfo);
 
     if (setterArraySize == 0 ||
         setterArraySize % setterElemSize)
     {
-        webgl->ErrorInvalidValue("%s: expected an array of length a multiple of"
-                                 " %d, got an array of length %d.",
-                                 funcName, setterElemSize, setterArraySize);
+        mContext->ErrorInvalidValue("%s: Expected an array of length a multiple of %d,"
+                                    " got an array of length %d.",
+                                    funcName, setterElemSize, setterArraySize);
         return false;
     }
 
     /* GLES 2.0.25, Section 2.10, p38
      *   When loading `N` elements starting at an arbitrary position `k` in a uniform
      *   declared as an array, elements `k` through `k + N - 1` in the array will be
      *   replaced with the new values. Values for any array element that exceeds the
      *   highest array element index used, as reported by `GetActiveUniform`, will be
      *   ignored by GL.
      */
-    if (!mActiveInfo->mIsArray &&
+    if (!mInfo->mActiveInfo->mIsArray &&
         setterArraySize != setterElemSize)
     {
-        webgl->ErrorInvalidOperation("%s: expected an array of length exactly %d"
-                                     " (since this uniform is not an array"
-                                     " uniform), got an array of length %d.",
-                                     funcName, setterElemSize, setterArraySize);
+        mContext->ErrorInvalidOperation("%s: Expected an array of length exactly %d"
+                                        " (since this uniform is not an array uniform),"
+                                        " got an array of length %d.",
+                                        funcName, setterElemSize, setterArraySize);
         return false;
     }
 
     return true;
 }
 
-bool
-WebGLUniformLocation::ValidateSamplerSetter(GLint value, WebGLContext* webgl,
-                                            const char* funcName) const
+JS::Value
+WebGLUniformLocation::GetUniform(JSContext* js) const
 {
     MOZ_ASSERT(mLinkInfo);
 
-    if (mActiveInfo->mElemType != LOCAL_GL_SAMPLER_2D &&
-        mActiveInfo->mElemType != LOCAL_GL_SAMPLER_CUBE)
-    {
-        return true;
-    }
-
-    if (value >= 0 && value < (GLint)webgl->GLMaxTextureUnits())
-        return true;
-
-    webgl->ErrorInvalidValue("%s: This uniform location is a sampler, but %d is not a"
-                             " valid texture unit.",
-                             funcName, value);
-    return false;
-}
-
-JS::Value
-WebGLUniformLocation::GetUniform(JSContext* js, WebGLContext* webgl) const
-{
-    MOZ_ASSERT(mLinkInfo);
-
-    uint8_t elemSize = mActiveInfo->mElemSize;
+    const uint8_t elemSize = mInfo->mActiveInfo->mElemSize;
     static const uint8_t kMaxElemSize = 16;
     MOZ_ASSERT(elemSize <= kMaxElemSize);
 
     GLuint prog = mLinkInfo->prog->mGLName;
 
-    gl::GLContext* gl = webgl->GL();
+    gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
 
-    switch (mActiveInfo->mElemType) {
+    switch (mInfo->mActiveInfo->mElemType) {
     case LOCAL_GL_INT:
     case LOCAL_GL_INT_VEC2:
     case LOCAL_GL_INT_VEC3:
     case LOCAL_GL_INT_VEC4:
     case LOCAL_GL_SAMPLER_2D:
     case LOCAL_GL_SAMPLER_3D:
     case LOCAL_GL_SAMPLER_CUBE:
     case LOCAL_GL_SAMPLER_2D_SHADOW:
@@ -232,19 +212,19 @@ WebGLUniformLocation::GetUniform(JSConte
     case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
         {
             GLint buffer[kMaxElemSize] = {0};
             gl->fGetUniformiv(prog, mLoc, buffer);
 
             if (elemSize == 1)
                 return JS::Int32Value(buffer[0]);
 
-            JSObject* obj = dom::Int32Array::Create(js, webgl, elemSize, buffer);
+            JSObject* obj = dom::Int32Array::Create(js, mContext, elemSize, buffer);
             if (!obj) {
-                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                mContext->ErrorOutOfMemory("getUniform: Out of memory.");
                 return JS::NullValue();
             }
             return JS::ObjectOrNullValue(obj);
         }
 
     case LOCAL_GL_BOOL:
     case LOCAL_GL_BOOL_VEC2:
     case LOCAL_GL_BOOL_VEC3:
@@ -258,17 +238,17 @@ WebGLUniformLocation::GetUniform(JSConte
 
             bool boolBuffer[kMaxElemSize];
             for (uint8_t i = 0; i < kMaxElemSize; i++)
                 boolBuffer[i] = buffer[i];
 
             JS::RootedValue val(js);
             // Be careful: we don't want to convert all of |uv|!
             if (!dom::ToJSValue(js, boolBuffer, elemSize, &val)) {
-                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                mContext->ErrorOutOfMemory("getUniform: Out of memory.");
                 return JS::NullValue();
             }
             return val;
         }
 
     case LOCAL_GL_FLOAT:
     case LOCAL_GL_FLOAT_VEC2:
     case LOCAL_GL_FLOAT_VEC3:
@@ -284,38 +264,38 @@ WebGLUniformLocation::GetUniform(JSConte
     case LOCAL_GL_FLOAT_MAT4x3:
         {
             GLfloat buffer[16] = {0.0f};
             gl->fGetUniformfv(prog, mLoc, buffer);
 
             if (elemSize == 1)
                 return JS::DoubleValue(buffer[0]);
 
-            JSObject* obj = dom::Float32Array::Create(js, webgl, elemSize, buffer);
+            JSObject* obj = dom::Float32Array::Create(js, mContext, elemSize, buffer);
             if (!obj) {
-                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                mContext->ErrorOutOfMemory("getUniform: Out of memory.");
                 return JS::NullValue();
             }
             return JS::ObjectOrNullValue(obj);
         }
 
     case LOCAL_GL_UNSIGNED_INT:
     case LOCAL_GL_UNSIGNED_INT_VEC2:
     case LOCAL_GL_UNSIGNED_INT_VEC3:
     case LOCAL_GL_UNSIGNED_INT_VEC4:
         {
             GLuint buffer[kMaxElemSize] = {0};
             gl->fGetUniformuiv(prog, mLoc, buffer);
 
             if (elemSize == 1)
                 return JS::DoubleValue(buffer[0]); // This is Double because only Int32 is special cased.
 
-            JSObject* obj = dom::Uint32Array::Create(js, webgl, elemSize, buffer);
+            JSObject* obj = dom::Uint32Array::Create(js, mContext, elemSize, buffer);
             if (!obj) {
-                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                mContext->ErrorOutOfMemory("getUniform: Out of memory.");
                 return JS::NullValue();
             }
             return JS::ObjectOrNullValue(obj);
         }
 
     default:
         MOZ_CRASH("GFX: Invalid elemType.");
     }
--- a/dom/canvas/WebGLUniformLocation.h
+++ b/dom/canvas/WebGLUniformLocation.h
@@ -34,34 +34,35 @@ public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLUniformLocation)
 
     virtual JSObject* WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto) override;
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
+    //////
+
     const WeakPtr<const webgl::LinkedProgramInfo> mLinkInfo;
+    webgl::UniformInfo* const mInfo;
     const GLuint mLoc;
     const size_t mArrayIndex;
-    const WebGLActiveInfo* const mActiveInfo;
+
+    //////
 
     WebGLUniformLocation(WebGLContext* webgl, const webgl::LinkedProgramInfo* linkInfo,
-                         GLuint loc, size_t arrayIndex, const WebGLActiveInfo* activeInfo);
+                         webgl::UniformInfo* info, GLuint loc, size_t arrayIndex);
 
-    bool ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
-                            const char* funcName) const;
-    bool ValidateSamplerSetter(GLint value, WebGLContext* webgl,
-                               const char* funcName) const;
+    bool ValidateForProgram(WebGLProgram* prog, const char* funcName) const;
     bool ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType,
-                             WebGLContext* webgl, const char* funcName) const;
+                             const char* funcName) const;
     bool ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize,
-                             WebGLContext* webgl, const char* funcName) const;
+                             const char* funcName) const;
 
-    JS::Value GetUniform(JSContext* js, WebGLContext* webgl) const;
+    JS::Value GetUniform(JSContext* js) const;
 
     // Needed for certain helper functions like ValidateObject.
     // `WebGLUniformLocation`s can't be 'Deleted' in the WebGL sense.
     bool IsDeleted() const { return false; }
 
 protected:
     ~WebGLUniformLocation();
 };
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -1,21 +1,21 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 TEST_DIRS += [
-    'compiledtest', 
+    'compiledtest',
     'gtest'
 ]
 
 # Change the following line(s) to avoid bug 1081323 (clobber after changing a manifest):
-# * Add a regression test for triangle-then-point rendering.
+# * Implement ReadPixel with PBOs.
 
 MOCHITEST_MANIFESTS += [
     'test/crash/mochitest.ini',
     'test/crossorigin/mochitest.ini',
     'test/mochitest.ini',
     'test/webgl-conf/generated-mochitest.ini',
     'test/webgl-mochitest/mochitest.ini',
 ]
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -6665,17 +6665,16 @@ fail-if = (os == 'b2g')
 [generated/test_conformance__programs__program-test.html]
 fail-if = (os == 'android' && android_version == '10')
 [generated/test_conformance__programs__use-program-crash-with-discard-in-fragment-shader.html]
 [generated/test_conformance__reading__read-pixels-pack-alignment.html]
 [generated/test_conformance__reading__read-pixels-test.html]
 skip-if = (os == 'android') || (os == 'linux')
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__renderbuffers__feedback-loop.html]
-fail-if = 1
 [generated/test_conformance__renderbuffers__framebuffer-object-attachment.html]
 skip-if = (os == 'android')
 [generated/test_conformance__renderbuffers__framebuffer-state-restoration.html]
 [generated/test_conformance__renderbuffers__framebuffer-test.html]
 [generated/test_conformance__renderbuffers__renderbuffer-initialization.html]
 [generated/test_conformance__rendering__culling.html]
 [generated/test_conformance__rendering__default-texture-draw-bug.html]
 [generated/test_conformance__rendering__draw-arrays-out-of-bounds.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -31,19 +31,16 @@ subsuite = webgl
 skip-if = os == 'b2g' || ((os == 'linux') && (buildapp == 'mulet'))
 
 [generated/test_..__always-fail.html]
 fail-if = 1
 [generated/test_conformance__context__context-lost.html]
 fail-if = 1
 [generated/test_conformance__glsl__misc__shaders-with-invariance.html]
 fail-if = 1
-[generated/test_conformance__glsl__misc__shaders-with-name-conflicts.html]
-[generated/test_conformance__renderbuffers__feedback-loop.html]
-fail-if = 1
 
 ####################
 # Tests requesting non-local network connections.
 
 [generated/test_conformance__more__functions__readPixelsBadArgs.html]
 # (TODO) FATAL ERROR: Non-local network connections are disabled and a connection attempt to www.opengl.org (45.55.206.190) was made.
 skip-if = 1
 [generated/test_2_conformance__more__functions__readPixelsBadArgs.html]
--- a/dom/canvas/test/webgl-mochitest/mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest/mochitest.ini
@@ -66,16 +66,17 @@ support-files = ../captureStream_common.
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
 [test_hidden_depth_stencil.html]
 fail-if = (os == 'win' && os_version == '5.1')
 [test_implicit_color_buffer_float.html]
 [test_highp_fs.html]
 [test_no_arr_points.html]
 skip-if = android_version == '18' #Android 4.3 aws only; bug 1030942
 [test_noprog_draw.html]
+[test_pixel_pack_buffer.html]
 [test_privileged_exts.html]
 [test_renderer_strings.html]
 [test_sab_with_webgl.html]
 [test_texsubimage_float.html]
 [test_uninit_data.html]
 [test_webgl_available.html]
 #[test_webgl_color_buffer_float.html]
 # We haven't cleaned up the Try results yet, but let's get this on the books first.
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_pixel_pack_buffer.html
@@ -0,0 +1,288 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='UTF-8'>
+    <script src='/tests/SimpleTest/SimpleTest.js'></script>
+    <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+  </head>
+  <body>
+    <script>
+
+var RED   = [1, 0, 0, 1];
+var GREEN = [0, 1, 0, 1];
+var BLUE  = [0, 0, 1, 1];
+var WHITE = [1, 1, 1, 1];
+var ZERO  = [0, 0, 0, 0];
+
+function DrawColors(gl) {
+  var fnClearToColor = function(color) {
+    gl.clearColor(color[0], color[1], color[2], color[3]);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+  };
+
+  gl.enable(gl.SCISSOR_TEST);
+
+  // +---+
+  // |G W|
+  // |R B|
+  // +---+
+
+  gl.scissor(0, 0, 1, 1);
+  fnClearToColor(RED);
+
+  gl.scissor(1, 0, 1, 1);
+  fnClearToColor(BLUE);
+
+  gl.scissor(0, 1, 1, 1);
+  fnClearToColor(GREEN);
+
+  gl.scissor(1, 1, 1, 1);
+  fnClearToColor(WHITE);
+}
+
+function ClearBufferPair(gl, byteCount) {
+  // Using `null` here clears to zero according to WebGL.
+  gl.bufferData(gl.PIXEL_PACK_BUFFER, byteCount, gl.STREAM_READ);
+
+  var arr = new Uint8Array(byteCount);
+  return arr;
+}
+
+function ColorToString(color, offset=0) {
+  var arr = [ color[offset+0],
+              color[offset+1],
+              color[offset+2],
+              color[offset+3] ];
+  return '[' + arr.join(', ') + ']';
+}
+
+function TestIsUNormColor(refColor, testData, offset) {
+  if (testData.length < offset + 4) {
+    ok(false, 'testData not long enough.');
+  }
+
+  var refUNormColor = [
+    (refColor[0] * 255) | 0,
+    (refColor[1] * 255) | 0,
+    (refColor[2] * 255) | 0,
+    (refColor[3] * 255) | 0,
+  ];
+
+  var refStr = ColorToString(refUNormColor);
+  var testStr = ColorToString(testData, offset);
+  ok(testStr == refStr, 'Expected ' + refStr + ', was ' + testStr + '.');
+}
+
+function section(text) {
+  ok(true, '');
+  ok(true, 'Section: ' + text);
+}
+
+function EnsureNoError(gl) {
+  var glErr = gl.getError();
+  while (gl.getError()) {}
+
+  if (!glErr)
+    return;
+
+  var extraInfo = '';
+
+  var err = new Error();
+  var stackStr = err.stack;
+  if (stackStr !== undefined) {
+    var stackArr = stackStr.split('\n');
+    stackStr = stackArr[1]; // First one after present scope.
+    extraInfo = ': ' + stackStr;
+  }
+
+  ok(false, 'Unexpected GL error: 0x' + glErr.toString(16) + extraInfo);
+}
+
+function TestError(gl, refErrVal, str='') {
+  if (str == '') {
+    str = 'gl.getError()';
+  } else {
+    str = str + ': gl.getError()';
+  }
+
+  var err = gl.getError();
+  while (gl.getError()) {}
+
+  ShouldBe(err.toString(16), refErrVal.toString(16), str);
+}
+
+function ShouldBe(val, ref, str='') {
+  if (str != '') {
+    str += ': ';
+  }
+
+  ok(val == ref, str + 'Should be `' + ref + '`, was `' + val +