Merge mozilla-central to inbound. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Tue, 12 Feb 2019 12:12:06 +0200
changeset 458699 9f3a8e9d5915
parent 458698 b716914a3375 (current diff)
parent 458649 5416b072d9d5 (diff)
child 458700 b3f7df7c7a53
push id35544
push userccoroiu@mozilla.com
push dateTue, 12 Feb 2019 16:29:08 +0000
treeherdermozilla-central@c849fb69e2e7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
gfx/wr/webrender/src/picture.rs
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -89,17 +89,22 @@ const messageListeners = {
             break;
           case "showcontrols":
             media.setAttribute("controls", "true");
             break;
           case "fullscreen":
             if (this.content.document.fullscreenEnabled) {
               media.requestFullscreen();
             }
-
+            break;
+          case "pictureinpicture":
+            let event = new this.content.CustomEvent("MozTogglePictureInPicture", {
+              bubbles: true,
+            }, this.content);
+            media.dispatchEvent(event);
             break;
         }
       }
     );
   },
 
   "ContextMenu:ReloadFrame": function(aMessage) {
     let forceReload = aMessage.objects && aMessage.objects.forceReload;
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1481,16 +1481,19 @@ pref("media.autoplay.default", 1); // 0=
 
 #ifdef NIGHTLY_BUILD
 // Block WebAudio from playing automatically.
 pref("media.autoplay.block-webaudio", true);
 #else
 pref("media.autoplay.block-webaudio", false);
 #endif
 
+#ifdef NIGHTLY_BUILD
+pref("media.videocontrols.picture-in-picture.enabled", false);
+#endif
 
 // Play with different values of the decay time and get telemetry,
 // 0 means to randomize (and persist) the experiment value in users' profiles,
 // -1 means no experiment is run and we use the preferred value for frecency (6h)
 pref("browser.cache.frecency_experiment", 0);
 
 pref("browser.translation.detectLanguage", false);
 pref("browser.translation.neverForLanguages", "");
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -170,16 +170,23 @@
       <menuitem id="context-video-fullscreen"
                 accesskey="&videoFullScreen.accesskey;"
                 label="&videoFullScreen.label;"
                 oncommand="gContextMenu.mediaCommand('fullscreen');"/>
       <menuitem id="context-leave-dom-fullscreen"
                 accesskey="&leaveDOMFullScreen.accesskey;"
                 label="&leaveDOMFullScreen.label;"
                 oncommand="gContextMenu.leaveDOMFullScreen();"/>
+#ifdef NIGHTLY_BUILD
+      <!-- Don't forget to add a properly localized label and access key
+           before letting this ride up to beta. -->
+      <menuitem id="context-video-pictureinpicture"
+                label="Picture in Picture"
+                oncommand="gContextMenu.mediaCommand('pictureinpicture');"/>
+#endif
       <menuseparator id="context-media-sep-commands"/>
       <menuitem id="context-reloadimage"
                 label="&reloadImageCmd.label;"
                 accesskey="&reloadImageCmd.accesskey;"
                 oncommand="gContextMenu.reloadImage();"/>
       <menuitem id="context-viewimage"
                 label="&viewImageCmd.label;"
                 accesskey="&viewImageCmd.accesskey;"
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -665,16 +665,22 @@ nsContextMenu.prototype = {
     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
     this.showItem("context-media-mute", onMedia && !this.target.muted);
     this.showItem("context-media-unmute", onMedia && this.target.muted);
     this.showItem("context-media-playbackrate", onMedia && this.target.duration != Number.POSITIVE_INFINITY);
     this.showItem("context-media-loop", onMedia);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
     this.showItem("context-video-fullscreen", this.onVideo && !this.target.ownerDocument.fullscreen);
+    if (AppConstants.NIGHTLY_BUILD) {
+      let shouldDisplay = Services.prefs.getBoolPref("media.videocontrols.picture-in-picture.enabled") &&
+                          this.onVideo &&
+                          !this.target.ownerDocument.fullscreen;
+      this.showItem("context-video-pictureinpicture", shouldDisplay);
+    }
     this.showItem("context-media-eme-learnmore", this.onDRMMedia);
     this.showItem("context-media-eme-separator", this.onDRMMedia);
 
     // Disable them when there isn't a valid media source loaded.
     if (onMedia) {
       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
       this.setItemAttr("context-media-playbackrate-125x", "checked", this.target.playbackRate == 1.25);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -410,16 +410,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   Normandy: "resource://normandy/Normandy.jsm",
   ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PdfJs: "resource://pdf.js/PdfJs.jsm",
   PermissionUI: "resource:///modules/PermissionUI.jsm",
+  PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
   PingCentre: "resource:///modules/PingCentre.jsm",
   PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
   ReaderParent: "resource:///modules/ReaderParent.jsm",
   RemotePrompt: "resource:///modules/RemotePrompt.jsm",
@@ -507,16 +508,18 @@ const listeners = {
     "webrtc:UpdatingIndicators": ["webrtcUI"],
   },
 
   mm: {
     "Content:Click": ["ContentClick"],
     "ContentSearch": ["ContentSearch"],
     "FormValidation:ShowPopup": ["FormValidationHandler"],
     "FormValidation:HidePopup": ["FormValidationHandler"],
+    "PictureInPicture:Request": ["PictureInPicture"],
+    "PictureInPicture:Close": ["PictureInPicture"],
     "Prompt:Open": ["RemotePrompt"],
     "Reader:FaviconRequest": ["ReaderParent"],
     "Reader:UpdateReaderButton": ["ReaderParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
     "RemoteLogins:findLogins": ["LoginManagerParent"],
     "RemoteLogins:findRecipes": ["LoginManagerParent"],
     "RemoteLogins:onFormSubmit": ["LoginManagerParent"],
     "RemoteLogins:autoCompleteLogins": ["LoginManagerParent"],
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3184,27 +3184,28 @@ var SessionStoreInternal = {
    * trigger a remoteness changing load with the most recent load arguments.
    *
    * This method's promise will resolve when the process for the given
    * xul:browser element has successfully been swapped.
    *
    * @param tab to navigate and restore.
    */
   async _asyncNavigateAndRestore(tab) {
-    let initialBrowser = tab.linkedBrowser;
+    let permanentKey = tab.linkedBrowser.permanentKey;
+
     // NOTE: This is currently the only async operation used, but this is likely
     // to change in the future.
-    await TabStateFlusher.flush(initialBrowser);
+    await TabStateFlusher.flush(tab.linkedBrowser);
 
     // Now that we have flushed state, our loadArguments, etc. may have been
     // overwritten by multiple calls to navigateAndRestore. Load the most
     // recently stored one.
     let {loadArguments, historyIndex} =
-      this._remotenessChangingBrowsers.get(initialBrowser.permanentKey);
-    this._remotenessChangingBrowsers.delete(initialBrowser.permanentKey);
+      this._remotenessChangingBrowsers.get(permanentKey);
+    this._remotenessChangingBrowsers.delete(permanentKey);
 
     // The tab might have been closed/gone in the meantime.
     if (tab.closing || !tab.linkedBrowser) {
       return;
     }
 
     // The tab or its window might be gone.
     let window = tab.ownerGlobal;
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -1576,19 +1576,19 @@ var Scratchpad = {
       this.editor.setFontSize(Services.prefs.getIntPref(EDITOR_FONT_SIZE));
 
       this.editor.on("change", this._onChanged);
       // Keep a reference to the bound version for use in onUnload.
       this.updateStatusBar = Scratchpad.updateStatusBar.bind(this);
       this.editor.on("cursorActivity", this.updateStatusBar);
       const okstring = this.strings.GetStringFromName("selfxss.okstring");
       const msg = this.strings.formatStringFromName("selfxss.msg", [okstring], 1);
-      this._onPaste = WebConsoleUtils.pasteHandlerGen(this.editor.container.contentDocument.body,
-                                                      this.notificationBox,
-                                                      msg, okstring);
+      this._onPaste = pasteHandlerGen(this.editor.container.contentDocument.body,
+        this.notificationBox, msg, okstring);
+
       editorElement.addEventListener("paste", this._onPaste, true);
       editorElement.addEventListener("drop", this._onPaste);
       this.editor.on("saveRequested", () => this.saveFile());
       this.editor.focus();
       this.editor.setCursor({ line: lines.length, ch: lines.pop().length });
 
       // Add the commands controller for the source-editor.
       this.editor.insertCommandsController();
@@ -2263,8 +2263,57 @@ var CloseObserver = {
 
 XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function() {
   return Services.strings.createBundle(SCRATCHPAD_L10N);
 });
 
 addEventListener("load", Scratchpad.onLoad.bind(Scratchpad), false);
 addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
 addEventListener("close", Scratchpad.onClose.bind(Scratchpad), false);
+
+/**
+ * The inputNode "paste" event handler generator. Helps prevent
+ * self-xss attacks
+ *
+ * @param Element inputField
+ * @param Element notificationBox
+ * @returns A function to be added as a handler to 'paste' and
+ *'drop' events on the input field
+ */
+function pasteHandlerGen(inputField, notificationBox, msg, okstring) {
+  const handler = function(event) {
+    if (WebConsoleUtils.usageCount >= WebConsoleUtils.CONSOLE_ENTRY_THRESHOLD) {
+      inputField.removeEventListener("paste", handler);
+      inputField.removeEventListener("drop", handler);
+      return true;
+    }
+    if (notificationBox.getNotificationWithValue("selfxss-notification")) {
+      event.preventDefault();
+      event.stopPropagation();
+      return false;
+    }
+
+    const notification = notificationBox.appendNotification(msg,
+      "selfxss-notification", null,
+      notificationBox.PRIORITY_WARNING_HIGH, null,
+      function(eventType) {
+        // Cleanup function if notification is dismissed
+        if (eventType == "removed") {
+          inputField.removeEventListener("keyup", pasteKeyUpHandler);
+        }
+      });
+
+    function pasteKeyUpHandler() {
+      const value = inputField.value || inputField.textContent;
+      if (value.includes(okstring)) {
+        notificationBox.removeNotification(notification);
+        inputField.removeEventListener("keyup", pasteKeyUpHandler);
+        WebConsoleUtils.usageCount = WebConsoleUtils.CONSOLE_ENTRY_THRESHOLD;
+      }
+    }
+    inputField.addEventListener("keyup", pasteKeyUpHandler);
+
+    event.preventDefault();
+    event.stopPropagation();
+    return false;
+  };
+  return handler;
+}
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -29,24 +29,16 @@ const {KeyCodes} = require("devtools/cli
 const {PluralForm} = require("devtools/shared/plural-form");
 const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper(DBG_STRINGS_URI);
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1",
   "nsIClipboardHelper");
 
-Object.defineProperty(this, "WebConsoleUtils", {
-  get: function() {
-    return require("devtools/client/webconsole/utils").Utils;
-  },
-  configurable: true,
-  enumerable: true,
-});
-
 this.EXPORTED_SYMBOLS = ["VariablesView", "escapeHTML"];
 
 /**
  * A tree view for inspecting scopes, objects and properties.
  * Iterable via "for (let [id, scope] of instance) { }".
  * Requires the devtools common.css and debugger.css skin stylesheets.
  *
  * To allow replacing variable or property values in this view, provide an
@@ -3323,24 +3315,107 @@ VariablesView.getGrip = function(aValue)
       // fall through
     case "object":
       if (aValue === null) {
         return { type: "null" };
       }
       // fall through
     case "function":
       return { type: "object",
-               class: WebConsoleUtils.getObjectClassName(aValue) };
+               class: getObjectClassName(aValue) };
     default:
       console.error("Failed to provide a grip for value of " + typeof value +
                     ": " + aValue);
       return null;
   }
 };
 
+// Match the function name from the result of toString() or toSource().
+//
+// Examples:
+// (function foobar(a, b) { ...
+// function foobar2(a) { ...
+// function() { ...
+const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
+
+/**
+ * Helper function to deduce the name of the provided function.
+ *
+ * @param function function
+ *        The function whose name will be returned.
+ * @return string
+ *         Function name.
+ */
+function getFunctionName(func) {
+  let name = null;
+  if (func.name) {
+    name = func.name;
+  } else {
+    let desc;
+    try {
+      desc = func.getOwnPropertyDescriptor("displayName");
+    } catch (ex) {
+      // Ignore.
+    }
+    if (desc && typeof desc.value == "string") {
+      name = desc.value;
+    }
+  }
+  if (!name) {
+    try {
+      const str = (func.toString() || func.toSource()) + "";
+      name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
+    } catch (ex) {
+      // Ignore.
+    }
+  }
+  return name;
+}
+
+/**
+ * Get the object class name. For example, the |window| object has the Window
+ * class name (based on [object Window]).
+ *
+ * @param object object
+ *        The object you want to get the class name for.
+ * @return string
+ *         The object class name.
+ */
+function getObjectClassName(object) {
+  if (object === null) {
+    return "null";
+  }
+  if (object === undefined) {
+    return "undefined";
+  }
+
+  const type = typeof object;
+  if (type != "object") {
+    // Grip class names should start with an uppercase letter.
+    return type.charAt(0).toUpperCase() + type.substr(1);
+  }
+
+  let className;
+
+  try {
+    className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
+    if (!className) {
+      className = ((object.constructor + "")
+                    .match(/^\[object (\S+)\]$/) || [])[1];
+    }
+    if (!className && typeof object.constructor == "function") {
+      className = getFunctionName(object.constructor);
+    }
+  } catch (ex) {
+    // Ignore.
+  }
+
+  return className;
+}
+
 /**
  * Returns a custom formatted property string for a grip.
  *
  * @param any aGrip
  *        @see Variable.setGrip
  * @param object aOptions
  *        Options:
  *        - concise: boolean that tells you want a concisely formatted string.
--- a/devtools/client/shared/widgets/VariablesViewController.jsm
+++ b/devtools/client/shared/widgets/VariablesViewController.jsm
@@ -8,24 +8,16 @@
 var {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
 var {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 var {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 
-Object.defineProperty(this, "WebConsoleUtils", {
-  get: function() {
-    return require("devtools/client/webconsole/utils").Utils;
-  },
-  configurable: true,
-  enumerable: true,
-});
-
 XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
   Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
 );
 
 const MAX_LONG_STRING_LENGTH = 200000;
 const MAX_PROPERTY_ITEMS = 2000;
 const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
 
@@ -557,32 +549,32 @@ VariablesViewController.prototype = {
     }
 
     // If the source is primitive then an expander is not needed.
     if (VariablesView.isPrimitive({ value: aSource })) {
       return;
     }
 
     // If the source is a long string then show the arrow.
-    if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
+    if (isActorGrip(aSource) && aSource.type == "longString") {
       aTarget.showArrow();
     }
 
     // Make sure that properties are always available on expansion.
     aTarget.onexpand = () => this.populate(aTarget, aSource);
 
     // Some variables are likely to contain a very large number of properties.
     // It's a good idea to be prepared in case of an expansion.
     if (aTarget.shouldPrefetch) {
       aTarget.addEventListener("mouseover", aTarget.onexpand);
     }
 
     // Register all the actors that this controller now depends on.
     for (const grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
-      if (WebConsoleUtils.isActorGrip(grip)) {
+      if (isActorGrip(grip)) {
         this._actors.add(grip.actor);
       }
     }
   },
 
   /**
    * Adds properties to a Scope, Variable, or Property in the view. Triggered
    * when a scope is expanded or certain variables are hovered.
@@ -845,8 +837,20 @@ var StackFrameUtils = this.StackFrameUti
         label += " [" +
           (f.name || f.userDisplayName || f.displayName || "(anonymous)") +
         "]";
         break;
     }
     return label;
   },
 };
+
+/**
+ * Check if the given value is a grip with an actor.
+ *
+ * @param mixed grip
+ *        Value you want to check if it is a grip with an actor.
+ * @return boolean
+ *         True if the given value is a grip with an actor.
+ */
+function isActorGrip(grip) {
+  return grip && typeof (grip) == "object" && grip.actor;
+}
--- a/devtools/client/webconsole/utils.js
+++ b/devtools/client/webconsole/utils.js
@@ -4,33 +4,19 @@
  * 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";
 
 const {Cc, Ci} = require("chrome");
 const Services = require("Services");
 
-// Match the function name from the result of toString() or toSource().
-//
-// Examples:
-// (function foobar(a, b) { ...
-// function foobar2(a) { ...
-// function() { ...
-const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
-
 // Number of terminal entries for the self-xss prevention to go away
 const CONSOLE_ENTRY_THRESHOLD = 5;
 
-exports.CONSOLE_WORKER_IDS = [
-  "SharedWorker",
-  "ServiceWorker",
-  "Worker",
-];
-
 var WebConsoleUtils = {
 
   CONSOLE_ENTRY_THRESHOLD,
 
   /**
    * Wrap a string in an nsISupportsString object.
    *
    * @param string string
@@ -39,60 +25,16 @@ var WebConsoleUtils = {
   supportsString: function(string) {
     const str = Cc["@mozilla.org/supports-string;1"]
               .createInstance(Ci.nsISupportsString);
     str.data = string;
     return str;
   },
 
   /**
-   * Clone an object.
-   *
-   * @param object object
-   *        The object you want cloned.
-   * @param boolean recursive
-   *        Tells if you want to dig deeper into the object, to clone
-   *        recursively.
-   * @param function [filter]
-   *        Optional, filter function, called for every property. Three
-   *        arguments are passed: key, value and object. Return true if the
-   *        property should be added to the cloned object. Return false to skip
-   *        the property.
-   * @return object
-   *         The cloned object.
-   */
-  cloneObject: function(object, recursive, filter) {
-    if (typeof object != "object") {
-      return object;
-    }
-
-    let temp;
-
-    if (Array.isArray(object)) {
-      temp = [];
-      Array.forEach(object, function(value, index) {
-        if (!filter || filter(index, value, object)) {
-          temp.push(recursive ? WebConsoleUtils.cloneObject(value) : value);
-        }
-      });
-    } else {
-      temp = {};
-      for (const key in object) {
-        const value = object[key];
-        if (object.hasOwnProperty(key) &&
-            (!filter || filter(key, value, object))) {
-          temp[key] = recursive ? WebConsoleUtils.cloneObject(value) : value;
-        }
-      }
-    }
-
-    return temp;
-  },
-
-  /**
    * Copies certain style attributes from one element to another.
    *
    * @param Node from
    *        The target node.
    * @param Node to
    *        The destination node.
    */
   copyTextStyles: function(from, to) {
@@ -100,123 +42,16 @@ var WebConsoleUtils = {
     const style = win.getComputedStyle(from);
     to.style.fontFamily = style.fontFamily;
     to.style.fontSize = style.fontSize;
     to.style.fontWeight = style.fontWeight;
     to.style.fontStyle = style.fontStyle;
   },
 
   /**
-   * Determine if the given request mixes HTTP with HTTPS content.
-   *
-   * @param string request
-   *        Location of the requested content.
-   * @param string location
-   *        Location of the current page.
-   * @return boolean
-   *         True if the content is mixed, false if not.
-   */
-  isMixedHTTPSRequest: function(request, location) {
-    try {
-      const requestURI = Services.io.newURI(request);
-      const contentURI = Services.io.newURI(location);
-      return (contentURI.scheme == "https" && requestURI.scheme != "https");
-    } catch (ex) {
-      return false;
-    }
-  },
-
-  /**
-   * Helper function to deduce the name of the provided function.
-   *
-   * @param function function
-   *        The function whose name will be returned.
-   * @return string
-   *         Function name.
-   */
-  getFunctionName: function(func) {
-    let name = null;
-    if (func.name) {
-      name = func.name;
-    } else {
-      let desc;
-      try {
-        desc = func.getOwnPropertyDescriptor("displayName");
-      } catch (ex) {
-        // Ignore.
-      }
-      if (desc && typeof desc.value == "string") {
-        name = desc.value;
-      }
-    }
-    if (!name) {
-      try {
-        const str = (func.toString() || func.toSource()) + "";
-        name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
-      } catch (ex) {
-        // Ignore.
-      }
-    }
-    return name;
-  },
-
-  /**
-   * Get the object class name. For example, the |window| object has the Window
-   * class name (based on [object Window]).
-   *
-   * @param object object
-   *        The object you want to get the class name for.
-   * @return string
-   *         The object class name.
-   */
-  getObjectClassName: function(object) {
-    if (object === null) {
-      return "null";
-    }
-    if (object === undefined) {
-      return "undefined";
-    }
-
-    const type = typeof object;
-    if (type != "object") {
-      // Grip class names should start with an uppercase letter.
-      return type.charAt(0).toUpperCase() + type.substr(1);
-    }
-
-    let className;
-
-    try {
-      className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
-      if (!className) {
-        className = ((object.constructor + "")
-                     .match(/^\[object (\S+)\]$/) || [])[1];
-      }
-      if (!className && typeof object.constructor == "function") {
-        className = this.getFunctionName(object.constructor);
-      }
-    } catch (ex) {
-      // Ignore.
-    }
-
-    return className;
-  },
-
-  /**
-   * Check if the given value is a grip with an actor.
-   *
-   * @param mixed grip
-   *        Value you want to check if it is a grip with an actor.
-   * @return boolean
-   *         True if the given value is a grip with an actor.
-   */
-  isActorGrip: function(grip) {
-    return grip && typeof (grip) == "object" && grip.actor;
-  },
-
-  /**
    * Value of devtools.selfxss.count preference
    *
    * @type number
    * @private
    */
   _usageCount: 0,
   get usageCount() {
     if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
@@ -229,60 +64,12 @@ var WebConsoleUtils = {
     return WebConsoleUtils._usageCount;
   },
   set usageCount(newUC) {
     if (newUC <= CONSOLE_ENTRY_THRESHOLD) {
       WebConsoleUtils._usageCount = newUC;
       Services.prefs.setIntPref("devtools.selfxss.count", newUC);
     }
   },
-  /**
-   * The inputNode "paste" event handler generator. Helps prevent
-   * self-xss attacks
-   *
-   * @param Element inputField
-   * @param Element notificationBox
-   * @returns A function to be added as a handler to 'paste' and
-   *'drop' events on the input field
-   */
-  pasteHandlerGen: function(inputField, notificationBox, msg, okstring) {
-    const handler = function(event) {
-      if (WebConsoleUtils.usageCount >= CONSOLE_ENTRY_THRESHOLD) {
-        inputField.removeEventListener("paste", handler);
-        inputField.removeEventListener("drop", handler);
-        return true;
-      }
-      if (notificationBox.getNotificationWithValue("selfxss-notification")) {
-        event.preventDefault();
-        event.stopPropagation();
-        return false;
-      }
-
-      const notification = notificationBox.appendNotification(msg,
-        "selfxss-notification", null,
-        notificationBox.PRIORITY_WARNING_HIGH, null,
-        function(eventType) {
-          // Cleanup function if notification is dismissed
-          if (eventType == "removed") {
-            inputField.removeEventListener("keyup", pasteKeyUpHandler);
-          }
-        });
-
-      function pasteKeyUpHandler(event2) {
-        const value = inputField.value || inputField.textContent;
-        if (value.includes(okstring)) {
-          notificationBox.removeNotification(notification);
-          inputField.removeEventListener("keyup", pasteKeyUpHandler);
-          WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
-        }
-      }
-      inputField.addEventListener("keyup", pasteKeyUpHandler);
-
-      event.preventDefault();
-      event.stopPropagation();
-      return false;
-    };
-    return handler;
-  },
 };
 
 exports.Utils = WebConsoleUtils;
 
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -2647,38 +2647,48 @@ RefPtr<MediaManager::StreamPromise> Medi
                 doc, NS_LITERAL_STRING("microphone"))) {
           audioPerm = nsIPermissionManager::DENY_ACTION;
         } else {
           rv = permManager->TestExactPermissionFromPrincipal(
               principal, "microphone", &audioPerm);
           MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
         }
       } else {
-        rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
-                                                           &audioPerm);
-        MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+        if (!dom::FeaturePolicyUtils::IsFeatureAllowed(
+                doc, NS_LITERAL_STRING("display-capture"))) {
+          audioPerm = nsIPermissionManager::DENY_ACTION;
+        } else {
+          rv = permManager->TestExactPermissionFromPrincipal(
+              principal, "screen", &audioPerm);
+          MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+        }
       }
     }
 
     uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mVideo)) {
       if (videoType == MediaSourceEnum::Camera) {
         if (Preferences::GetBool("media.getusermedia.camera.deny", false) ||
             !dom::FeaturePolicyUtils::IsFeatureAllowed(
                 doc, NS_LITERAL_STRING("camera"))) {
           videoPerm = nsIPermissionManager::DENY_ACTION;
         } else {
           rv = permManager->TestExactPermissionFromPrincipal(
               principal, "camera", &videoPerm);
           MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
         }
       } else {
-        rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
-                                                           &videoPerm);
-        MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+        if (!dom::FeaturePolicyUtils::IsFeatureAllowed(
+                doc, NS_LITERAL_STRING("display-capture"))) {
+          videoPerm = nsIPermissionManager::DENY_ACTION;
+        } else {
+          rv = permManager->TestExactPermissionFromPrincipal(
+              principal, "screen", &videoPerm);
+          MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+        }
       }
     }
 
     if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
         (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
         (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
       windowListener->Remove(sourceListener);
       return StreamPromise::CreateAndReject(
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -29,16 +29,17 @@ static FeatureMap sSupportedFeatures[] =
     {"camera", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
     {"encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll},
     {"fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eAll},
     {"geolocation", FeaturePolicyUtils::FeaturePolicyValue::eAll},
     {"microphone", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
     {"midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
     {"payment", FeaturePolicyUtils::FeaturePolicyValue::eAll},
     {"document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll},
+    {"display-capture", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
     // TODO: not supported yet!!!
     {"speaker", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
     {"vr", FeaturePolicyUtils::FeaturePolicyValue::eAll},
 };
 
 /* static */ bool FeaturePolicyUtils::IsSupportedFeature(
     const nsAString& aFeatureName) {
   uint32_t numFeatures =
--- a/editor/nsIEditorSpellCheck.idl
+++ b/editor/nsIEditorSpellCheck.idl
@@ -135,27 +135,16 @@ interface nsIEditorSpellCheck : nsISuppo
   /**
    * Used to filter the content (for example, to skip blockquotes in email from
    * spellchecking. Call this before calling InitSpellChecker; calling it
    * after initialization will have no effect.
    */
   void          setFilterType(in unsigned long filterType);
 
   /**
-   * Like CheckCurrentWord, checks the word you give it, returning true if it's
-   * misspelled. This is faster than CheckCurrentWord because it does not
-   * compute any suggestions.
-   *
-   * Watch out: this does not clear any suggestions left over from previous
-   * calls to CheckCurrentWord, so there may be suggestions, but they will be
-   * invalid.
-   */
-  boolean       CheckCurrentWordNoSuggest(in AString suggestedWord);
-
-  /**
    * Update the dictionary in use to be sure it corresponds to what the editor
    * needs.  The update is asynchronous and is not complete until the given
    * callback is called.
    */
   void          UpdateCurrentDictionary([optional] in nsIEditorSpellCheckCallback callback);
 
 };
 
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -429,24 +429,16 @@ EditorSpellCheck::CheckCurrentWord(const
                                    bool* aIsMisspelled) {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   DeleteSuggestedWordList();
   return mSpellChecker->CheckWord(aSuggestedWord, aIsMisspelled,
                                   &mSuggestedWordList);
 }
 
-NS_IMETHODIMP
-EditorSpellCheck::CheckCurrentWordNoSuggest(const nsAString& aSuggestedWord,
-                                            bool* aIsMisspelled) {
-  NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
-
-  return mSpellChecker->CheckWord(aSuggestedWord, aIsMisspelled, nullptr);
-}
-
 RefPtr<CheckWordPromise> EditorSpellCheck::CheckCurrentWordsNoSuggest(
     const nsTArray<nsString>& aSuggestedWords) {
   if (NS_WARN_IF(!mSpellChecker)) {
     return CheckWordPromise::CreateAndReject(NS_ERROR_NOT_INITIALIZED,
                                              __func__);
   }
 
   return mSpellChecker->CheckWords(aSuggestedWords);
--- a/extensions/spellcheck/hunspell/glue/PRemoteSpellcheckEngine.ipdl
+++ b/extensions/spellcheck/hunspell/glue/PRemoteSpellcheckEngine.ipdl
@@ -7,17 +7,16 @@ include protocol PContent;
 namespace mozilla {
 
 sync protocol PRemoteSpellcheckEngine {
   manager PContent;
 
 parent:
   async __delete__();
 
-  sync Check(nsString aWord) returns (bool aIsMisspelled);
   async CheckAsync(nsString[] aWord) returns (bool[] aIsMisspelled);
 
   sync CheckAndSuggest(nsString aWord) returns (bool aIsMisspelled, nsString[] aSuggestions);
 
   sync SetDictionary(nsString aDictionary) returns (bool success);
 
   /*
    * Set current dictionary from list of dictionary name.
--- a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.cpp
+++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.cpp
@@ -31,25 +31,16 @@ mozilla::ipc::IPCResult RemoteSpellcheck
       aResolve(Tuple<const bool&, const nsString&>(true, dictionary));
       return IPC_OK();
     }
   }
   aResolve(Tuple<const bool&, const nsString&>(false, EmptyString()));
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvCheck(
-    const nsString& aWord, bool* aIsMisspelled) {
-  nsresult rv = mSpellChecker->CheckWord(aWord, aIsMisspelled, nullptr);
-
-  // If CheckWord failed, we can't tell whether the word is correctly spelled.
-  if (NS_FAILED(rv)) *aIsMisspelled = false;
-  return IPC_OK();
-}
-
 mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvCheckAsync(
     nsTArray<nsString>&& aWords, CheckAsyncResolver&& aResolve) {
   nsTArray<bool> misspells;
   misspells.SetCapacity(aWords.Length());
   for (auto& word : aWords) {
     bool misspelled;
     nsresult rv = mSpellChecker->CheckWord(word, &misspelled, nullptr);
     // If CheckWord failed, we can't tell whether the word is correctly spelled
--- a/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.h
+++ b/extensions/spellcheck/hunspell/glue/RemoteSpellCheckEngineParent.h
@@ -21,19 +21,16 @@ class RemoteSpellcheckEngineParent : pub
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual mozilla::ipc::IPCResult RecvSetDictionary(const nsString& aDictionary,
                                                     bool* success);
 
   virtual mozilla::ipc::IPCResult RecvSetDictionaryFromList(
       nsTArray<nsString>&& aList, SetDictionaryFromListResolver&& aResolve);
 
-  virtual mozilla::ipc::IPCResult RecvCheck(const nsString& aWord,
-                                            bool* aIsMisspelled);
-
   virtual mozilla::ipc::IPCResult RecvCheckAsync(nsTArray<nsString>&& aWord,
                                                  CheckAsyncResolver&& aResolve);
 
   virtual mozilla::ipc::IPCResult RecvCheckAndSuggest(
       const nsString& aWord, bool* aIsMisspelled,
       InfallibleTArray<nsString>* aSuggestions);
 
  private:
--- a/extensions/spellcheck/src/mozSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozSpellChecker.cpp
@@ -145,25 +145,22 @@ RefPtr<CheckWordPromise> mozSpellChecker
 }
 
 nsresult mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled,
                                     nsTArray<nsString> *aSuggestions) {
   nsresult result;
   bool correct;
 
   if (XRE_IsContentProcess()) {
-    nsString wordwrapped = nsString(aWord);
-    bool rv;
-    if (aSuggestions) {
-      rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled,
-                                        aSuggestions);
-    } else {
-      rv = mEngine->SendCheck(wordwrapped, aIsMisspelled);
+    MOZ_ASSERT(aSuggestions, "Use CheckWords if content process");
+    if (!mEngine->SendCheckAndSuggest(nsString(aWord), aIsMisspelled,
+                                      aSuggestions)) {
+      return NS_ERROR_NOT_AVAILABLE;
     }
-    return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+    return NS_OK;
   }
 
   if (!mSpellCheckingEngine) {
     return NS_ERROR_NULL_POINTER;
   }
   *aIsMisspelled = false;
   result = mSpellCheckingEngine->Check(aWord, &correct);
   NS_ENSURE_SUCCESS(result, result);
--- a/extensions/spellcheck/src/mozSpellChecker.h
+++ b/extensions/spellcheck/src/mozSpellChecker.h
@@ -55,17 +55,18 @@ class mozSpellChecker final {
                               nsTArray<nsString>* aSuggestions);
 
   /**
    * Checks if a word is misspelled. No document is required to use this method.
    * @param aWord is the word to check.
    * @param aIsMisspelled will be set to true if the word is misspelled.
    * @param aSuggestions is an array of nsStrings which represent the
    * suggested replacements for the misspelled word. The array will be empty
-   * if there aren't any suggestions.
+   * in chrome process if there aren't any suggestions. If suggestions is
+   * unnecessary, use CheckWords of async version.
    */
   nsresult CheckWord(const nsAString& aWord, bool* aIsMisspelled,
                      nsTArray<nsString>* aSuggestions);
 
   /**
    * This is a flavor of CheckWord, is async version of CheckWord.
    * @Param aWords is array of words to check
    */
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -428,17 +428,17 @@ static const FeatureInfo sFeatureInfoArr
     {"texture_float",
      GLVersion::GL3,
      GLESVersion::ES3,
      GLContext::Extension_None,
      {GLContext::ARB_texture_float, GLContext::OES_texture_float,
       GLContext::Extensions_End}},
     {"texture_float_linear",
      GLVersion::GL3_1,
-     GLESVersion::ES3,
+     GLESVersion::NONE,
      GLContext::Extension_None,
      {GLContext::ARB_texture_float, GLContext::OES_texture_float_linear,
       GLContext::Extensions_End}},
     {
         "texture_half_float",
         GLVersion::GL3,
         GLESVersion::ES3,
         GLContext::Extension_None,
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -957,17 +957,17 @@ impl AlphaBatchBuilder {
                     bounding_rect,
                     z_id,
                     PrimitiveInstanceData::from(instance),
                 );
             }
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
-                let prim_cache_address = gpu_cache.get_address(&picture.gpu_location);
+                let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: picture.local_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
@@ -1901,17 +1901,17 @@ impl AlphaBatchBuilder {
                         if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                             request.with_tile(tile.tile_offset),
                             image_data.alpha_type,
                             get_shader_opacity(opacity_binding),
                         ) {
-                            let prim_cache_address = gpu_cache.get_address(&tile.handle);
+                            let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
                             let prim_header = PrimitiveHeader {
                                 specific_prim_address: prim_cache_address,
                                 local_rect: tile.local_rect,
                                 local_clip_rect: tile.local_clip_rect,
                                 task_address,
                                 clip_task_address,
                                 transform_id,
                             };
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -1,19 +1,19 @@
 /* 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 api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
-use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags};
+use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags, PremultipliedColorF};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
 use clip::{ClipDataStore, ClipStore, ClipChainStack};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
-use gpu_cache::GpuCache;
+use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
 use picture::{RetainedTiles, TileCache, DirtyRegion};
 use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer};
 #[cfg(feature = "replay")]
 use prim_store::{PrimitiveStoreStats};
@@ -52,31 +52,66 @@ pub struct FrameBuilderConfig {
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
     pub chase_primitive: ChasePrimitive,
     pub enable_picture_caching: bool,
     /// True if we're running tests (i.e. via wrench).
     pub testing: bool,
 }
 
+/// A set of common / global resources that are retained between
+/// new display lists, such that any GPU cache handles can be
+/// persisted even when a new display list arrives.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub struct FrameGlobalResources {
+    /// The image shader block for the most common / default
+    /// set of image parameters (color white, stretch == rect.size).
+    pub default_image_handle: GpuCacheHandle,
+}
+
+impl FrameGlobalResources {
+    pub fn empty() -> Self {
+        FrameGlobalResources {
+            default_image_handle: GpuCacheHandle::new(),
+        }
+    }
+
+    pub fn update(
+        &mut self,
+        gpu_cache: &mut GpuCache,
+    ) {
+        if let Some(mut request) = gpu_cache.request(&mut self.default_image_handle) {
+            request.push(PremultipliedColorF::WHITE);
+            request.push(PremultipliedColorF::WHITE);
+            request.push([
+                -1.0,       // -ve means use prim rect for stretch size
+                0.0,
+                0.0,
+                0.0,
+            ]);
+        }
+    }
+}
+
 /// A builder structure for `tiling::Frame`
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct FrameBuilder {
     screen_rect: DeviceIntRect,
     background_color: Option<ColorF>,
     window_size: DeviceIntSize,
     root_pic_index: PictureIndex,
     /// Cache of surface tiles from the previous frame builder
     /// that can optionally be consumed by this frame builder.
     pending_retained_tiles: RetainedTiles,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     #[cfg_attr(feature = "capture", serde(skip))] //TODO
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
+    pub globals: FrameGlobalResources,
 }
 
 pub struct FrameVisibilityContext<'a> {
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub screen_world_rect: WorldRect,
     pub device_pixel_scale: DevicePixelScale,
     pub surfaces: &'a [SurfaceInfo],
     pub debug_flags: DebugFlags,
@@ -188,36 +223,39 @@ impl FrameBuilder {
             hit_testing_runs: Vec::new(),
             prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
             clip_store: ClipStore::new(),
             screen_rect: DeviceIntRect::zero(),
             window_size: DeviceIntSize::zero(),
             background_color: None,
             root_pic_index: PictureIndex(0),
             pending_retained_tiles: RetainedTiles::new(),
+            globals: FrameGlobalResources::empty(),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
                 enable_picture_caching: false,
                 testing: false,
             },
         }
     }
 
     /// Provide any cached surface tiles from the previous frame builder
     /// to a new frame builder. These will be consumed or dropped the
     /// first time a new frame builder creates a frame.
-    pub fn set_retained_tiles(
+    pub fn set_retained_resources(
         &mut self,
         retained_tiles: RetainedTiles,
+        globals: FrameGlobalResources,
     ) {
         debug_assert!(self.pending_retained_tiles.tiles.is_empty());
         self.pending_retained_tiles = retained_tiles;
+        self.globals = globals;
     }
 
     pub fn with_display_list_flattener(
         screen_rect: DeviceIntRect,
         background_color: Option<ColorF>,
         window_size: DeviceIntSize,
         flattener: DisplayListFlattener,
     ) -> Self {
@@ -226,40 +264,43 @@ impl FrameBuilder {
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             root_pic_index: flattener.root_pic_index,
             screen_rect,
             background_color,
             window_size,
             pending_retained_tiles: RetainedTiles::new(),
             config: flattener.config,
+            globals: FrameGlobalResources::empty(),
         }
     }
 
     /// Destroy an existing frame builder. This is called just before
     /// a frame builder is replaced with a newly built scene.
     pub fn destroy(
         self,
         retained_tiles: &mut RetainedTiles,
         clip_scroll_tree: &ClipScrollTree,
-    ) {
+    ) -> FrameGlobalResources {
         self.prim_store.destroy(
             retained_tiles,
             clip_scroll_tree,
         );
 
         // In general, the pending retained tiles are consumed by the frame
         // builder the first time a frame is built after a new scene has
         // arrived. However, if two scenes arrive in quick succession, the
         // frame builder may not have had a chance to build a frame and
         // consume the pending tiles. In this case, the pending tiles will
         // be lost, causing a full invalidation of the entire screen. To
         // avoid this, if there are still pending tiles, include them in
         // the retained tiles passed to the next frame builder.
         retained_tiles.merge(self.pending_retained_tiles);
+
+        self.globals
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         screen_world_rect: WorldRect,
         clip_scroll_tree: &ClipScrollTree,
@@ -469,16 +510,18 @@ impl FrameBuilder {
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(stamp);
         gpu_cache.begin_frame(stamp);
 
+        self.globals.update(gpu_cache);
+
         let mut transform_palette = TransformPalette::new();
         clip_scroll_tree.update_tree(
             pan,
             scene_properties,
             Some(&mut transform_palette),
         );
         self.clip_store.clear_old_instances();
 
@@ -552,16 +595,17 @@ impl FrameBuilder {
                 prim_store: &self.prim_store,
                 resource_cache,
                 use_dual_source_blending,
                 clip_scroll_tree,
                 data_stores,
                 surfaces: &surfaces,
                 scratch,
                 screen_world_rect,
+                globals: &self.globals,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1519,28 +1519,30 @@ impl TileCache {
                     }
                 }
 
                 // Only cache tiles that have had the same content for at least two
                 // frames. This skips caching on pages / benchmarks that are changing
                 // every frame, which is wasteful.
                 if tile.same_frames >= FRAMES_BEFORE_PICTURE_CACHING {
                     // Ensure that this texture is allocated.
-                    resource_cache.texture_cache.update(
-                        &mut tile.handle,
-                        descriptor,
-                        TextureFilter::Linear,
-                        None,
-                        [0.0; 3],
-                        DirtyRect::All,
-                        gpu_cache,
-                        None,
-                        UvRectKind::Rect,
-                        Eviction::Eager,
-                    );
+                    if !resource_cache.texture_cache.is_allocated(&tile.handle) {
+                        resource_cache.texture_cache.update(
+                            &mut tile.handle,
+                            descriptor,
+                            TextureFilter::Linear,
+                            None,
+                            [0.0; 3],
+                            DirtyRect::All,
+                            gpu_cache,
+                            None,
+                            UvRectKind::Rect,
+                            Eviction::Eager,
+                        );
+                    }
 
                     let cache_item = resource_cache
                         .get_texture_cache_item(&tile.handle);
 
                     let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
                     let valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
 
                     tile.valid_rect = visible_rect
@@ -2186,18 +2188,16 @@ pub struct PicturePrimitive {
 
     /// The local rect of this picture. It is built
     /// dynamically during the first picture traversal.
     pub local_rect: LayoutRect,
 
     /// Local clip rect for this picture.
     pub local_clip_rect: LayoutRect,
 
-    pub gpu_location: GpuCacheHandle,
-
     /// If Some(..) the tile cache that is associated with this picture.
     #[cfg_attr(feature = "capture", serde(skip))] //TODO
     pub tile_cache: Option<TileCache>,
 
     /// The config options for this picture.
     options: PictureOptions,
 }
 
@@ -2301,17 +2301,16 @@ impl PicturePrimitive {
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             requested_raster_space,
             spatial_node_index,
             local_rect: LayoutRect::zero(),
             local_clip_rect,
-            gpu_location: GpuCacheHandle::new(),
             tile_cache,
             options,
         }
     }
 
     pub fn take_context(
         &mut self,
         pic_index: PictureIndex,
@@ -2795,17 +2794,16 @@ impl PicturePrimitive {
             // If the local rect changed (due to transforms in child primitives) then
             // invalidate the GPU cache location to re-upload the new local rect
             // and stretch size. Drop shadow filters also depend on the local rect
             // size for the extra GPU cache data handle.
             // TODO(gw): In future, if we support specifying a flag which gets the
             //           stretch size from the segment rect in the shaders, we can
             //           remove this invalidation here completely.
             if self.local_rect != surface_rect {
-                gpu_cache.invalidate(&self.gpu_location);
                 if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
                     gpu_cache.invalidate(&self.extra_gpu_data_handle);
                 }
                 self.local_rect = surface_rect;
             }
 
             // Check if any of the surfaces can't be rasterized in local space but want to.
             if raster_config.establishes_raster_root {
--- a/gfx/wr/webrender/src/prim_store/image.rs
+++ b/gfx/wr/webrender/src/prim_store/image.rs
@@ -5,17 +5,17 @@
 use api::{
     AlphaType, ColorDepth, ColorF, ColorU, DeviceIntRect, DeviceIntSideOffsets,
     DeviceIntSize, ImageRendering, LayoutRect, LayoutSize, LayoutPrimitiveInfo,
     PremultipliedColorF, Shadow, TileOffset, YuvColorSpace, YuvFormat, LayoutVector2D,
 };
 use api::ImageKey as ApiImageKey;
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use frame_builder::FrameBuildingState;
-use gpu_cache::{GpuCacheHandle, GpuDataRequest};
+use gpu_cache::{GpuDataRequest};
 use intern::{Internable, InternDebug};
 use intern_types;
 use prim_store::{
     EdgeAaSegmentMask, OpacityBindingIndex, PrimitiveInstanceKind,
     PrimitiveOpacity, PrimitiveSceneData, PrimKey, PrimKeyCommonData,
     PrimTemplate, PrimTemplateCommonData, PrimitiveStore, SegmentInstanceIndex,
     SizeKey
 };
@@ -26,17 +26,16 @@ use render_task::{
 use resource_cache::ImageRequest;
 use util::pack_as_float;
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct VisibleImageTile {
     pub tile_offset: TileOffset,
-    pub handle: GpuCacheHandle,
     pub edge_flags: EdgeAaSegmentMask,
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -2345,27 +2345,16 @@ impl PrimitiveStore {
                             &prim_info.combined_local_clip_rect,
                             frame_context.screen_world_rect,
                             plane_split_anchor,
                         );
                     }
                 } else {
                     prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                 }
-
-                if let Some(mut request) = frame_state.gpu_cache.request(&mut pic.gpu_location) {
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push([
-                        -1.0,       // -ve means use prim rect for stretch size
-                        0.0,
-                        0.0,
-                        0.0,
-                    ]);
-                }
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::Rectangle { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
@@ -2764,26 +2753,18 @@ impl PrimitiveStore {
                             );
 
                             for tile in tiles {
                                 frame_state.resource_cache.request_image(
                                     request.with_tile(tile.offset),
                                     frame_state.gpu_cache,
                                 );
 
-                                let mut handle = GpuCacheHandle::new();
-                                if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
-                                    request.push(PremultipliedColorF::WHITE);
-                                    request.push(PremultipliedColorF::WHITE);
-                                    request.push([tile.rect.size.width, tile.rect.size.height, 0.0, 0.0]);
-                                }
-
                                 image_instance.visible_tiles.push(VisibleImageTile {
                                     tile_offset: tile.offset,
-                                    handle,
                                     edge_flags: tile.edge_flags & edge_flags,
                                     local_rect: tile.rect,
                                     local_clip_rect: tight_clip_rect,
                                 });
                             }
                         }
 
                         if image_instance.visible_tiles.is_empty() {
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -606,26 +606,29 @@ impl Document {
         self.frame_is_valid = false;
         self.hit_tester_is_valid = false;
 
         // Give the old frame builder a chance to destroy any resources.
         // Right now, all this does is build a hash map of any cached
         // surface tiles, that can be provided to the next frame builder.
         let mut retained_tiles = RetainedTiles::new();
         if let Some(frame_builder) = self.frame_builder.take() {
-            frame_builder.destroy(
+            let globals = frame_builder.destroy(
                 &mut retained_tiles,
                 &self.clip_scroll_tree,
             );
+
+            // Provide any cached tiles from the previous frame builder to
+            // the newly built one.
+            built_scene.frame_builder.set_retained_resources(
+                retained_tiles,
+                globals,
+            );
         }
 
-        // Provide any cached tiles from the previous frame builder to
-        // the newly built one.
-        built_scene.frame_builder.set_retained_tiles(retained_tiles);
-
         self.frame_builder = Some(built_scene.frame_builder);
 
         self.scratch.recycle(recycler);
 
         let old_scrolling_states = self.clip_scroll_tree.drain();
         self.clip_scroll_tree = built_scene.clip_scroll_tree;
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -7,16 +7,17 @@ use api::{DocumentLayer, FilterOp, Image
 use api::{MixBlendMode, PipelineId, DeviceRect, LayoutSize, WorldRect};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use debug_render::DebugItem;
 use device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
+use frame_builder::FrameGlobalResources;
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use picture::{RecordedDirtyRegion, SurfaceInfo};
 use prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
@@ -53,16 +54,17 @@ pub struct RenderTargetContext<'a, 'rc> 
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub use_dual_source_blending: bool,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub data_stores: &'a DataStores,
     pub surfaces: &'a [SurfaceInfo],
     pub scratch: &'a PrimitiveScratchBuffer,
     pub screen_world_rect: WorldRect,
+    pub globals: &'a FrameGlobalResources,
 }
 
 /// Represents a number of rendering operations on a surface.
 ///
 /// In graphics parlance, a "render target" usually means "a surface (texture or
 /// framebuffer) bound to the output of a shader". This trait has a slightly
 /// different meaning, in that it represents the operations on that surface
 /// _before_ it's actually bound and rendered. So a `RenderTarget` is built by
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -927,18 +927,16 @@ description = See corresponding comment 
 [PBackgroundLSSnapshot::LoadItem]
 description = See corresponding comment in PBackgroundLSSnapshot.ipdl
 [PBackgroundLSSnapshot::LoadKeys]
 description = See corresponding comment in PBackgroundLSSnapshot.ipdl
 [PBackgroundLSSnapshot::IncreasePeakUsage]
 description = See corresponding comment in PBackgroundLSSnapshot.ipdl
 [PBackgroundLSSnapshot::Ping]
 description = See corresponding comment in PBackgroundLSSnapshot.ipdl
-[PRemoteSpellcheckEngine::Check]
-description =
 [PRemoteSpellcheckEngine::CheckAndSuggest]
 description =
 [PRemoteSpellcheckEngine::SetDictionary]
 description =
 [PGPU::AddLayerTreeIdMapping]
 description =
 [PGPU::GetDeviceStatus]
 description =
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -7403,19 +7403,17 @@ bool GCRuntime::shouldCollectNurseryForS
     case State::NotActive:
     case State::Sweep:
     case State::Finalize:
     case State::Compact:
     case State::Decommit:
       return true;
     case State::Mark:
       return (nonincrementalByAPI || budget.isUnlimited() || lastMarkSlice ||
-              nursery().minorGCRequested() ||
-              nursery().freeSpace() <
-                  tunables.nurseryFreeThresholdForIdleCollection() ||
+              nursery().shouldCollect() ||
               hasIncrementalTwoSliceZealMode());
     case State::Finish:
       return false;
     case State::MarkRoots:
       MOZ_CRASH("Unexpected GC state");
   }
 
   return false;
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -692,17 +692,17 @@ inline void js::Nursery::startProfile(Pr
   startTimes_[key] = ReallyNow();
 }
 
 inline void js::Nursery::endProfile(ProfileKey key) {
   profileDurations_[key] = ReallyNow() - startTimes_[key];
   totalDurations_[key] += profileDurations_[key];
 }
 
-bool js::Nursery::needIdleTimeCollection() const {
+bool js::Nursery::shouldCollect() const {
   uint32_t threshold = tunables().nurseryFreeThresholdForIdleCollection();
   return minorGCRequested() || freeSpace() < threshold;
 }
 
 static inline bool IsFullStoreBufferReason(JS::GCReason reason) {
   return reason == JS::GCReason::FULL_WHOLE_CELL_BUFFER ||
          reason == JS::GCReason::FULL_GENERIC_BUFFER ||
          reason == JS::GCReason::FULL_VALUE_BUFFER ||
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -357,17 +357,17 @@ class Nursery {
   bool minorGCRequested() const {
     return minorGCTriggerReason_ != JS::GCReason::NO_REASON;
   }
   JS::GCReason minorGCTriggerReason() const { return minorGCTriggerReason_; }
   void clearMinorGCRequest() {
     minorGCTriggerReason_ = JS::GCReason::NO_REASON;
   }
 
-  bool needIdleTimeCollection() const;
+  bool shouldCollect() const;
 
   bool enableProfiling() const { return enableProfiling_; }
 
   bool addMapWithNurseryMemory(MapObject* obj) {
     MOZ_ASSERT_IF(!mapsWithNurseryMemory_.empty(),
                   mapsWithNurseryMemory_.back() != obj);
     return mapsWithNurseryMemory_.append(obj);
   }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1154,22 +1154,22 @@ JS_PUBLIC_API bool JS_AddExtraGCRootsTra
 JS_PUBLIC_API void JS_RemoveExtraGCRootsTracer(JSContext* cx,
                                                JSTraceDataOp traceOp,
                                                void* data) {
   return cx->runtime()->gc.removeBlackRootsTracer(traceOp, data);
 }
 
 JS_PUBLIC_API bool JS::IsIdleGCTaskNeeded(JSRuntime* rt) {
   // Currently, we only collect nursery during idle time.
-  return rt->gc.nursery().needIdleTimeCollection();
+  return rt->gc.nursery().shouldCollect();
 }
 
 JS_PUBLIC_API void JS::RunIdleTimeGCTask(JSRuntime* rt) {
   gc::GCRuntime& gc = rt->gc;
-  if (gc.nursery().needIdleTimeCollection()) {
+  if (gc.nursery().shouldCollect()) {
     gc.minorGC(JS::GCReason::IDLE_TIME_COLLECTION);
   }
 }
 
 JS_PUBLIC_API void JS_GC(JSContext* cx, JS::GCReason reason) {
   AssertHeapIsIdle();
   JS::PrepareForFullGC(cx);
   cx->runtime()->gc.gc(GC_NORMAL, reason);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/TypedArray/bug1526838.js
@@ -0,0 +1,10 @@
+// |reftest| skip-if(!this.hasOwnProperty("BigInt"))
+
+const testArray = [1n];
+for (const constructor of anyTypedArrayConstructors) {
+    assertThrows(() => new constructor(testArray), TypeError);
+    assertThrows(() => new constructor(testArray.values()), TypeError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/TypedArrayObject-inl.h
+++ b/js/src/vm/TypedArrayObject-inl.h
@@ -609,17 +609,17 @@ class ElementSpecific {
     MOZ_ASSERT(!v.isMagic());
 
     if (MOZ_LIKELY(canConvertInfallibly(v))) {
       *result = infallibleValueToNative(v);
       return true;
     }
 
     double d;
-    MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol());
+    MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol() || IF_BIGINT(v.isBigInt(), false));
     if (!(v.isString() ? StringToNumber(cx, v.toString(), &d)
                        : ToNumber(cx, v, &d))) {
       return false;
     }
 
     *result = doubleToNative(d);
     return true;
   }
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6562,40 +6562,19 @@ nsresult PresShell::EventHandler::Handle
     }
 
     // Only capture mouse events and pointer events.
     nsCOMPtr<nsIContent> pointerCapturingContent =
         PointerEventHandler::GetPointerCapturingContent(aGUIEvent);
 
     if (pointerCapturingContent) {
       rootFrameToHandleEvent = pointerCapturingContent->GetPrimaryFrame();
-
       if (!rootFrameToHandleEvent) {
-        RefPtr<PresShell> shell =
-            PresShell::GetShellForEventTarget(nullptr, pointerCapturingContent);
-        if (!shell) {
-          // If we can't process event for the capturing content, release
-          // the capture.
-          PointerEventHandler::ReleaseIfCaptureByDescendant(
-              pointerCapturingContent);
-          return NS_OK;
-        }
-
-        nsCOMPtr<nsIContent> overrideClickTarget =
-            GetOverrideClickTarget(aGUIEvent, aFrame);
-
-        // Dispatch events to the capturing content even it's frame is
-        // destroyed.
-        PointerEventHandler::DispatchPointerFromMouseOrTouch(
-            shell, nullptr, pointerCapturingContent, aGUIEvent, false,
-            aEventStatus, nullptr);
-
-        return shell->HandleEventWithTarget(
-            aGUIEvent, nullptr, pointerCapturingContent, aEventStatus, true,
-            nullptr, overrideClickTarget);
+        return HandleEventWithPointerCapturingContentWithoutItsFrame(
+            aFrame, aGUIEvent, pointerCapturingContent, aEventStatus);
       }
     }
 
     WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
     bool isWindowLevelMouseExit =
         (aGUIEvent->mMessage == eMouseExitFromWidget) &&
         (mouseEvent && mouseEvent->mExitFrom == WidgetMouseEvent::eTopLevel);
 
@@ -7443,16 +7422,57 @@ PresShell::EventHandler::ComputeRootFram
 
   // scrollable frames should use the scrolling container as the root instead
   // of the document
   nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
   return scrollFrame ? scrollFrame->GetScrolledFrame()
                      : aRootFrameToHandleEvent;
 }
 
+nsresult
+PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
+    nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+    nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
+  MOZ_ASSERT(aGUIEvent);
+  MOZ_ASSERT(aPointerCapturingContent);
+  MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
+             "Handle the event with frame rather than only with the content");
+  MOZ_ASSERT(aEventStatus);
+
+  RefPtr<PresShell> presShellForCapturingContent =
+      PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
+  if (!presShellForCapturingContent) {
+    // If we can't process event for the capturing content, release
+    // the capture.
+    PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIContent> overrideClickTarget =
+      GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
+
+  // Dispatch events to the capturing content even it's frame is
+  // destroyed.
+  PointerEventHandler::DispatchPointerFromMouseOrTouch(
+      presShellForCapturingContent, nullptr, aPointerCapturingContent,
+      aGUIEvent, false, aEventStatus, nullptr);
+
+  if (presShellForCapturingContent == mPresShell) {
+    return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
+                                 aEventStatus, true, nullptr,
+                                 overrideClickTarget);
+  }
+
+  EventHandler eventHandlerForCapturingContent(
+      std::move(presShellForCapturingContent));
+  return eventHandlerForCapturingContent.HandleEventWithTarget(
+      aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
+      overrideClickTarget);
+}
+
 Document* PresShell::GetPrimaryContentDocument() {
   nsPresContext* context = GetPresContext();
   if (!context || !context->IsRoot()) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
   if (!shellAsTreeItem) {
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -495,16 +495,18 @@ class PresShell final : public nsIPresSh
   /**
    * EventHandler is implementation of nsIPresShell::HandleEvent().
    */
   class MOZ_STACK_CLASS EventHandler final {
    public:
     EventHandler() = delete;
     EventHandler(const EventHandler& aOther) = delete;
     explicit EventHandler(PresShell& aPresShell) : mPresShell(aPresShell) {}
+    explicit EventHandler(RefPtr<PresShell>&& aPresShell)
+        : mPresShell(aPresShell.forget()) {}
 
     /**
      * HandleEvent() may dispatch aGUIEvent.  This may redirect the event to
      * another PresShell, or the event may be handled by other classes like
      * AccessibleCaretEventHub, or discarded.
      *
      * @param aFrame                    aFrame of nsIPresShell::HandleEvent().
      *                                  (Perhaps, should be root frame of
@@ -792,16 +794,35 @@ class PresShell final : public nsIPresSh
      *                                          aRootFrameToHandleEvent.
      *                                          I.e., never returns nullptr.
      */
     nsIFrame* ComputeRootFrameToHandleEventWithCapturingContent(
         nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
         bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted);
 
     /**
+     * HandleEventWithPointerCapturingContentWithoutItsFrame() handles
+     * aGUIEvent with aPointerCapturingContent when it does not have primary
+     * frame.
+     *
+     * @param aFrameForPresShell        The frame for mPresShell.  Typically,
+     *                                  aFrame of HandleEvent().
+     * @param aGUIEvent                 The handling event.
+     * @param aPointerCapturingContent  Current pointer capturing content.
+     *                                  Must not be nullptr.
+     * @param aEventStatus              [in/out] The event status of aGUIEvent.
+     * @return                          Basically, result of
+     *                                  HandeEventWithTraget().
+     */
+    MOZ_CAN_RUN_SCRIPT
+    nsresult HandleEventWithPointerCapturingContentWithoutItsFrame(
+        nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+        nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus);
+
+    /**
      * XXX Needs better name.
      * HandleEventInternal() dispatches aEvent into the DOM tree and
      * notify EventStateManager of that.
      *
      * @param aEvent                    Event to be dispatched.
      * @param aEventStatus              [in/out] EventStatus of aEvent.
      * @param aIsHandlingNativeEvent    true if aGUIEvent represents a native
      *                                  event.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java
@@ -343,18 +343,42 @@ public final class GeckoProfile {
         if (profileName == null) {
             throw new IllegalArgumentException("Unable to create GeckoProfile for empty profile name.");
         }
 
         mName = profileName;
         mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
 
         mProfileDir = profileDir;
-        if (profileDir != null && !profileDir.isDirectory()) {
-            throw new IllegalArgumentException("Profile directory must exist if specified.");
+        if (profileDir != null) {
+            if (!profileDir.isDirectory()) {
+                throw new IllegalArgumentException("Profile directory must exist if specified: " +
+                        profileDir.getPath());
+            }
+
+            // Ensure that we can write to the profile directory.
+            //
+            // We would use `writeFile`, but that function just logs exceptions; we need them to
+            // provide useful feedback.
+            FileWriter fileWriter = null;
+            try {
+                fileWriter = new FileWriter(new File(profileDir, ".can-write-sentinel"), false);
+                fileWriter.write(0);
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Profile directory must be writable if specified: " +
+                        profileDir.getPath(), e);
+            } finally {
+                try {
+                    if (fileWriter != null) {
+                        fileWriter.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(LOGTAG, "Error closing .can-write-sentinel; ignoring", e);
+                }
+            }
         }
     }
 
     /**
      * Return the custom data object associated with this profile, which was set by the
      * previous {@link #setData(Object)} call. This association is valid for the duration
      * of the process lifetime. The caller must ensure proper synchronization, typically
      * by synchronizing on the object returned by {@link #getLock()}.
--- a/python/mozboot/mozboot/archlinux.py
+++ b/python/mozboot/mozboot/archlinux.py
@@ -65,17 +65,16 @@ class ArchlinuxBootstrapper(NodeInstall,
 
     MOBILE_ANDROID_COMMON_PACKAGES = [
         # It would be nice to handle alternative JDKs.  See
         # https://wiki.archlinux.org/index.php/Java.
         'jdk8-openjdk',
         # For downloading the Android SDK and NDK.
         'wget',
         # See comment about 32 bit binaries and multilib below.
-        'multilib/lib32-libstdc++5',
         'multilib/lib32-ncurses',
         'multilib/lib32-readline',
         'multilib/lib32-zlib',
     ]
 
     def __init__(self, version, dist_id, **kwargs):
         print('Using an experimental bootstrapper for Archlinux.')
         BaseBootstrapper.__init__(self, **kwargs)
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/screen-capture/feature-policy.https.html.ini
@@ -0,0 +1,7 @@
+[feature-policy.https.sub.html]
+  [Default "display-capture" feature policy ["self"\] disallows cross-origin iframes.]
+    expected: FAIL
+
+[feature-policy.https.html]
+  [Default "display-capture" feature policy ["self"\] disallows cross-origin iframes.]
+    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/screen-capture/feature-policy.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/get-host-info.sub.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script>
+  'use strict';
+
+  async function gDM({audio, video}) {
+    let stream;
+    try {
+      stream = await navigator.mediaDevices.getDisplayMedia({audio, video});
+      if (stream.getVideoTracks().length == 0) {
+        throw {name: `requested video track must be present with ` +
+                     `audio ${audio} and video ${video}, or fail`};
+      }
+    } finally {
+      if (stream) {
+        stream.getTracks().forEach(track => track.stop());
+      }
+    }
+  }
+
+  const cross_domain = get_host_info().HTTPS_REMOTE_ORIGIN;
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'display-capture',
+    'NotAllowedError',
+    async () => {
+      await gDM({video: true});
+      await gDM({audio: true, video: true});
+      await gDM({audio: true});
+    }
+  );
+  </script>
+</body>
new file mode 100644
--- /dev/null
+++ b/toolkit/actors/PictureInPictureChild.jsm
@@ -0,0 +1,122 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["PictureInPictureChild"];
+
+const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
+
+var gWeakVideo = null;
+
+class PictureInPictureChild extends ActorChild {
+  handleEvent(event) {
+    switch (event.type) {
+      case "MozTogglePictureInPicture": {
+        this.togglePictureInPicture(event.target);
+        break;
+      }
+    }
+  }
+
+  togglePictureInPicture(video) {
+    if (this.inPictureInPicture(video)) {
+      this.closePictureInPicture(video);
+    } else {
+      this.requestPictureInPicture(video);
+    }
+  }
+
+  inPictureInPicture(video) {
+    return gWeakVideo && gWeakVideo.get() === video;
+  }
+
+  closePictureInPicture() {
+    this.mm.sendAsyncMessage("PictureInPicture:Close", {
+      browingContextId: this.docShell.browsingContext.id,
+    });
+  }
+
+  requestPictureInPicture(video) {
+    gWeakVideo = Cu.getWeakReference(video);
+    this.mm.sendAsyncMessage("PictureInPicture:Request", {
+      videoHeight: video.videoHeight,
+      videoWidth: video.videoWidth,
+    });
+  }
+
+  receiveMessage(message) {
+    switch (message.name) {
+      case "PictureInPicture:SetupPlayer": {
+        this.setupPlayer();
+        break;
+      }
+    }
+  }
+
+  async setupPlayer() {
+    if (!gWeakVideo) {
+      this.closePictureInPicture();
+    }
+
+    let originatingVideo = gWeakVideo.get();
+    if (!originatingVideo) {
+      this.closePictureInPicture();
+    }
+
+    let webProgress = this.mm
+                          .docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIWebProgress);
+    if (webProgress.isLoadingDocument) {
+      await new Promise(resolve => {
+        this.mm.addEventListener("load", resolve, {
+          once: true,
+          mozSystemGroup: true,
+          capture: true,
+        });
+      });
+    }
+
+    let stream = originatingVideo.mozCaptureStream();
+
+    let doc = this.content.document;
+    let playerVideo = doc.createElement("video");
+    playerVideo.srcObject = stream;
+    playerVideo.removeAttribute("controls");
+    playerVideo.setAttribute("autoplay", "true");
+
+    // Mute the video and rely on the originating video's audio playback.
+    // This way, we sidestep the AutoplayPolicy blocking stuff.
+    playerVideo.muted = true;
+
+    // Force the player video to assume maximum height and width of the
+    // containing window
+    playerVideo.style.height = "100vh";
+    playerVideo.style.width = "100vw";
+
+    // And now try to get rid of as much surrounding whitespace as possible.
+    playerVideo.style.margin = "0";
+    doc.body.style.overflow = "hidden";
+    doc.body.style.margin = "0";
+
+    playerVideo.play();
+
+    // A little hack to make the current frame show up in the player
+    if (originatingVideo.paused) {
+      await originatingVideo.play();
+      await originatingVideo.pause();
+    }
+
+    doc.body.appendChild(playerVideo);
+
+    let originatingWindow = originatingVideo.ownerGlobal;
+    originatingWindow.addEventListener("unload", (e) => {
+      this.closePictureInPicture(originatingVideo);
+    }, { once: true });
+
+    this.content.addEventListener("unload", () => {
+      gWeakVideo = null;
+    }, { once: true });
+  }
+}
--- a/toolkit/actors/moz.build
+++ b/toolkit/actors/moz.build
@@ -35,8 +35,13 @@ FINAL_TARGET_FILES.actors += [
     'SelectionSourceChild.jsm',
     'ThumbnailsChild.jsm',
     'UAWidgetsChild.jsm',
     'UnselectedTabHoverChild.jsm',
     'WebChannelChild.jsm',
     'WebNavigationChild.jsm',
     'ZoomChild.jsm',
 ]
+
+if CONFIG['NIGHTLY_BUILD']:
+    FINAL_TARGET_FILES.actors += [
+        'PictureInPictureChild.jsm',
+    ]
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -82,16 +82,19 @@ DIRS += [
 ]
 
 if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
     DIRS += ['narrate'];
 
     if CONFIG['NS_PRINTING']:
         DIRS += ['printing']
 
+    if CONFIG['NIGHTLY_BUILD']:
+        DIRS += ['pictureinpicture']
+
 if CONFIG['BUILD_CTYPES']:
     DIRS += ['ctypes']
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['autocomplete', 'printingui', 'satchel']
 
 if CONFIG['MOZ_TOOLKIT_SEARCH']:
     DIRS += ['search']
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/PictureInPicture.jsm
@@ -0,0 +1,167 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["PictureInPicture"];
+
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const PLAYER_URI = "chrome://global/content/pictureinpicture/player.xhtml";
+const PLAYER_FEATURES = `chrome,titlebar=no,alwaysontop,resizable`;
+const WINDOW_TYPE = "Toolkit:PictureInPicture";
+
+/**
+ * This module is responsible for creating a Picture in Picture window to host
+ * a clone of a video element running in web content.
+ */
+
+var PictureInPicture = {
+  // Listeners are added in nsBrowserGlue.js lazily
+  receiveMessage(aMessage) {
+    let browser = aMessage.target;
+
+    switch (aMessage.name) {
+      case "PictureInPicture:Request": {
+        let videoData = aMessage.data;
+        this.handlePictureInPictureRequest(browser, videoData);
+        break;
+      }
+      case "PictureInPicture:Close": {
+        /**
+         * Content has requested that its Picture in Picture window go away.
+         */
+        this.closePipWindow();
+        break;
+      }
+    }
+  },
+
+  /**
+   * Find and close any pre-existing Picture in Picture windows.
+   */
+  closePipWindow() {
+    // This uses an enumerator, but there really should only be one of
+    // these things.
+    for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
+      if (win.closed) {
+        continue;
+      }
+
+      win.close();
+    }
+  },
+
+  /**
+   * A request has come up from content to open a Picture in Picture
+   * window.
+   *
+   * @param browser (xul:browser)
+   *   The browser that is requesting the Picture in Picture window.
+   *
+   * @param videoData (object)
+   *   An object containing the following properties:
+   *
+   *   videoHeight (int):
+   *     The preferred height of the video.
+   *
+   *   videoWidth (int):
+   *     The preferred width of the video.
+   *
+   * @returns Promise
+   *   Resolves once the Picture in Picture window has been created, and
+   *   the player component inside it has finished loading.
+   */
+  async handlePictureInPictureRequest(browser, videoData) {
+    let parentWin = browser.ownerGlobal;
+    this.closePipWindow();
+    let win = await this.openPipWindow(parentWin, videoData);
+    win.setupPlayer(browser, videoData);
+  },
+
+  /**
+   * Open a Picture in Picture window on the same screen as parentWin,
+   * sized based on the information in videoData.
+   *
+   * @param parentWin (chrome window)
+   *   The window hosting the browser that requested the Picture in
+   *   Picture window.
+   *
+   * @param videoData (object)
+   *   An object containing the following properties:
+   *
+   *   videoHeight (int):
+   *     The preferred height of the video.
+   *
+   *   videoWidth (int):
+   *     The preferred width of the video.
+   *
+   * @returns Promise
+   *   Resolves once the window has opened and loaded the player component.
+   */
+  async openPipWindow(parentWin, videoData) {
+    let { videoHeight, videoWidth } = videoData;
+
+    // The Picture in Picture window will open on the same display as the
+    // originating window, and anchor to the bottom right.
+    let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
+                          .getService(Ci.nsIScreenManager);
+    let screen = screenManager.screenForRect(parentWin.screenX,
+                                             parentWin.screenY, 1, 1);
+
+    // Now that we have the right screen, let's see how much available
+    // real-estate there is for us to work with.
+    let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
+    screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth,
+                                  screenHeight);
+
+    // For now, the Picture in Picture window will be a maximum of a quarter
+    // of the screen height, and a third of the screen width.
+    const MAX_HEIGHT = screenHeight.value / 4;
+    const MAX_WIDTH = screenWidth.value / 3;
+
+    let resultWidth = videoWidth;
+    let resultHeight = videoHeight;
+
+    if (videoHeight > MAX_HEIGHT || videoWidth > MAX_WIDTH) {
+      let aspectRatio = videoWidth / videoHeight;
+      // We're bigger than the max - take the largest dimension and clamp
+      // it to the associated max. Recalculate the other dimension to maintain
+      // aspect ratio.
+      if (videoWidth >= videoHeight) {
+        // We're clamping the width, so the height must be adjusted to match
+        // the original aspect ratio. Since aspect ratio is width over height,
+        // that means we need to _divide_ the MAX_WIDTH by the aspect ratio to
+        // calculate the appropriate height.
+        resultWidth = MAX_WIDTH;
+        resultHeight = Math.floor(MAX_WIDTH / aspectRatio);
+      } else {
+        // We're clamping the height, so the width must be adjusted to match
+        // the original aspect ratio. Since aspect ratio is width over height,
+        // this means we need to _multiply_ the MAX_HEIGHT by the aspect ratio
+        // to calculate the appropriate width.
+        resultHeight = MAX_HEIGHT;
+        resultWidth = Math.floor(MAX_HEIGHT * aspectRatio);
+      }
+    }
+
+    // Now that we have the dimensions of the video, we need to figure out how
+    // to position it in the bottom right corner. Since we know the width of the
+    // available rect, we need to subtract the dimensions of the window we're
+    // opening to get the top left coordinates that openWindow expects.
+    let pipLeft = screenWidth.value - resultWidth;
+    let pipTop = screenHeight.value - resultHeight;
+    let features = `${PLAYER_FEATURES},top=${pipTop},left=${pipLeft},` +
+                   `width=${resultWidth},height=${resultHeight}`;
+
+    let pipWindow =
+      Services.ww.openWindow(parentWin, PLAYER_URI, null, features, null);
+
+    return new Promise(resolve => {
+      pipWindow.addEventListener("load", () => {
+        resolve(pipWindow);
+      }, { once: true });
+    });
+  },
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/content/player.js
@@ -0,0 +1,30 @@
+/* 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/. */
+
+async function setupPlayer(originatingBrowser, videoData) {
+  window.windowUtils.setChromeMargin(0, 0, 0, 0);
+  let holder = document.querySelector(".player-holder");
+  let browser = document.getElementById("browser");
+  browser.remove();
+
+  browser.setAttribute("nodefaultsrc", "true");
+  browser.sameProcessAsFrameLoader = originatingBrowser.frameLoader;
+  holder.appendChild(browser);
+
+  browser.loadURI("about:blank", {
+    triggeringPrincipal: originatingBrowser.contentPrincipal,
+  });
+
+  let mm = browser.frameLoader.messageManager;
+  mm.sendAsyncMessage("PictureInPicture:SetupPlayer");
+
+  // If the content process hosting the video crashes, let's
+  // just close the window for now.
+  browser.addEventListener("oop-browser-crashed", () => {
+    window.close();
+  });
+
+  await window.promiseDocumentFlushed(() => {});
+  browser.style.MozWindowDragging = "drag";
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/content/player.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+      windowtype="Toolkit:PictureInPicture">
+  <head>
+    <meta charset="utf-8"/>
+    <link rel="stylesheet" type="text/css"
+          href="chrome://global/skin/pictureinpicture/player.css"/>
+    <script type="application/javascript"
+            src="chrome://global/content/pictureinpicture/player.js"></script>
+  </head>
+
+  <body>
+    <div class="player-holder">
+      <xul:browser type="content" primary="true" remote="true" remoteType="web" id="browser"></xul:browser>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/jar.mn
@@ -0,0 +1,8 @@
+#filter substitution
+# 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/.
+
+toolkit.jar:
+  content/global/pictureinpicture/player.xhtml   (content/player.xhtml)
+  content/global/pictureinpicture/player.js      (content/player.js)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+    'PictureInPicture.jsm',
+]
--- a/toolkit/modules/ActorManagerParent.jsm
+++ b/toolkit/modules/ActorManagerParent.jsm
@@ -92,16 +92,17 @@
  * If Fission is being simulated, and an actor needs to receive events from
  * sub-frames, it must use "allFrames".
  */
 
 var EXPORTED_SYMBOLS = ["ActorManagerParent"];
 
 const {ExtensionUtils} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 const {DefaultMap} = ExtensionUtils;
 
 let ACTORS = {
   AudioPlayback: {
     child: {
       module: "resource://gre/actors/AudioPlaybackChild.jsm",
       messages: [
@@ -332,16 +333,31 @@ let ACTORS = {
       messages: [
         "FullZoom",
         "TextZoom",
       ],
     },
   },
 };
 
+if (AppConstants.NIGHTLY_BUILD) {
+  ACTORS.PictureInPicture = {
+    child: {
+      module: "resource://gre/actors/PictureInPictureChild.jsm",
+      events: {
+        "MozTogglePictureInPicture": {capture: true, wantUntrusted: true},
+      },
+
+      messages: [
+        "PictureInPicture:SetupPlayer",
+      ],
+    },
+  };
+}
+
 class ActorSet {
   constructor(group, actorSide) {
     this.group = group;
     this.actorSide = actorSide;
 
     this.actors = new Map();
     this.events = [];
     this.messages = new DefaultMap(() => []);
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -100,8 +100,9 @@ toolkit.jar:
 #endif
   skin/classic/global/plugins/plugin.svg                    (../../shared/plugins/plugin.svg)
   skin/classic/global/plugins/plugin-blocked.svg            (../../shared/plugins/plugin-blocked.svg)
   skin/classic/global/plugins/pluginGeneric.svg             (../../shared/extensions/category-plugins.svg)
   skin/classic/global/plugins/pluginProblem.css             (../../shared/plugins/pluginProblem.css)
   skin/classic/global/plugins/contentPluginBlocked.png      (../../shared/plugins/contentPluginBlocked.png)
   skin/classic/global/plugins/contentPluginCrashed.png      (../../shared/plugins/contentPluginCrashed.png)
   skin/classic/global/plugins/contentPluginStripe.png       (../../shared/plugins/contentPluginStripe.png)
+  skin/classic/global/pictureinpicture/player.css           (../../shared/pictureinpicture/player.css)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/pictureinpicture/player.css
@@ -0,0 +1,18 @@
+/* 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/. */
+
+body {
+  margin: 0;
+}
+
+.player-holder {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+}
+
+browser {
+  flex: 1;
+}