Merge mozilla-central to autoland a=merge on a CLOSED TREE
authorCoroiu Cristina <ccoroiu@mozilla.com>
Mon, 06 May 2019 00:42:50 +0300
changeset 472619 e86a75908fd3f530563496cee8777f0e64e3fd20
parent 472618 25bc2e11f12fa6b52e8783dd468f72cfeda3f025 (current diff)
parent 472601 1e3244e602fc0373508049b6fe544332f800f033 (diff)
child 472620 1d35f8d88bdd007e01d42c4ff76c6d10d7c01a98
push id35969
push userccoroiu@mozilla.com
push dateMon, 06 May 2019 04:24:23 +0000
treeherdermozilla-central@f0566d1a9ab1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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 autoland a=merge on a CLOSED TREE
gfx/thebes/gfxPlatform.cpp
testing/web-platform/meta/2dcontext/shadows/2d.shadow.enable.x.html.ini
testing/web-platform/meta/css/css-contain/contain-size-multicol-001.html.ini
testing/web-platform/meta/performance-timeline/po-observer.html.ini
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 2.2.154
+Current extension version is: 2.2.160
 
-Taken from upstream commit: 762c58e0
+Taken from upstream commit: 155304a0
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.2.154';
-var pdfjsBuild = '762c58e0';
+var pdfjsVersion = '2.2.160';
+var pdfjsBuild = '155304a0';
 
 var pdfjsSharedUtil = __w_pdfjs_require__(1);
 
 var pdfjsDisplayAPI = __w_pdfjs_require__(6);
 
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(18);
 
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(19);
@@ -1298,17 +1298,17 @@ function _fetchDocument(worker, source, 
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
     source.progressiveDone = pdfDataRangeTransport.progressiveDone;
   }
 
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.2.154',
+    apiVersion: '2.2.160',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -3092,19 +3092,19 @@ const InternalRenderTask = function Inte
       }
     }
 
   }
 
   return InternalRenderTask;
 }();
 
-const version = '2.2.154';
+const version = '2.2.160';
 exports.version = version;
-const build = '762c58e0';
+const build = '155304a0';
 exports.build = build;
 
 /***/ }),
 /* 7 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-const pdfjsVersion = '2.2.154';
-const pdfjsBuild = '762c58e0';
+const pdfjsVersion = '2.2.160';
+const pdfjsBuild = '155304a0';
 
 const pdfjsCoreWorker = __w_pdfjs_require__(1);
 
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
@@ -373,17 +373,17 @@ var WorkerMessageHandler = {
 
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     const verbosity = (0, _util.getVerbosityLevel)();
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.2.154';
+    let workerVersion = '2.2.160';
 
     if (apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
     }
 
     var docId = docParams.docId;
     var docBaseUrl = docParams.docBaseUrl;
     var workerHandlerName = docParams.docId + '_worker';
@@ -19611,32 +19611,34 @@ var NullOptimizer = function NullOptimiz
   }
 
   NullOptimizer.prototype = {
     push(fn, args) {
       this.queue.fnArray.push(fn);
       this.queue.argsArray.push(args);
     },
 
-    flush() {}
+    flush() {},
+
+    reset() {}
 
   };
   return NullOptimizer;
 }();
 
 var OperatorList = function OperatorListClosure() {
   var CHUNK_SIZE = 1000;
   var CHUNK_SIZE_ABOUT = CHUNK_SIZE - 5;
 
   function OperatorList(intent, messageHandler, pageIndex) {
     this.messageHandler = messageHandler;
     this.fnArray = [];
     this.argsArray = [];
 
-    if (messageHandler && this.intent !== 'oplist') {
+    if (messageHandler && intent !== 'oplist') {
       this.optimizer = new QueueOptimizer(this);
     } else {
       this.optimizer = new NullOptimizer(this);
     }
 
     this.dependencies = Object.create(null);
     this._totalLength = 0;
     this.pageIndex = pageIndex;
@@ -20549,31 +20551,42 @@ var PartialEvaluator = function PartialE
           var xrefFontStats = xref.stats.fontTypes;
           xrefFontStats[fontType] = true;
         } catch (ex) {}
 
         fontCapability.resolve(new TranslatedFont(font.loadedName, new _fonts.ErrorFont(reason instanceof Error ? reason.message : reason), font));
       });
       return fontCapability.promise;
     },
-    buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) {
+
+    buildPath(operatorList, fn, args, parsingText = false) {
       var lastIndex = operatorList.length - 1;
 
       if (!args) {
         args = [];
       }
 
       if (lastIndex < 0 || operatorList.fnArray[lastIndex] !== _util.OPS.constructPath) {
+        if (parsingText) {
+          (0, _util.warn)(`Encountered path operator "${fn}" inside of a text object.`);
+          operatorList.addOp(_util.OPS.save, null);
+        }
+
         operatorList.addOp(_util.OPS.constructPath, [[fn], args]);
+
+        if (parsingText) {
+          operatorList.addOp(_util.OPS.restore, null);
+        }
       } else {
         var opArgs = operatorList.argsArray[lastIndex];
         opArgs[0].push(fn);
         Array.prototype.push.apply(opArgs[1], args);
       }
     },
+
     handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args, cs, patterns, resources, task) {
       var patternName = args[args.length - 1];
       var pattern;
 
       if ((0, _primitives.isName)(patternName) && (pattern = patterns.get(patternName.name))) {
         var dict = (0, _primitives.isStream)(pattern) ? pattern.dict : pattern;
         var typeNum = dict.get('PatternType');
 
@@ -20606,16 +20619,17 @@ var PartialEvaluator = function PartialE
       initialState = initialState || new EvalState();
 
       if (!operatorList) {
         throw new Error('getOperatorList: missing "operatorList" parameter');
       }
 
       var self = this;
       var xref = this.xref;
+      let parsingText = false;
       var imageCache = Object.create(null);
 
       var xobjs = resources.get('XObject') || _primitives.Dict.empty;
 
       var patterns = resources.get('Pattern') || _primitives.Dict.empty;
 
       var stateManager = new StateManager(initialState);
       var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
@@ -20728,16 +20742,24 @@ var PartialEvaluator = function PartialE
             case _util.OPS.setFont:
               var fontSize = args[1];
               next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state).then(function (loadedName) {
                 operatorList.addDependency(loadedName);
                 operatorList.addOp(_util.OPS.setFont, [loadedName, fontSize]);
               }));
               return;
 
+            case _util.OPS.beginText:
+              parsingText = true;
+              break;
+
+            case _util.OPS.endText:
+              parsingText = false;
+              break;
+
             case _util.OPS.endInlineImage:
               var cacheKey = args[0].cacheKey;
 
               if (cacheKey) {
                 var cacheEntry = imageCache[cacheKey];
 
                 if (cacheEntry !== undefined) {
                   operatorList.addOp(cacheEntry.fn, cacheEntry.args);
@@ -20909,21 +20931,18 @@ var PartialEvaluator = function PartialE
               return;
 
             case _util.OPS.moveTo:
             case _util.OPS.lineTo:
             case _util.OPS.curveTo:
             case _util.OPS.curveTo2:
             case _util.OPS.curveTo3:
             case _util.OPS.closePath:
-              self.buildPath(operatorList, fn, args);
-              continue;
-
             case _util.OPS.rectangle:
-              self.buildPath(operatorList, fn, args);
+              self.buildPath(operatorList, fn, args, parsingText);
               continue;
 
             case _util.OPS.markPoint:
             case _util.OPS.markPointProps:
             case _util.OPS.beginMarkedContent:
             case _util.OPS.beginMarkedContentProps:
             case _util.OPS.endMarkedContent:
             case _util.OPS.beginCompat:
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -1480,17 +1480,19 @@ let PDFViewerApplication = {
 
     _boundEvents.windowAfterPrint = () => {
       eventBus.dispatch('afterprint', {
         source: window
       });
     };
 
     window.addEventListener('visibilitychange', webViewerVisibilityChange);
-    window.addEventListener('wheel', webViewerWheel);
+    window.addEventListener('wheel', webViewerWheel, {
+      passive: false
+    });
     window.addEventListener('click', webViewerClick);
     window.addEventListener('keydown', webViewerKeyDown);
     window.addEventListener('resize', _boundEvents.windowResize);
     window.addEventListener('hashchange', _boundEvents.windowHashChange);
     window.addEventListener('beforeprint', _boundEvents.windowBeforePrint);
     window.addEventListener('afterprint', _boundEvents.windowAfterPrint);
   },
 
--- a/browser/extensions/pdfjs/moz.yaml
+++ b/browser/extensions/pdfjs/moz.yaml
@@ -15,15 +15,15 @@ origin:
   description: Portable Document Format (PDF) viewer that is built with HTML5
 
   # Full URL for the package's homepage/etc
   # Usually different from repository url
   url: https://github.com/mozilla/pdf.js
 
   # Human-readable identifier for this version/release
   # Generally "version NNN", "tag SSS", "bookmark SSS"
-  release: version 2.2.154
+  release: version 2.2.160
 
   # The package's license, where possible using the mnemonic from
   # https://spdx.org/licenses/
   # Multiple licenses can be specified (as a YAML list)
   # A "LICENSE" file must exist containing the full license text
   license: Apache-2.0
--- a/dom/base/test/browser_bug593387.js
+++ b/dom/base/test/browser_bug593387.js
@@ -3,17 +3,17 @@
  * Loads a chrome document in a content docshell and then inserts a
  * X-Frame-Options: DENY iframe into the document and verifies that the document
  * loads. The policy we are enforcing is outlined here:
  * https://bugzilla.mozilla.org/show_bug.cgi?id=593387#c17
 */
 
 add_task(async function test() {
   await BrowserTestUtils.withNewTab({ gBrowser,
-                                      url: "chrome://global/content/aboutProfiles.xhtml" },
+                                      url: "chrome://global/content/mozilla.xhtml" },
                                      async function(newBrowser) {
     // NB: We load the chrome:// page in the parent process.
     await testXFOFrameInChrome(newBrowser);
 
     // Run next test (try the same with a content top-level context)
     await BrowserTestUtils.loadURI(newBrowser, "http://example.com/");
     await BrowserTestUtils.browserLoaded(newBrowser);
 
--- a/dom/base/test/test_blocking_image.html
+++ b/dom/base/test/test_blocking_image.html
@@ -48,16 +48,23 @@ function onLoad() {
 
     // image from HTTP should be blocked.
     img.src = "http://example.com/tests/image/test/mochitest/shaver.png";
     doc.body.appendChild(img);
 
     imgListener(img).then(() => {
       ok(true, "img shouldn't be loaded");
 
+      // `iframe` is a content iframe, and thus not in the same docgroup with
+      // us, which are a chrome-privileged test.
+      //
+      // Ensure the frame is laid out so that this cross-origin
+      // getComputedStyle call is guaranteed to work after bug 1440537.
+      iframe.getBoundingClientRect();
+
       // We can't use matches(":-moz-suppressed") here, as -moz-suppressed is
       // chrome-only, however now we are in a content iframe.
       is(iframe.contentWindow.getComputedStyle(img).color, "rgb(0, 128, 0)",
          "color of img should be green");
       is(img.imageBlockingStatus, Ci.nsIContentPolicy.REJECT_SERVER,
          "imageBlockingStatues should be REJECT_SERVER.");
 
       img2.src = "https://test.invalid";
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -186,17 +186,18 @@ static const uint32_t kUsageFileCookie =
  */
 const uint32_t kFlushTimeoutMs = 5000;
 
 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
 
 const uint32_t kDefaultNextGen = false;
 const uint32_t kDefaultOriginLimitKB = 5 * 1024;
 const uint32_t kDefaultShadowWrites = true;
-const uint32_t kDefaultSnapshotPrefill = 4096;
+const uint32_t kDefaultSnapshotPrefill = 16384;
+const uint32_t kDefaultSnapshotGradualPrefill = 4096;
 const uint32_t kDefaultClientValidation = true;
 /**
  *
  */
 const char kNextGenPref[] = "dom.storage.next_gen";
 /**
  * LocalStorage data limit as determined by summing up the lengths of all string
  * keys and values.  This is consistent with the legacy implementation and other
@@ -216,16 +217,27 @@ const char kDefaultQuotaPref[] = "dom.st
 const char kShadowWritesPref[] = "dom.storage.shadow_writes";
 /**
  * Byte budget for sending data down to the LSSnapshot instance when it is first
  * created.  If there is less data than this (measured by tallying the string
  * length of the keys and values), all data is sent, otherwise partial data is
  * sent.  See `Snapshot`.
  */
 const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill";
+/**
+ * When a specific value is requested by an LSSnapshot that is not already fully
+ * populated, gradual prefill is used. This preference specifies the number of
+ * bytes to be used to send values beyond the specific value that is requested.
+ * (The size of the explicitly requested value does not impact this preference.)
+ * Setting the value to 0 disables gradual prefill. Tests may set this value to
+ * -1 which is converted to INT_MAX in order to cause gradual prefill to send
+ * all values not previously sent.
+ */
+const char kSnapshotGradualPrefillPref[] =
+    "dom.storage.snapshot_gradual_prefill";
 
 const char kClientValidationPref[] = "dom.storage.client_validation";
 
 /**
  * The amount of time a PreparedDatastore instance should stick around after a
  * preload is triggered in order to give time for the page to use LocalStorage
  * without triggering worst-case synchronous jank.
  */
@@ -1698,20 +1710,22 @@ class Datastore final
   void NoteFinishedDatabase(Database* aDatabase);
 
   void NoteActiveDatabase(Database* aDatabase);
 
   void NoteInactiveDatabase(Database* aDatabase);
 
   void GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
                            nsTArray<LSItemInfo>& aItemInfos,
-                           uint32_t& aTotalLength, int64_t& aInitialUsage,
-                           int64_t& aPeakUsage,
+                           uint32_t& aNextLoadIndex, uint32_t& aTotalLength,
+                           int64_t& aInitialUsage, int64_t& aPeakUsage,
                            LSSnapshot::LoadState& aLoadState);
 
+  const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }
+
   void GetItem(const nsString& aKey, nsString& aValue) const;
 
   void GetKeys(nsTArray<nsString>& aKeys) const;
 
   //////////////////////////////////////////////////////////////////////////////
   // Mutation Methods
   //
   // These are only called during Snapshot::RecvCheckpoint
@@ -1956,20 +1970,20 @@ class Snapshot final : public PBackgroun
    * The set of keys for which values have been sent to the child LSSnapshot.
    * Cleared once all values have been sent as indicated by
    * mLoadedItems.Count()==mTotalLength and therefore mLoadedAllItems should be
    * true.  No requests should be received for keys already in this set, and
    * this is enforced by fatal IPC error (unless fuzzing).
    */
   nsTHashtable<nsStringHashKey> mLoadedItems;
   /**
-   * The set of keys for which a RecvLoadItem request was received but there
-   * was no such key, and so null was returned.  The child LSSnapshot will also
-   * cache these values, so redundant requests are also handled with fatal
-   * process termination just like for mLoadedItems.  Also cleared when
+   * The set of keys for which a RecvLoadValueAndMoreItems request was received
+   * but there was no such key, and so null was returned.  The child LSSnapshot
+   * will also cache these values, so redundant requests are also handled with
+   * fatal process termination just like for mLoadedItems.  Also cleared when
    * mLoadedAllItems becomes true because then the child can infer that all
    * other values must be null.  (Note: this could also be done when
    * mLoadKeysReceived is true as a further optimization, but is not.)
    */
   nsTHashtable<nsStringHashKey> mUnknownItems;
   /**
    * Values that have changed in mDatastore as reported by SaveItem
    * notifications that are not yet known to the child LSSnapshot.
@@ -1985,16 +1999,21 @@ class Snapshot final : public PBackgroun
    * Latched state of mDatastore's keys during a SaveItem notification with
    * aAffectsOrder=true.  The ordered keys needed to be saved off so that a
    * consistent ordering could be presented to the child LSSnapshot when it asks
    * for them via RecvLoadKeys.
    */
   nsTArray<nsString> mKeys;
   nsString mDocumentURI;
   /**
+   * The index used for restoring iteration over not yet sent key/value pairs to
+   * the child LSSnapshot.
+   */
+  uint32_t mNextLoadIndex;
+  /**
    * The number of key/value pairs that were present in the Datastore at the
    * time the snapshot was created.  Once we have sent this many values to the
    * child LSSnapshot, we can infer that it has received all of the keys/values
    * and set mLoadedAllItems to true and clear mLoadedItems and mUnknownItems.
    * Note that knowing the keys/values is not the same as knowing their ordering
    * and so mKeys may be retained.
    */
   uint32_t mTotalLength;
@@ -2007,53 +2026,58 @@ class Snapshot final : public PBackgroun
   bool mSavedKeys;
   bool mActorDestroyed;
   bool mFinishReceived;
   bool mLoadedReceived;
   /**
    * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or
    * LoadState::AllUnorderedItems.  It will be AllOrderedItems if the initial
    * snapshot contained all the data or if the state was AllOrderedKeys and
-   * successive RecvLoadItem requests have resulted in the LSSnapshot being told
-   * all of the key/value pairs.  It will be AllUnorderedItems if the state was
-   * LoadState::Partial and successive RecvLoadItem requests got all the
-   * keys/values but the key ordering was not retrieved.
+   * successive RecvLoadValueAndMoreItems requests have resulted in the
+   * LSSnapshot being told all of the key/value pairs.  It will be
+   * AllUnorderedItems if the state was LoadState::Partial and successive
+   * RecvLoadValueAndMoreItem requests got all the keys/values but the key
+   * ordering was not retrieved.
    */
   bool mLoadedAllItems;
   /**
    * True if LSSnapshot's mLoadState should be LoadState::AllOrderedItems or
    * AllOrderedKeys.  This can occur because of the initial snapshot, or because
    * a RecvLoadKeys request was received.
    */
   bool mLoadKeysReceived;
   bool mSentMarkDirty;
 
  public:
   // Created in AllocPBackgroundLSSnapshotParent.
   Snapshot(Database* aDatabase, const nsAString& aDocumentURI);
 
-  void Init(nsTHashtable<nsStringHashKey>& aLoadedItems, uint32_t aTotalLength,
+  void Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
+            uint32_t aNextLoadIndex, uint32_t aTotalLength,
             int64_t aInitialUsage, int64_t aPeakUsage,
             LSSnapshot::LoadState aLoadState) {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aInitialUsage >= 0);
     MOZ_ASSERT(aPeakUsage >= aInitialUsage);
-    MOZ_ASSERT_IF(aLoadState == LSSnapshot::LoadState::AllOrderedItems,
-                  aLoadedItems.Count() == 0);
+    MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems,
+                  aNextLoadIndex < aTotalLength);
     MOZ_ASSERT(mTotalLength == 0);
     MOZ_ASSERT(mUsage == -1);
     MOZ_ASSERT(mPeakUsage == -1);
 
     mLoadedItems.SwapElements(aLoadedItems);
+    mNextLoadIndex = aNextLoadIndex;
     mTotalLength = aTotalLength;
     mUsage = aInitialUsage;
     mPeakUsage = aPeakUsage;
     if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
       mLoadKeysReceived = true;
     } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
+      MOZ_ASSERT(mLoadedItems.Count() == 0);
+      MOZ_ASSERT(mNextLoadIndex == mTotalLength);
       mLoadedReceived = true;
       mLoadedAllItems = true;
       mLoadKeysReceived = true;
     }
   }
 
   /**
    * Called via NotifySnapshots by Datastore whenever it is updating its
@@ -2080,18 +2104,19 @@ class Snapshot final : public PBackgroun
 
   mozilla::ipc::IPCResult RecvCheckpoint(
       nsTArray<LSWriteInfo>&& aWriteInfos) override;
 
   mozilla::ipc::IPCResult RecvFinish() override;
 
   mozilla::ipc::IPCResult RecvLoaded() override;
 
-  mozilla::ipc::IPCResult RecvLoadItem(const nsString& aKey,
-                                       nsString* aValue) override;
+  mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
+      const nsString& aKey, nsString* aValue,
+      nsTArray<LSItemInfo>* aItemInfos) override;
 
   mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* aKeys) override;
 
   mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aRequestedSize,
                                                 const int64_t& aMinSize,
                                                 int64_t* aSize) override;
 
   mozilla::ipc::IPCResult RecvPing() override;
@@ -2772,16 +2797,18 @@ typedef nsClassHashtable<nsCStringHashKe
     ObserverHashtable;
 
 StaticAutoPtr<ObserverHashtable> gObservers;
 
 Atomic<bool> gNextGen(kDefaultNextGen);
 Atomic<uint32_t, Relaxed> gOriginLimitKB(kDefaultOriginLimitKB);
 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
+Atomic<int32_t, Relaxed> gSnapshotGradualPrefill(
+    kDefaultSnapshotGradualPrefill);
 Atomic<bool> gClientValidation(kDefaultClientValidation);
 
 typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
 
 // Can only be touched on the Quota Manager I/O thread.
 StaticAutoPtr<UsageHashtable> gUsages;
 
 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
@@ -2959,16 +2986,33 @@ void SnapshotPrefillPrefChangedCallback(
   // The magic -1 is for use only by tests.
   if (snapshotPrefill == -1) {
     snapshotPrefill = INT32_MAX;
   }
 
   gSnapshotPrefill = snapshotPrefill;
 }
 
+void SnapshotGradualPrefillPrefChangedCallback(const char* aPrefName,
+                                               void* aClosure) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aPrefName, kSnapshotGradualPrefillPref));
+  MOZ_ASSERT(!aClosure);
+
+  int32_t snapshotGradualPrefill =
+      Preferences::GetInt(aPrefName, kDefaultSnapshotGradualPrefill);
+
+  // The magic -1 is for use only by tests.
+  if (snapshotGradualPrefill == -1) {
+    snapshotGradualPrefill = INT32_MAX;
+  }
+
+  gSnapshotGradualPrefill = snapshotGradualPrefill;
+}
+
 void ClientValidationPrefChangedCallback(const char* aPrefName,
                                          void* aClosure) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref));
   MOZ_ASSERT(!aClosure);
 
   gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation);
 }
@@ -3030,16 +3074,19 @@ void InitializeLocalStorage() {
   }
 
   Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
                                        kShadowWritesPref);
 
   Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
                                        kSnapshotPrefillPref);
 
+  Preferences::RegisterCallbackAndCall(
+      SnapshotGradualPrefillPrefChangedCallback, kSnapshotGradualPrefillPref);
+
   Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback,
                                        kClientValidationPref);
 
 #ifdef DEBUG
   gLocalStorageInitialized = true;
 #endif
 }
 
@@ -4596,16 +4643,17 @@ void Datastore::NoteInactiveDatabase(Dat
     }
 
     mPendingUsageDeltas.Clear();
   }
 }
 
 void Datastore::GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
                                     nsTArray<LSItemInfo>& aItemInfos,
+                                    uint32_t& aNextLoadIndex,
                                     uint32_t& aTotalLength,
                                     int64_t& aInitialUsage, int64_t& aPeakUsage,
                                     LSSnapshot::LoadState& aLoadState) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
 
 #ifdef DEBUG
@@ -4615,61 +4663,77 @@ void Datastore::GetSnapshotInitInfo(nsTH
     int64_t sizeOfKey = static_cast<int64_t>(item.key().Length());
     sizeOfKeys += sizeOfKey;
     sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
   }
   MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
   MOZ_ASSERT(mSizeOfItems == sizeOfItems);
 #endif
 
-  int64_t size = 0;
   if (mSizeOfKeys <= gSnapshotPrefill) {
     if (mSizeOfItems <= gSnapshotPrefill) {
       aItemInfos.AppendElements(mOrderedItems);
+
+      MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
+      aNextLoadIndex = mValues.Count();
+
       aLoadState = LSSnapshot::LoadState::AllOrderedItems;
     } else {
+      int64_t size = mSizeOfKeys;
       nsString value;
-      for (auto item : mOrderedItems) {
+      for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
+        const LSItemInfo& item = mOrderedItems[index];
+
+        const nsString& key = item.key();
+
         if (!value.IsVoid()) {
           value = item.value();
 
-          size += static_cast<int64_t>(item.key().Length()) +
-                  static_cast<int64_t>(value.Length());
+          size += static_cast<int64_t>(value.Length());
 
           if (size <= gSnapshotPrefill) {
-            aLoadedItems.PutEntry(item.key());
+            aLoadedItems.PutEntry(key);
           } else {
             value.SetIsVoid(true);
+
+            // We set value to void so that will guard against entering the
+            // parent branch during next iterations. So aNextLoadIndex is set
+            // only once.
+            aNextLoadIndex = index;
           }
         }
 
         LSItemInfo* itemInfo = aItemInfos.AppendElement();
-        itemInfo->key() = item.key();
+        itemInfo->key() = key;
         itemInfo->value() = value;
       }
 
       aLoadState = LSSnapshot::LoadState::AllOrderedKeys;
     }
   } else {
-    for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
-      const nsAString& key = iter.Key();
-      const nsString& value = iter.Data();
+    int64_t size = 0;
+    for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
+      const LSItemInfo& item = mOrderedItems[index];
+
+      const nsString& key = item.key();
+      const nsString& value = item.value();
 
       size += static_cast<int64_t>(key.Length()) +
               static_cast<int64_t>(value.Length());
 
       if (size > gSnapshotPrefill) {
+        aNextLoadIndex = index;
         break;
       }
 
       aLoadedItems.PutEntry(key);
 
       LSItemInfo* itemInfo = aItemInfos.AppendElement();
-      itemInfo->key() = iter.Key();
-      itemInfo->value() = iter.Data();
+      itemInfo->key() = key;
+      itemInfo->value() = value;
     }
 
     MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
     aLoadState = LSSnapshot::LoadState::Partial;
   }
 
   aTotalLength = mValues.Count();
 
@@ -5256,29 +5320,32 @@ mozilla::ipc::IPCResult Database::RecvPB
   MOZ_ASSERT(!mAllowedToClose);
 
   auto* snapshot = static_cast<Snapshot*>(aActor);
 
   // TODO: This can be optimized depending on which operation triggers snapshot
   //       creation. For example clear() doesn't need to receive items at all.
   nsTHashtable<nsStringHashKey> loadedItems;
   nsTArray<LSItemInfo> itemInfos;
+  uint32_t nextLoadIndex;
   uint32_t totalLength;
   int64_t initialUsage;
   int64_t peakUsage;
   LSSnapshot::LoadState loadState;
-  mDatastore->GetSnapshotInitInfo(loadedItems, itemInfos, totalLength,
-                                  initialUsage, peakUsage, loadState);
+  mDatastore->GetSnapshotInitInfo(loadedItems, itemInfos, nextLoadIndex,
+                                  totalLength, initialUsage, peakUsage,
+                                  loadState);
 
   if (aIncreasePeakUsage) {
     int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize);
     peakUsage += size;
   }
 
-  snapshot->Init(loadedItems, totalLength, initialUsage, peakUsage, loadState);
+  snapshot->Init(loadedItems, nextLoadIndex, totalLength, initialUsage,
+                 peakUsage, loadState);
 
   RegisterSnapshot(snapshot);
 
   aInitInfo->itemInfos() = std::move(itemInfos);
   aInitInfo->totalLength() = totalLength;
   aInitInfo->initialUsage() = initialUsage;
   aInitInfo->peakUsage() = peakUsage;
   aInitInfo->loadState() = loadState;
@@ -5334,17 +5401,17 @@ void Snapshot::SaveItem(const nsAString&
     return;
   }
 
   if (!mLoadedItems.GetEntry(aKey) && !mUnknownItems.GetEntry(aKey)) {
     nsString oldValue(aOldValue);
     mValues.LookupForAdd(aKey).OrInsert([oldValue]() { return oldValue; });
   }
 
-  if (aAffectsOrder && !mSavedKeys && !mLoadKeysReceived) {
+  if (aAffectsOrder && !mSavedKeys) {
     mDatastore->GetKeys(mKeys);
     mSavedKeys = true;
   }
 }
 
 void Snapshot::MarkDirty() {
   AssertIsOnBackgroundThread();
 
@@ -5484,20 +5551,21 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
   mValues.Clear();
   mKeys.Clear();
   mLoadedAllItems = true;
   mLoadKeysReceived = true;
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult Snapshot::RecvLoadItem(const nsString& aKey,
-                                               nsString* aValue) {
+mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems(
+    const nsString& aKey, nsString* aValue, nsTArray<LSItemInfo>* aItemInfos) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aValue);
+  MOZ_ASSERT(aItemInfos);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mFinishReceived)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
   }
 
   if (NS_WARN_IF(mLoadedReceived)) {
@@ -5522,27 +5590,114 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
     mDatastore->GetItem(aKey, *aValue);
   }
 
   if (aValue->IsVoid()) {
     mUnknownItems.PutEntry(aKey);
   } else {
     mLoadedItems.PutEntry(aKey);
 
-    if (mLoadedItems.Count() == mTotalLength) {
-      mLoadedItems.Clear();
-      mUnknownItems.Clear();
+    // mLoadedItems.Count()==mTotalLength is checked below.
+  }
+
+  // Load some more key/value pairs (as many as the snapshot gradual prefill
+  // byte budget allows).
+
+  if (gSnapshotGradualPrefill > 0) {
+    const nsTArray<LSItemInfo>& orderedItems = mDatastore->GetOrderedItems();
+
+    uint32_t length;
+    if (mSavedKeys) {
+      length = mKeys.Length();
+    } else {
+      length = orderedItems.Length();
+    }
+
+    int64_t size = 0;
+    while (mNextLoadIndex < length) {
+      // If the datastore's ordering has changed, mSavedKeys will be true and
+      // mKeys contains an ordered list of the keys. Otherwise we can use the
+      // datastore's key ordering which is still the same as when the snapshot
+      // was created.
+
+      nsString key;
+      if (mSavedKeys) {
+        key = mKeys[mNextLoadIndex];
+      } else {
+        key = orderedItems[mNextLoadIndex].key();
+      }
+
+      // Normally we would do this:
+      // if (!mLoadedItems.GetEntry(key)) {
+      //   ...
+      //   mLoadedItems.PutEntry(key);
+      // }
+      // but that requires two hash lookups. We can reduce that to just one
+      // hash lookup if we always call PutEntry and check the number of entries
+      // before and after the put (which is very cheap). However, if we reach
+      // the prefill limit, we need to call RemoveEntry, but that is also cheap
+      // because we pass the entry (not the key).
+
+      uint32_t countBeforePut = mLoadedItems.Count();
+      auto loadedItemEntry = mLoadedItems.PutEntry(key);
+      if (countBeforePut != mLoadedItems.Count()) {
+        // Check mValues first since that contains values as they existed when
+        // our snapshot was created, but have since been changed/removed in the
+        // datastore. If it's not there, then the datastore has the
+        // still-current value. However, if the datastore's key ordering has
+        // changed, we need to do a hash lookup rather than being able to do an
+        // optimized direct access to the index.
+
+        nsString value;
+        auto valueEntry = mValues.Lookup(key);
+        if (valueEntry) {
+          value = valueEntry.Data();
+        } else if (mSavedKeys) {
+          mDatastore->GetItem(nsString(key), value);
+        } else {
+          value = orderedItems[mNextLoadIndex].value();
+        }
+
+        // All not loaded keys must have a value.
+        MOZ_ASSERT(!value.IsVoid());
+
+        size += static_cast<int64_t>(key.Length()) +
+                static_cast<int64_t>(value.Length());
+
+        if (size > gSnapshotGradualPrefill) {
+          mLoadedItems.RemoveEntry(loadedItemEntry);
+
+          // mNextLoadIndex is not incremented, so we will resume at the same
+          // position next time.
+          break;
+        }
+
+        if (valueEntry) {
+          valueEntry.Remove();
+        }
+
+        LSItemInfo* itemInfo = aItemInfos->AppendElement();
+        itemInfo->key() = key;
+        itemInfo->value() = value;
+      }
+
+      mNextLoadIndex++;
+    }
+  }
+
+  if (mLoadedItems.Count() == mTotalLength) {
+    mLoadedItems.Clear();
+    mUnknownItems.Clear();
 #ifdef DEBUG
-      for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
-        MOZ_ASSERT(iter.Data().IsVoid());
-      }
+    for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
+      MOZ_ASSERT(iter.Data().IsVoid());
+    }
 #endif
-      mValues.Clear();
-      mLoadedAllItems = true;
-    }
+    mValues.Clear();
+    mLoadedAllItems = true;
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult Snapshot::RecvLoadKeys(nsTArray<nsString>* aKeys) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aKeys);
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -427,32 +427,43 @@ nsresult LSSnapshot::GetItemInternal(con
 
   switch (mLoadState) {
     case LoadState::Partial: {
       if (mValues.Get(aKey, &result)) {
         MOZ_ASSERT(!result.IsVoid());
       } else if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) {
         result.SetIsVoid(true);
       } else {
-        if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
+        nsTArray<LSItemInfo> itemInfos;
+        if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
+                nsString(aKey), &result, &itemInfos))) {
           return NS_ERROR_FAILURE;
         }
 
         if (result.IsVoid()) {
           mUnknownItems.PutEntry(aKey);
         } else {
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
-          if (mLoadedItems.Count() == mInitLength) {
-            mLoadedItems.Clear();
-            mUnknownItems.Clear();
-            mLength = 0;
-            mLoadState = LoadState::AllUnorderedItems;
-          }
+          // mLoadedItems.Count()==mInitLength is checked below.
+        }
+
+        for (uint32_t i = 0; i < itemInfos.Length(); i++) {
+          const LSItemInfo& itemInfo = itemInfos[i];
+
+          mLoadedItems.PutEntry(itemInfo.key());
+          mValues.Put(itemInfo.key(), itemInfo.value());
+        }
+
+        if (mLoadedItems.Count() == mInitLength) {
+          mLoadedItems.Clear();
+          mUnknownItems.Clear();
+          mLength = 0;
+          mLoadState = LoadState::AllUnorderedItems;
         }
       }
 
       if (aValue.WasPassed()) {
         const nsString& value = aValue.Value();
         if (!value.IsVoid()) {
           mValues.Put(aKey, value);
         } else if (!result.IsVoid()) {
@@ -461,25 +472,36 @@ nsresult LSSnapshot::GetItemInternal(con
       }
 
       break;
     }
 
     case LoadState::AllOrderedKeys: {
       if (mValues.Get(aKey, &result)) {
         if (result.IsVoid()) {
-          if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
+          nsTArray<LSItemInfo> itemInfos;
+          if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
+                  nsString(aKey), &result, &itemInfos))) {
             return NS_ERROR_FAILURE;
           }
 
           MOZ_ASSERT(!result.IsVoid());
 
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
+          // mLoadedItems.Count()==mInitLength is checked below.
+
+          for (uint32_t i = 0; i < itemInfos.Length(); i++) {
+            const LSItemInfo& itemInfo = itemInfos[i];
+
+            mLoadedItems.PutEntry(itemInfo.key());
+            mValues.Put(itemInfo.key(), itemInfo.value());
+          }
+
           if (mLoadedItems.Count() == mInitLength) {
             mLoadedItems.Clear();
             MOZ_ASSERT(mLength == 0);
             mLoadState = LoadState::AllOrderedItems;
           }
         }
       } else {
         result.SetIsVoid(true);
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -1,36 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 include protocol PBackgroundLSSnapshot;
 
+include PBackgroundLSSharedTypes;
+
 include "mozilla/dom/localstorage/SerializationHelpers.h";
 
 using mozilla::dom::LSSnapshot::LoadState
   from "mozilla/dom/LSSnapshot.h";
 
 namespace mozilla {
 namespace dom {
 
 /**
- * LocalStorage key/value pair wire representations.  `value` may be void in
- * cases where there is a value but it is not being sent for memory/bandwidth
- * conservation purposes.  (It's not possible to have a null/undefined `value`
- * as Storage is defined explicitly as a String store.)
- */
-struct LSItemInfo
-{
-  nsString key;
-  nsString value;
-};
-
-/**
  * Initial LSSnapshot state as produced by Datastore::GetSnapshotInitInfo.  See
  * `LSSnapshot::LoadState` for more details about the possible states and a
  * high level overview.
  */
 struct LSSnapshotInitInfo
 {
   /**
    * As many key/value or key/void pairs as the snapshot prefill byte budget
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -43,10 +43,22 @@ struct LSSimpleRequestPreloadedParams
   PrincipalInfo principalInfo;
 };
 
 union LSSimpleRequestParams
 {
   LSSimpleRequestPreloadedParams;
 };
 
+/**
+ * LocalStorage key/value pair wire representations.  `value` may be void in
+ * cases where there is a value but it is not being sent for memory/bandwidth
+ * conservation purposes.  (It's not possible to have a null/undefined `value`
+ * as Storage is defined explicitly as a String store.)
+ */
+struct LSItemInfo
+{
+  nsString key;
+  nsString value;
+};
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSSnapshot.ipdl
+++ b/dom/localstorage/PBackgroundLSSnapshot.ipdl
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 include protocol PBackgroundLSDatabase;
 
+include PBackgroundLSSharedTypes;
+
 namespace mozilla {
 namespace dom {
 
 struct LSSetItemInfo
 {
   nsString key;
   nsString oldValue;
   nsString value;
@@ -45,25 +47,26 @@ parent:
   async Checkpoint(LSWriteInfo[] writeInfos);
 
   async Finish();
 
   async Loaded();
 
   /**
    * Invoked on demand to load an item that didn't fit into the initial
-   * snapshot prefill.
+   * snapshot prefill and also some additional key/value pairs to lower down
+   * the need to use this synchronous message again.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
    * consult any other threads or perform any I/O.
    */
-  sync LoadItem(nsString key)
-    returns (nsString value);
+  sync LoadValueAndMoreItems(nsString key)
+    returns (nsString value, LSItemInfo[] itemInfos);
 
   /**
    * Invoked on demand to load all keys in in their canonical order if they
    * didn't fit into the initial snapshot prefill.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
--- a/dom/localstorage/test/unit/test_snapshotting.js
+++ b/dom/localstorage/test/unit/test_snapshotting.js
@@ -1,232 +1,328 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 async function testSteps() {
   const url = "http://example.com";
 
+  info("Setting pref");
+
+  Services.prefs.setBoolPref("dom.storage.snapshot_reusing", false);
+
   const items = [
     { key: "key1", value: "value1" },
     { key: "key2", value: "value2" },
     { key: "key3", value: "value3" },
     { key: "key4", value: "value4" },
     { key: "key5", value: "value5" },
     { key: "key6", value: "value6" },
     { key: "key7", value: "value7" },
     { key: "key8", value: "value8" },
     { key: "key9", value: "value9" },
     { key: "key10", value: "value10" },
   ];
 
-  function getPartialPrefill() {
-    let size = 0;
-    for (let i = 0; i < items.length / 2; i++) {
-      let item = items[i];
-      size += item.key.length + item.value.length;
+  let sizeOfOneKey;
+  let sizeOfOneValue;
+  let sizeOfOneItem;
+  let sizeOfKeys = 0;
+  let sizeOfItems = 0;
+
+  for (let i = 0; i < items.length; i++) {
+    let item = items[i];
+    let sizeOfKey = item.key.length;
+    let sizeOfValue = item.value.length;
+    let sizeOfItem = sizeOfKey + sizeOfValue;
+    if (i == 0) {
+      sizeOfOneKey = sizeOfKey;
+      sizeOfOneValue = sizeOfValue;
+      sizeOfOneItem = sizeOfItem;
     }
-    return size;
+    sizeOfKeys += sizeOfKey;
+    sizeOfItems += sizeOfItem;
   }
 
+  info("Size of one key is " + sizeOfOneKey);
+  info("Size of one value is " + sizeOfOneValue);
+  info("Size of one item is " + sizeOfOneItem);
+  info("Size of keys is " + sizeOfKeys);
+  info("Size of items is " + sizeOfItems);
+
   const prefillValues = [
-    0,                   // no prefill
-    getPartialPrefill(), // partial prefill
-    -1,                  // full prefill
+    // Zero prefill (prefill disabled)
+    0,
+    // Less than one key length prefill
+    sizeOfOneKey - 1,
+    // Greater than one key length and less than one item length prefill
+    sizeOfOneKey + 1,
+    // Precisely one item length prefill
+    sizeOfOneItem,
+    // Precisely two times one item length prefill
+    2 * sizeOfOneItem,
+    // Precisely three times one item length prefill
+    3 * sizeOfOneItem,
+    // Precisely four times one item length prefill
+    4 * sizeOfOneItem,
+    // Precisely size of keys prefill
+    sizeOfKeys,
+    // Less than size of keys plus one value length prefill
+    sizeOfKeys + sizeOfOneValue - 1,
+    // Precisely size of keys plus one value length prefill
+    sizeOfKeys + sizeOfOneValue,
+    // Greater than size of keys plus one value length and less than size of
+    // keys plus two times one value length prefill
+    sizeOfKeys + sizeOfOneValue + 1,
+    // Precisely size of keys plus two times one value length prefill
+    sizeOfKeys + 2 * sizeOfOneValue,
+    // Precisely size of keys plus three times one value length prefill
+    sizeOfKeys + 3 * sizeOfOneValue,
+    // Precisely size of keys plus four times one value length prefill
+    sizeOfKeys + 4 * sizeOfOneValue,
+    // Precisely size of keys plus five times one value length prefill
+    sizeOfKeys + 5 * sizeOfOneValue,
+    // Precisely size of keys plus six times one value length prefill
+    sizeOfKeys + 6 * sizeOfOneValue,
+    // Precisely size of keys plus seven times one value length prefill
+    sizeOfKeys + 7 * sizeOfOneValue,
+    // Precisely size of keys plus eight times one value length prefill
+    sizeOfKeys + 8 * sizeOfOneValue,
+    // Precisely size of keys plus nine times one value length prefill
+    sizeOfKeys + 9 * sizeOfOneValue,
+    // Precisely size of items prefill
+    sizeOfItems,
+    // Unlimited prefill
+    -1,
   ];
 
-  info("Setting pref");
-
-  Services.prefs.setBoolPref("dom.storage.snapshot_reusing", false);
-
   for (let prefillValue of prefillValues) {
-    info("Setting prefill value");
+    info("Setting prefill value to " + prefillValue);
 
     Services.prefs.setIntPref("dom.storage.snapshot_prefill", prefillValue);
 
-    info("Getting storage");
-
-    let storage = getLocalStorage(getPrincipal(url));
-
-    // 1st snapshot
-
-    info("Adding data");
-
-    for (let item of items) {
-      storage.setItem(item.key, item.value);
-    }
-
-    info("Saving key order");
-
-    // This forces GetKeys to be called internally.
-    let savedKeys = Object.keys(storage);
-
-    // GetKey should match GetKeys
-    for (let i = 0; i < savedKeys.length; i++) {
-      is(storage.key(i), savedKeys[i], "Correct key");
-    }
+    const gradualPrefillValues = [
+      // Zero gradual prefill
+      0,
+      // Less than one key length gradual prefill
+      sizeOfOneKey - 1,
+      // Greater than one key length and less than one item length gradual
+      // prefill
+      sizeOfOneKey + 1,
+      // Precisely one item length gradual prefill
+      sizeOfOneItem,
+      // Precisely two times one item length gradual prefill
+      2 * sizeOfOneItem,
+      // Precisely three times one item length gradual prefill
+      3 * sizeOfOneItem,
+      // Precisely four times one item length gradual prefill
+      4 * sizeOfOneItem,
+      // Precisely five times one item length gradual prefill
+      5 * sizeOfOneItem,
+      // Precisely six times one item length gradual prefill
+      6 * sizeOfOneItem,
+      // Precisely seven times one item length gradual prefill
+      7 * sizeOfOneItem,
+      // Precisely eight times one item length gradual prefill
+      8 * sizeOfOneItem,
+      // Precisely nine times one item length gradual prefill
+      9 * sizeOfOneItem,
+      // Precisely size of items prefill
+      sizeOfItems,
+      // Unlimited gradual prefill
+      -1,
+    ];
 
-    info("Returning to event loop");
-
-    // Returning to event loop forces the internal snapshot to finish.
-    await returnToEventLoop();
+    for (let gradualPrefillValue of gradualPrefillValues) {
+      info("Setting gradual prefill value to " + gradualPrefillValue);
 
-    // 2nd snapshot
+      Services.prefs.setIntPref("dom.storage.snapshot_gradual_prefill",
+                                gradualPrefillValue);
 
-    info("Verifying length");
+      info("Getting storage");
 
-    is(storage.length, items.length, "Correct length");
+      let storage = getLocalStorage(getPrincipal(url));
 
-    info("Verifying key order");
+      // 1st snapshot
 
-    let keys = Object.keys(storage);
-
-    is(keys.length, savedKeys.length);
+      info("Adding data");
 
-    for (let i = 0; i < keys.length; i++) {
-      is(keys[i], savedKeys[i], "Correct key");
-    }
+      for (let item of items) {
+        storage.setItem(item.key, item.value);
+      }
 
-    info("Verifying values");
+      info("Saving key order");
 
-    for (let item of items) {
-      is(storage.getItem(item.key), item.value, "Correct value");
-    }
+      // This forces GetKeys to be called internally.
+      let savedKeys = Object.keys(storage);
 
-    info("Returning to event loop");
+      // GetKey should match GetKeys
+      for (let i = 0; i < savedKeys.length; i++) {
+        is(storage.key(i), savedKeys[i], "Correct key");
+      }
 
-    await returnToEventLoop();
-
-    // 3rd snapshot
+      info("Returning to event loop");
 
-    // Force key2 to load.
-    storage.getItem("key2");
+      // Returning to event loop forces the internal snapshot to finish.
+      await returnToEventLoop();
+
+      // 2nd snapshot
+
+      info("Verifying length");
 
-    // Fill out write infos a bit.
-    storage.removeItem("key5");
-    storage.setItem("key5", "value5");
-    storage.removeItem("key5");
-    storage.setItem("key11", "value11");
-    storage.setItem("key5", "value5");
+      is(storage.length, items.length, "Correct length");
+
+      info("Verifying key order");
 
-    items.push({ key: "key11", value: "value11" });
+      let keys = Object.keys(storage);
+
+      is(keys.length, savedKeys.length);
 
-    info("Verifying length");
+      for (let i = 0; i < keys.length; i++) {
+        is(keys[i], savedKeys[i], "Correct key");
+      }
 
-    is(storage.length, items.length, "Correct length");
+      info("Verifying values");
 
-    // This forces to get all keys from the parent and then apply write infos
-    // on already cached values.
-    savedKeys = Object.keys(storage);
+      for (let item of items) {
+        is(storage.getItem(item.key), item.value, "Correct value");
+      }
 
-    info("Verifying values");
+      info("Returning to event loop");
+
+      await returnToEventLoop();
+
+      // 3rd snapshot
 
-    for (let item of items) {
-      is(storage.getItem(item.key), item.value, "Correct value");
-    }
-
-    storage.removeItem("key11");
+      // Force key2 to load.
+      storage.getItem("key2");
 
-    items.pop();
-
-    info("Returning to event loop");
+      // Fill out write infos a bit.
+      storage.removeItem("key5");
+      storage.setItem("key5", "value5");
+      storage.removeItem("key5");
+      storage.setItem("key11", "value11");
+      storage.setItem("key5", "value5");
 
-    await returnToEventLoop();
+      items.push({ key: "key11", value: "value11" });
 
-    // 4th snapshot
+      info("Verifying length");
+
+      is(storage.length, items.length, "Correct length");
 
-    // Force loading of all items.
-    info("Verifying length");
+      // This forces to get all keys from the parent and then apply write infos
+      // on already cached values.
+      savedKeys = Object.keys(storage);
 
-    is(storage.length, items.length, "Correct length");
+      info("Verifying values");
 
-    info("Verifying values");
+      for (let item of items) {
+        is(storage.getItem(item.key), item.value, "Correct value");
+      }
 
-    for (let item of items) {
-      is(storage.getItem(item.key), item.value, "Correct value");
-    }
+      storage.removeItem("key11");
 
-    is(storage.getItem("key11"), null, "Correct value");
+      items.pop();
 
-    info("Returning to event loop");
+      info("Returning to event loop");
+
+      await returnToEventLoop();
 
-    await returnToEventLoop();
+      // 4th snapshot
 
-    // 5th snapshot
+      // Force loading of all items.
+      info("Verifying length");
 
-    // Force loading of all keys.
-    info("Saving key order");
+      is(storage.length, items.length, "Correct length");
+
+      info("Verifying values");
 
-    savedKeys = Object.keys(storage);
+      for (let item of items) {
+        is(storage.getItem(item.key), item.value, "Correct value");
+      }
+
+      is(storage.getItem("key11"), null, "Correct value");
 
-    // Force loading of all items.
-    info("Verifying length");
+      info("Returning to event loop");
+
+      await returnToEventLoop();
 
-    is(storage.length, items.length, "Correct length");
+      // 5th snapshot
 
-    info("Verifying values");
+      // Force loading of all keys.
+      info("Saving key order");
 
-    for (let item of items) {
-      is(storage.getItem(item.key), item.value, "Correct value");
-    }
+      savedKeys = Object.keys(storage);
 
-    is(storage.getItem("key11"), null, "Correct value");
+      // Force loading of all items.
+      info("Verifying length");
+
+      is(storage.length, items.length, "Correct length");
 
-    info("Returning to event loop");
-
-    await returnToEventLoop();
+      info("Verifying values");
 
-    // 6th snapshot
-    info("Verifying unknown item");
+      for (let item of items) {
+        is(storage.getItem(item.key), item.value, "Correct value");
+      }
 
-    is(storage.getItem("key11"), null, "Correct value");
+      is(storage.getItem("key11"), null, "Correct value");
 
-    info("Verifying unknown item again");
+      info("Returning to event loop");
 
-    is(storage.getItem("key11"), null, "Correct value");
+      await returnToEventLoop();
 
-    info("Returning to event loop");
+      // 6th snapshot
+      info("Verifying unknown item");
 
-    await returnToEventLoop();
+      is(storage.getItem("key11"), null, "Correct value");
 
-    // 7th snapshot
+      info("Verifying unknown item again");
+
+      is(storage.getItem("key11"), null, "Correct value");
 
-    // Save actual key order.
-    info("Saving key order");
+      info("Returning to event loop");
 
-    savedKeys = Object.keys(storage);
+      await returnToEventLoop();
+
+      // 7th snapshot
 
-    await returnToEventLoop();
+      // Save actual key order.
+      info("Saving key order");
 
-    // 8th snapshot
+      savedKeys = Object.keys(storage);
 
-    // Force loading of all items, but in reverse order.
-    info("Getting values");
+      await returnToEventLoop();
+
+      // 8th snapshot
 
-    for (let i = items.length - 1; i >= 0; i--) {
-      let item = items[i];
-      storage.getItem(item.key);
-    }
+      // Force loading of all items, but in reverse order.
+      info("Getting values");
 
-    info("Verifying key order");
+      for (let i = items.length - 1; i >= 0; i--) {
+        let item = items[i];
+        storage.getItem(item.key);
+      }
 
-    keys = Object.keys(storage);
+      info("Verifying key order");
 
-    is(keys.length, savedKeys.length);
+      keys = Object.keys(storage);
+
+      is(keys.length, savedKeys.length);
 
-    for (let i = 0; i < keys.length; i++) {
-      is(keys[i], savedKeys[i], "Correct key");
-    }
+      for (let i = 0; i < keys.length; i++) {
+        is(keys[i], savedKeys[i], "Correct key");
+      }
 
-    await returnToEventLoop();
+      await returnToEventLoop();
 
-    // 9th snapshot
+      // 9th snapshot
 
-    info("Clearing");
+      info("Clearing");
 
-    storage.clear();
+      storage.clear();
 
-    info("Returning to event loop");
+      info("Returning to event loop");
 
-    await returnToEventLoop();
+      await returnToEventLoop();
+    }
   }
 }
--- a/dom/localstorage/test/unit/xpcshell.ini
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -31,11 +31,12 @@ run-sequentially = test_databaseShadowin
 [test_databaseShadowing_clearOriginsByPrefix2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js
 [test_eviction.js]
 [test_groupLimit.js]
 [test_groupMismatch.js]
 [test_migration.js]
 [test_originInit.js]
 [test_snapshotting.js]
+requesttimeoutfactor = 4
 [test_stringLength.js]
 [test_stringLength2.js]
 [test_usage.js]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -8091,23 +8091,24 @@ void ClearRequestBase::DeleteFiles(Quota
 
     bool initialized;
     if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
       initialized = aQuotaManager->IsOriginInitialized(origin);
     } else {
       initialized = aQuotaManager->IsTemporaryStorageInitialized();
     }
 
+    bool hasOtherClient = false;
+
     UsageInfo usageInfo;
 
     if (!mClientType.IsNull()) {
       // Checking whether there is any other client in the directory is needed.
       // If there is not, removing whole directory is needed.
       nsCOMPtr<nsIDirectoryEnumerator> originEntries;
-      bool hasOtherClient = false;
       if (NS_WARN_IF(NS_FAILED(
               file->GetDirectoryEntries(getter_AddRefs(originEntries)))) ||
           !originEntries) {
         return;
       }
 
       nsCOMPtr<nsIFile> clientFile;
       while (NS_SUCCEEDED((rv = originEntries->GetNextFile(
@@ -8195,21 +8196,23 @@ void ClearRequestBase::DeleteFiles(Quota
 
     // If it hasn't been initialized, we don't need to update the quota and
     // notify the removing client.
     if (!initialized) {
       return;
     }
 
     if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
-      if (mClientType.IsNull()) {
-        aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
-      } else {
+      if (hasOtherClient) {
+        MOZ_ASSERT(!mClientType.IsNull());
+
         aQuotaManager->DecreaseUsageForOrigin(aPersistenceType, group, origin,
                                               usageInfo.TotalUsage());
+      } else {
+        aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
       }
     }
 
     aQuotaManager->OriginClearCompleted(aPersistenceType, origin, mClientType);
   }
 }
 
 nsresult ClearRequestBase::DoDirectoryWork(QuotaManager* aQuotaManager) {
--- a/dom/tests/browser/browser_localStorage_snapshotting_e10s.js
+++ b/dom/tests/browser/browser_localStorage_snapshotting_e10s.js
@@ -130,243 +130,328 @@ add_task(async function() {
     key2: "initial2",
     key3: "initial3",
     key5: "initial5",
     key6: "initial6",
     key7: "initial7",
     key8: "initial8",
   };
 
-  function getPartialPrefill() {
-    let size = 0;
-    let entries = Object.entries(initialState);
-    for (let i = 0; i < entries.length / 2; i++) {
-      let entry = entries[i];
-      size += entry[0].length + entry[1].length;
+  let sizeOfOneKey;
+  let sizeOfOneValue;
+  let sizeOfOneItem;
+  let sizeOfKeys = 0;
+  let sizeOfItems = 0;
+
+  let entries = Object.entries(initialState);
+  for (let i = 0; i < entries.length; i++) {
+    let entry = entries[i];
+    let sizeOfKey = entry[0].length;
+    let sizeOfValue = entry[1].length;
+    let sizeOfItem = sizeOfKey + sizeOfValue;
+    if (i == 0) {
+      sizeOfOneKey = sizeOfKey;
+      sizeOfOneValue = sizeOfValue;
+      sizeOfOneItem = sizeOfItem;
     }
-    return size;
+    sizeOfKeys += sizeOfKey;
+    sizeOfItems += sizeOfItem;
   }
 
+  info("Size of one key is " + sizeOfOneKey);
+  info("Size of one value is " + sizeOfOneValue);
+  info("Size of one item is " + sizeOfOneItem);
+  info("Size of keys is " + sizeOfKeys);
+  info("Size of items is " + sizeOfItems);
+
   const prefillValues = [
-    0,                   // no prefill
-    getPartialPrefill(), // partial prefill
-    -1,                  // full prefill
+    // Zero prefill (prefill disabled)
+    0,
+    // Less than one key length prefill
+    sizeOfOneKey - 1,
+    // Greater than one key length and less than one item length prefill
+    sizeOfOneKey + 1,
+    // Precisely one item length prefill
+    sizeOfOneItem,
+    // Precisely two times one item length prefill
+    2 * sizeOfOneItem,
+    // Precisely size of keys prefill
+    sizeOfKeys,
+    // Less than size of keys plus one value length prefill
+    sizeOfKeys + sizeOfOneValue - 1,
+    // Precisely size of keys plus one value length prefill
+    sizeOfKeys + sizeOfOneValue,
+    // Greater than size of keys plus one value length and less than size of
+    // keys plus two times one value length prefill
+    sizeOfKeys + sizeOfOneValue + 1,
+    // Precisely size of keys plus two times one value length prefill
+    sizeOfKeys + 2 * sizeOfOneValue,
+    // Precisely size of keys plus three times one value length prefill
+    sizeOfKeys + 3 * sizeOfOneValue,
+    // Precisely size of keys plus four times one value length prefill
+    sizeOfKeys + 4 * sizeOfOneValue,
+    // Precisely size of keys plus five times one value length prefill
+    sizeOfKeys + 5 * sizeOfOneValue,
+    // Precisely size of keys plus six times one value length prefill
+    sizeOfKeys + 6 * sizeOfOneValue,
+    // Precisely size of items prefill
+    sizeOfItems,
+    // Unlimited prefill
+    -1,
   ];
 
   for (let prefillValue of prefillValues) {
-    info("Setting prefill value");
+    info("Setting prefill value to " + prefillValue);
 
     await SpecialPowers.pushPrefEnv({
       set: [
         ["dom.storage.snapshot_prefill", prefillValue],
       ],
     });
 
-    info("Stage 1");
-
-    const setRemoveMutations1 = [
-      ["key0", "setRemove10"],
-      ["key1", "setRemove11"],
-      ["key2", null],
-      ["key3", "setRemove13"],
-      ["key4", "setRemove14"],
-      ["key5", "setRemove15"],
-      ["key6", "setRemove16"],
-      ["key7", "setRemove17"],
-      ["key8", null],
-      ["key9", "setRemove19"],
-    ];
-
-    const setRemoveState1 = {
-      key0: "setRemove10",
-      key1: "setRemove11",
-      key3: "setRemove13",
-      key4: "setRemove14",
-      key5: "setRemove15",
-      key6: "setRemove16",
-      key7: "setRemove17",
-      key9: "setRemove19",
-    };
-
-    const setRemoveMutations2 = [
-      ["key0", "setRemove20"],
-      ["key1", null],
-      ["key2", "setRemove22"],
-      ["key3", "setRemove23"],
-      ["key4", "setRemove24"],
-      ["key5", "setRemove25"],
-      ["key6", "setRemove26"],
-      ["key7", null],
-      ["key8", "setRemove28"],
-      ["key9", "setRemove29"],
+    const gradualPrefillValues = [
+      // Zero gradual prefill
+      0,
+      // Less than one key length gradual prefill
+      sizeOfOneKey - 1,
+      // Greater than one key length and less than one item length gradual
+      // prefill
+      sizeOfOneKey + 1,
+      // Precisely one item length gradual prefill
+      sizeOfOneItem,
+      // Precisely two times one item length gradual prefill
+      2 * sizeOfOneItem,
+      // Precisely three times one item length gradual prefill
+      3 * sizeOfOneItem,
+      // Precisely four times one item length gradual prefill
+      4 * sizeOfOneItem,
+      // Precisely five times one item length gradual prefill
+      5 * sizeOfOneItem,
+      // Precisely six times one item length gradual prefill
+      6 * sizeOfOneItem,
+      // Precisely size of items prefill
+      sizeOfItems,
+      // Unlimited gradual prefill
+      -1,
     ];
 
-    const setRemoveState2 = {
-      key0: "setRemove20",
-      key2: "setRemove22",
-      key3: "setRemove23",
-      key4: "setRemove24",
-      key5: "setRemove25",
-      key6: "setRemove26",
-      key8: "setRemove28",
-      key9: "setRemove29",
-    };
+    for (let gradualPrefillValue of gradualPrefillValues) {
+      info("Setting gradual prefill value to " + gradualPrefillValue);
+
+      await SpecialPowers.pushPrefEnv({
+        set: [
+          ["dom.storage.snapshot_gradual_prefill", gradualPrefillValue],
+        ],
+      });
+
+      info("Stage 1");
 
-    // Apply initial mutations using an explicit snapshot. The explicit
-    // snapshot here ensures that the parent process have received the changes.
-    await beginExplicitSnapshot(writerTab1);
-    await applyMutations(writerTab1, initialMutations);
-    await endExplicitSnapshot(writerTab1);
+      const setRemoveMutations1 = [
+        ["key0", "setRemove10"],
+        ["key1", "setRemove11"],
+        ["key2", null],
+        ["key3", "setRemove13"],
+        ["key4", "setRemove14"],
+        ["key5", "setRemove15"],
+        ["key6", "setRemove16"],
+        ["key7", "setRemove17"],
+        ["key8", null],
+        ["key9", "setRemove19"],
+      ];
 
-    // Begin explicit snapshots in all tabs except readerTab2. All these tabs
-    // should see the initial state regardless what other tabs are doing.
-    await beginExplicitSnapshot(writerTab1);
-    await beginExplicitSnapshot(writerTab2);
-    await beginExplicitSnapshot(readerTab1);
+      const setRemoveState1 = {
+        key0: "setRemove10",
+        key1: "setRemove11",
+        key3: "setRemove13",
+        key4: "setRemove14",
+        key5: "setRemove15",
+        key6: "setRemove16",
+        key7: "setRemove17",
+        key9: "setRemove19",
+      };
 
-    // Apply first array of set/remove mutations in writerTab1 and end the
-    // explicit snapshot. This will trigger saving of values in other active
-    // snapshots.
-    await applyMutations(writerTab1, setRemoveMutations1);
-    await endExplicitSnapshot(writerTab1);
-
-    // Begin an explicit snapshot in readerTab2. writerTab1 already ended its
-    // explicit snapshot, so readerTab2 should see mutations done by
-    // writerTab1.
-    await beginExplicitSnapshot(readerTab2);
-
-    // Apply second array of set/remove mutations in writerTab2 and end the
-    // explicit snapshot. This will trigger saving of values in other active
-    // snapshots, but only if they haven't been saved already.
-    await applyMutations(writerTab2, setRemoveMutations2);
-    await endExplicitSnapshot(writerTab2);
+      const setRemoveMutations2 = [
+        ["key0", "setRemove20"],
+        ["key1", null],
+        ["key2", "setRemove22"],
+        ["key3", "setRemove23"],
+        ["key4", "setRemove24"],
+        ["key5", "setRemove25"],
+        ["key6", "setRemove26"],
+        ["key7", null],
+        ["key8", "setRemove28"],
+        ["key9", "setRemove29"],
+      ];
 
-    // Verify state in readerTab1, it should match the initial state.
-    await verifyState(readerTab1, initialState);
-    await endExplicitSnapshot(readerTab1);
-
-    // Verify state in readerTab2, it should match the state after the first
-    // array of set/remove mutatations have been applied and "commited".
-    await verifyState(readerTab2, setRemoveState1);
-    await endExplicitSnapshot(readerTab2);
+      const setRemoveState2 = {
+        key0: "setRemove20",
+        key2: "setRemove22",
+        key3: "setRemove23",
+        key4: "setRemove24",
+        key5: "setRemove25",
+        key6: "setRemove26",
+        key8: "setRemove28",
+        key9: "setRemove29",
+      };
 
-    // Verify final state, it should match the state after the second array of
-    // set/remove mutation have been applied and "commited". An explicit
-    // snapshot is used.
-    await beginExplicitSnapshot(readerTab1);
-    await verifyState(readerTab1, setRemoveState2);
-    await endExplicitSnapshot(readerTab1);
+      // Apply initial mutations using an explicit snapshot. The explicit
+      // snapshot here ensures that the parent process have received the
+      // changes.
+      await beginExplicitSnapshot(writerTab1);
+      await applyMutations(writerTab1, initialMutations);
+      await endExplicitSnapshot(writerTab1);
 
-    info("Stage 2");
+      // Begin explicit snapshots in all tabs except readerTab2. All these tabs
+      // should see the initial state regardless what other tabs are doing.
+      await beginExplicitSnapshot(writerTab1);
+      await beginExplicitSnapshot(writerTab2);
+      await beginExplicitSnapshot(readerTab1);
 
-    const setRemoveClearMutations1 = [
-      ["key0", "setRemoveClear10"],
-      ["key1", null],
-      [null, null],
-    ];
+      // Apply first array of set/remove mutations in writerTab1 and end the
+      // explicit snapshot. This will trigger saving of values in other active
+      // snapshots.
+      await applyMutations(writerTab1, setRemoveMutations1);
+      await endExplicitSnapshot(writerTab1);
 
-    const setRemoveClearState1 = {
-    };
+      // Begin an explicit snapshot in readerTab2. writerTab1 already ended its
+      // explicit snapshot, so readerTab2 should see mutations done by
+      // writerTab1.
+      await beginExplicitSnapshot(readerTab2);
+
+      // Apply second array of set/remove mutations in writerTab2 and end the
+      // explicit snapshot. This will trigger saving of values in other active
+      // snapshots, but only if they haven't been saved already.
+      await applyMutations(writerTab2, setRemoveMutations2);
+      await endExplicitSnapshot(writerTab2);
 
-    const setRemoveClearMutations2 = [
-      ["key8", null],
-      ["key9", "setRemoveClear29"],
-      [null, null],
-    ];
+      // Verify state in readerTab1, it should match the initial state.
+      await verifyState(readerTab1, initialState);
+      await endExplicitSnapshot(readerTab1);
+
+      // Verify state in readerTab2, it should match the state after the first
+      // array of set/remove mutatations have been applied and "commited".
+      await verifyState(readerTab2, setRemoveState1);
+      await endExplicitSnapshot(readerTab2);
 
-    const setRemoveClearState2 = {
-    };
+      // Verify final state, it should match the state after the second array of
+      // set/remove mutation have been applied and "commited". An explicit
+      // snapshot is used.
+      await beginExplicitSnapshot(readerTab1);
+      await verifyState(readerTab1, setRemoveState2);
+      await endExplicitSnapshot(readerTab1);
 
-    // This is very similar to previous stage except that in addition to
-    // set/remove, the clear operation is involved too.
-    await beginExplicitSnapshot(writerTab1);
-    await applyMutations(writerTab1, initialMutations);
-    await endExplicitSnapshot(writerTab1);
+      info("Stage 2");
 
-    await beginExplicitSnapshot(writerTab1);
-    await beginExplicitSnapshot(writerTab2);
-    await beginExplicitSnapshot(readerTab1);
+      const setRemoveClearMutations1 = [
+        ["key0", "setRemoveClear10"],
+        ["key1", null],
+        [null, null],
+      ];
 
-    await applyMutations(writerTab1, setRemoveClearMutations1);
-    await endExplicitSnapshot(writerTab1);
-
-    await beginExplicitSnapshot(readerTab2);
+      const setRemoveClearState1 = {
+      };
 
-    await applyMutations(writerTab2, setRemoveClearMutations2);
-    await endExplicitSnapshot(writerTab2);
+      const setRemoveClearMutations2 = [
+        ["key8", null],
+        ["key9", "setRemoveClear29"],
+        [null, null],
+      ];
 
-    await verifyState(readerTab1, initialState);
-    await endExplicitSnapshot(readerTab1);
+      const setRemoveClearState2 = {
+      };
 
-    await verifyState(readerTab2, setRemoveClearState1);
-    await endExplicitSnapshot(readerTab2);
+      // This is very similar to previous stage except that in addition to
+      // set/remove, the clear operation is involved too.
+      await beginExplicitSnapshot(writerTab1);
+      await applyMutations(writerTab1, initialMutations);
+      await endExplicitSnapshot(writerTab1);
 
-    await beginExplicitSnapshot(readerTab1);
-    await verifyState(readerTab1, setRemoveClearState2);
-    await endExplicitSnapshot(readerTab1);
+      await beginExplicitSnapshot(writerTab1);
+      await beginExplicitSnapshot(writerTab2);
+      await beginExplicitSnapshot(readerTab1);
 
-    info("Stage 3");
+      await applyMutations(writerTab1, setRemoveClearMutations1);
+      await endExplicitSnapshot(writerTab1);
+
+      await beginExplicitSnapshot(readerTab2);
+
+      await applyMutations(writerTab2, setRemoveClearMutations2);
+      await endExplicitSnapshot(writerTab2);
 
-    const changeOrderMutations = [
-      ["key1", null],
-      ["key2", null],
-      ["key3", null],
-      ["key5", null],
-      ["key6", null],
-      ["key7", null],
-      ["key8", null],
-      ["key8", "initial8"],
-      ["key7", "initial7"],
-      ["key6", "initial6"],
-      ["key5", "initial5"],
-      ["key3", "initial3"],
-      ["key2", "initial2"],
-      ["key1", "initial1"],
-    ];
+      await verifyState(readerTab1, initialState);
+      await endExplicitSnapshot(readerTab1);
+
+      await verifyState(readerTab2, setRemoveClearState1);
+      await endExplicitSnapshot(readerTab2);
+
+      await beginExplicitSnapshot(readerTab1);
+      await verifyState(readerTab1, setRemoveClearState2);
+      await endExplicitSnapshot(readerTab1);
+
+      info("Stage 3");
 
-    // Apply initial mutations using an explicit snapshot. The explicit
-    // snapshot here ensures that the parent process have received the changes.
-    await beginExplicitSnapshot(writerTab1);
-    await applyMutations(writerTab1, initialMutations);
-    await endExplicitSnapshot(writerTab1);
+      const changeOrderMutations = [
+        ["key1", null],
+        ["key2", null],
+        ["key3", null],
+        ["key5", null],
+        ["key6", null],
+        ["key7", null],
+        ["key8", null],
+        ["key8", "initial8"],
+        ["key7", "initial7"],
+        ["key6", "initial6"],
+        ["key5", "initial5"],
+        ["key3", "initial3"],
+        ["key2", "initial2"],
+        ["key1", "initial1"],
+      ];
 
-    // Begin explicit snapshots in all tabs except writerTab2 which is not used
-    // in this stage. All these tabs should see the initial order regardless
-    // what other tabs are doing.
-    await beginExplicitSnapshot(readerTab1);
-    await beginExplicitSnapshot(writerTab1);
-    await beginExplicitSnapshot(readerTab2);
-
-    // Get all keys in readerTab1 and end the explicit snapshot. No mutations
-    // have been applied yet.
-    let tab1Keys = await getKeys(readerTab1);
-    await endExplicitSnapshot(readerTab1);
+      // Apply initial mutations using an explicit snapshot. The explicit
+      // snapshot here ensures that the parent process have received the
+      // changes.
+      await beginExplicitSnapshot(writerTab1);
+      await applyMutations(writerTab1, initialMutations);
+      await endExplicitSnapshot(writerTab1);
 
-    // Apply mutations that change the order of keys and end the explicit
-    // snapshot. The state is unchanged. This will trigger saving of key order
-    // in other active snapshots, but only if the order hasn't been saved
-    // already.
-    await applyMutations(writerTab1, changeOrderMutations);
-    await endExplicitSnapshot(writerTab1);
+      // Begin explicit snapshots in all tabs except writerTab2 which is not
+      // used in this stage. All these tabs should see the initial order
+      // regardless what other tabs are doing.
+      await beginExplicitSnapshot(readerTab1);
+      await beginExplicitSnapshot(writerTab1);
+      await beginExplicitSnapshot(readerTab2);
 
-    // Get all keys in readerTab2 and end the explicit snapshot. Change order
-    // mutations have been applied, but the order should stay unchanged.
-    let tab2Keys = await getKeys(readerTab2);
-    await endExplicitSnapshot(readerTab2);
+      // Get all keys in readerTab1 and end the explicit snapshot. No mutations
+      // have been applied yet.
+      let tab1Keys = await getKeys(readerTab1);
+      await endExplicitSnapshot(readerTab1);
 
-    // Verify the key order is the same.
-    is(tab2Keys.length, tab1Keys.length, "Correct keys length");
-    for (let i = 0; i < tab2Keys.length; i++) {
-      is(tab2Keys[i], tab1Keys[i], "Correct key");
-    }
+      // Apply mutations that change the order of keys and end the explicit
+      // snapshot. The state is unchanged. This will trigger saving of key order
+      // in other active snapshots, but only if the order hasn't been saved
+      // already.
+      await applyMutations(writerTab1, changeOrderMutations);
+      await endExplicitSnapshot(writerTab1);
+
+      // Get all keys in readerTab2 and end the explicit snapshot. Change order
+      // mutations have been applied, but the order should stay unchanged.
+      let tab2Keys = await getKeys(readerTab2);
+      await endExplicitSnapshot(readerTab2);
 
-    // Verify final state, it should match the initial state since applied
-    // mutations only changed the key order. An explicit snapshot is used.
-    await beginExplicitSnapshot(readerTab1);
-    await verifyState(readerTab1, initialState);
-    await endExplicitSnapshot(readerTab1);
+      // Verify the key order is the same.
+      is(tab2Keys.length, tab1Keys.length, "Correct keys length");
+      for (let i = 0; i < tab2Keys.length; i++) {
+        is(tab2Keys[i], tab1Keys[i], "Correct key");
+      }
+
+      // Verify final state, it should match the initial state since applied
+      // mutations only changed the key order. An explicit snapshot is used.
+      await beginExplicitSnapshot(readerTab1);
+      await verifyState(readerTab1, initialState);
+      await endExplicitSnapshot(readerTab1);
+    }
   }
 
   // - Clean up.
   await cleanupTabs(knownTabs);
 
   clearOrigin();
 });
--- a/gfx/layers/ipc/SharedSurfacesParent.cpp
+++ b/gfx/layers/ipc/SharedSurfacesParent.cpp
@@ -50,29 +50,31 @@ void SharedSurfacesParent::Shutdown() {
   sInstance = nullptr;
 }
 
 /* static */
 already_AddRefed<DataSourceSurface> SharedSurfacesParent::Get(
     const wr::ExternalImageId& aId) {
   StaticMutexAutoLock lock(sMutex);
   if (!sInstance) {
+    gfxCriticalNote << "SSP:Get " << aId.mHandle << " shtd";
     return nullptr;
   }
 
   RefPtr<SourceSurfaceSharedDataWrapper> surface;
   sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface));
   return surface.forget();
 }
 
 /* static */
 already_AddRefed<DataSourceSurface> SharedSurfacesParent::Acquire(
     const wr::ExternalImageId& aId) {
   StaticMutexAutoLock lock(sMutex);
   if (!sInstance) {
+    gfxCriticalNote << "SSP:Acq " << aId.mHandle << " shtd";
     return nullptr;
   }
 
   RefPtr<SourceSurfaceSharedDataWrapper> surface;
   sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface));
 
   if (surface) {
     DebugOnly<bool> rv = surface->AddConsumer();
@@ -106,16 +108,17 @@ bool SharedSurfacesParent::Release(const
 
 /* static */
 void SharedSurfacesParent::AddSameProcess(const wr::ExternalImageId& aId,
                                           SourceSurfaceSharedData* aSurface) {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
   StaticMutexAutoLock lock(sMutex);
   if (!sInstance) {
+    gfxCriticalNote << "SSP:Ads " << aId.mHandle << " shtd";
     return;
   }
 
   // If the child bridge detects it is in the combined UI/GPU process, then it
   // will insert a wrapper surface holding the shared memory buffer directly.
   // This is good because we avoid mapping the same shared memory twice, but
   // still allow the original surface to be freed and remove the wrapper from
   // the table when it is no longer needed.
@@ -163,24 +166,26 @@ void SharedSurfacesParent::DestroyProces
 /* static */
 void SharedSurfacesParent::Add(const wr::ExternalImageId& aId,
                                const SurfaceDescriptorShared& aDesc,
                                base::ProcessId aPid) {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   MOZ_ASSERT(aPid != base::GetCurrentProcId());
   StaticMutexAutoLock lock(sMutex);
   if (!sInstance) {
+    gfxCriticalNote << "SSP:Add " << aId.mHandle << " shtd";
     return;
   }
 
   // Note that the surface wrapper maps in the given handle as read only.
   RefPtr<SourceSurfaceSharedDataWrapper> surface =
       new SourceSurfaceSharedDataWrapper();
   if (NS_WARN_IF(!surface->Init(aDesc.size(), aDesc.stride(), aDesc.format(),
                                 aDesc.handle(), aPid))) {
+    gfxCriticalNote << "SSP:Add " << aId.mHandle << " init";
     return;
   }
 
   uint64_t id = wr::AsUint64(aId);
   MOZ_ASSERT(!sInstance->mSurfaces.Contains(id));
 
   RefPtr<wr::RenderSharedSurfaceTextureHost> texture =
       new wr::RenderSharedSurfaceTextureHost(surface);
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2590,18 +2590,17 @@ static FeatureState& WebRenderHardwareQu
             // we have a desktop CAYMAN, SI, CIK, VI, or GFX9 device
           } else {
             featureWebRenderQualified.Disable(
                 FeatureStatus::Blocked, "Device too old",
                 NS_LITERAL_CSTRING("FEATURE_FAILURE_DEVICE_TOO_OLD"));
           }
 #endif
 #ifdef NIGHTLY_BUILD
-        } else if (adapterVendorID == u"0x8086" ||
-                   adapterVendorID == u"mesa/i965") {  // Intel
+        } else if (adapterVendorID == u"0x8086") {  // Intel
           const uint16_t supportedDevices[] = {
               // skylake gt2+
               0x1912,
               0x1913,
               0x1915,
               0x1916,
               0x1917,
               0x191a,
@@ -2665,30 +2664,35 @@ static FeatureState& WebRenderHardwareQu
             if (deviceID == id) {
               supported = true;
             }
           }
           if (!supported) {
             featureWebRenderQualified.Disable(
                 FeatureStatus::Blocked, "Device too old",
                 NS_LITERAL_CSTRING("FEATURE_FAILURE_DEVICE_TOO_OLD"));
-          } else if (adapterVendorID == u"mesa/i965") {
+          }
+#  ifdef MOZ_WIDGET_GTK
+          else {
+            // Performance is not great on 4k screens with WebRender + Linux.
+            // Disable it for now if it is too large.
             const int32_t maxPixels = 3440 * 1440;  // UWQHD
             int32_t pixels = aScreenSize.width * aScreenSize.height;
             if (pixels > maxPixels) {
               featureWebRenderQualified.Disable(
                   FeatureStatus::Blocked, "Screen size too large",
                   NS_LITERAL_CSTRING("FEATURE_FAILURE_SCREEN_SIZE_TOO_LARGE"));
             } else if (pixels <= 0) {
               featureWebRenderQualified.Disable(
                   FeatureStatus::Blocked, "Screen size unknown",
                   NS_LITERAL_CSTRING("FEATURE_FAILURE_SCREEN_SIZE_UNKNOWN"));
             }
           }
-#endif
+#  endif  // MOZ_WIDGET_GTK
+#endif    // NIGHTLY_BUILD
         } else {
           featureWebRenderQualified.Disable(
               FeatureStatus::Blocked, "Unsupported vendor",
               NS_LITERAL_CSTRING("FEATURE_FAILURE_UNSUPPORTED_VENDOR"));
         }
       }
     }
   } else {
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -915,17 +915,17 @@ description = See Bug 1518344 - investig
 [PVideoDecoderManager::PVideoDecoder]
 description =
 [PVideoDecoderManager::Readback]
 description =
 [PBackgroundStorage::Preload]
 description =
 [PBackgroundLSDatabase::PBackgroundLSSnapshot]
 description = See corresponding comment in PBackgroundLSDatabase.ipdl
-[PBackgroundLSSnapshot::LoadItem]
+[PBackgroundLSSnapshot::LoadValueAndMoreItems]
 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::CheckAndSuggest]
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1281,16 +1281,17 @@ pref("dom.storage.enabled", true);
 #ifdef EARLY_BETA_OR_EARLIER
 pref("dom.storage.next_gen", true);
 #else
 pref("dom.storage.next_gen", false);
 #endif
 pref("dom.storage.default_quota",      5120);
 pref("dom.storage.shadow_writes", true);
 pref("dom.storage.snapshot_prefill", 16384);
+pref("dom.storage.snapshot_gradual_prefill", 4096);
 pref("dom.storage.snapshot_reusing", true);
 pref("dom.storage.testing", false);
 pref("dom.storage.client_validation", true);
 
 pref("dom.send_after_paint_to_content", false);
 
 // Timeout clamp in ms for timeouts we clamp
 pref("dom.min_timeout_value", 4);
--- a/parser/expat/lib/xmlparse.c
+++ b/parser/expat/lib/xmlparse.c
@@ -1760,26 +1760,26 @@ XML_SetElementDeclHandler(XML_Parser par
 
 void XMLCALL
 XML_SetAttlistDeclHandler(XML_Parser parser,
                           XML_AttlistDeclHandler attdecl)
 {
   if (parser != NULL)
     attlistDeclHandler = attdecl;
 }
-#endif
-/* END MOZILLA CHANGE */
 
 void XMLCALL
 XML_SetEntityDeclHandler(XML_Parser parser,
                          XML_EntityDeclHandler handler)
 {
   if (parser != NULL)
     entityDeclHandler = handler;
 }
+#endif
+/* END MOZILLA CHANGE */
 
 void XMLCALL
 XML_SetXmlDeclHandler(XML_Parser parser,
                       XML_XmlDeclHandler handler) {
   if (parser != NULL)
     xmlDeclHandler = handler;
 }
 
--- a/parser/htmlparser/nsExpatDriver.cpp
+++ b/parser/htmlparser/nsExpatDriver.cpp
@@ -149,28 +149,16 @@ static int Driver_HandleExternalEntityRe
 
   nsExpatDriver* driver =
       static_cast<nsExpatDriver*>(aExternalEntityRefHandler);
 
   return driver->HandleExternalEntityRef(aOpenEntityNames, aBase, aSystemId,
                                          aPublicId);
 }
 
-static void Driver_HandleEntityDecl(
-    void* aUserData, const XML_Char* aEntityName, int aIsParameterEntity,
-    const XML_Char* aValue, int aValueLength, const XML_Char* aBase,
-    const XML_Char* aSystemId, const XML_Char* aPublicId,
-    const XML_Char* aNotationName) {
-  NS_ASSERTION(aUserData, "expat driver should exist");
-  if (aUserData) {
-    static_cast<nsExpatDriver*>(aUserData)->HandleEntityDecl(
-        aEntityName, aValue, aValueLength);
-  }
-}
-
 /***************************** END CALL BACKS ********************************/
 
 /***************************** CATALOG UTILS *********************************/
 
 // Initially added for bug 113400 to switch from the remote "XHTML 1.0 plus
 // MathML 2.0" DTD to the the lightweight customized version that Mozilla uses.
 // Since Mozilla is not validating, no need to fetch a *huge* file at each
 // click.
@@ -481,29 +469,16 @@ nsresult nsExpatDriver::HandleEndDoctype
     MaybeStopParser(rv);
   }
 
   mInternalSubset.Truncate();
 
   return NS_OK;
 }
 
-void nsExpatDriver::HandleEntityDecl(const char16_t* aEntityName,
-                                     const char16_t* aEntityValue,
-                                     const uint32_t aLength) {
-  MOZ_ASSERT(
-      mInInternalSubset || mInExternalDTD,
-      "Should only see entity declarations in the internal subset or in DTDs");
-  auto charLength = aLength / sizeof(char16_t);
-  nsDependentSubstring entityVal(aEntityValue, charLength);
-  if (entityVal.FindChar('<') != -1) {
-    MaybeStopParser(NS_ERROR_UNEXPECTED);
-  }
-}
-
 static nsresult ExternalDTDStreamReaderFunc(nsIUnicharInputStream* aIn,
                                             void* aClosure,
                                             const char16_t* aFromSegment,
                                             uint32_t aToOffset, uint32_t aCount,
                                             uint32_t* aWriteCount) {
   // Pass the buffer to expat for parsing.
   if (XML_Parse((XML_Parser)aClosure, (const char*)aFromSegment,
                 aCount * sizeof(char16_t), 0) == XML_STATUS_OK) {
@@ -1077,19 +1052,16 @@ nsExpatDriver::WillBuildModel(const CPar
       (XML_ExternalEntityRefHandler)Driver_HandleExternalEntityRef);
   XML_SetExternalEntityRefHandlerArg(mExpatParser, this);
   XML_SetCommentHandler(mExpatParser, Driver_HandleComment);
   XML_SetCdataSectionHandler(mExpatParser, Driver_HandleStartCdataSection,
                              Driver_HandleEndCdataSection);
 
   XML_SetParamEntityParsing(mExpatParser,
                             XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);
-  if (doc && doc->NodePrincipal()->IsSystemPrincipal()) {
-    XML_SetEntityDeclHandler(mExpatParser, Driver_HandleEntityDecl);
-  }
   XML_SetDoctypeDeclHandler(mExpatParser, Driver_HandleStartDoctypeDecl,
                             Driver_HandleEndDoctypeDecl);
 
   // Set up the user data.
   XML_SetUserData(mExpatParser, this);
 
   return mInternalState;
 }
--- a/parser/htmlparser/nsExpatDriver.h
+++ b/parser/htmlparser/nsExpatDriver.h
@@ -44,18 +44,16 @@ class nsExpatDriver : public nsIDTD, pub
   nsresult HandleDefault(const char16_t* aData, const uint32_t aLength);
   nsresult HandleStartCdataSection();
   nsresult HandleEndCdataSection();
   nsresult HandleStartDoctypeDecl(const char16_t* aDoctypeName,
                                   const char16_t* aSysid,
                                   const char16_t* aPubid,
                                   bool aHasInternalSubset);
   nsresult HandleEndDoctypeDecl();
-  void HandleEntityDecl(const char16_t* aEntityName,
-                        const char16_t* aEntityValue, const uint32_t aLength);
 
  private:
   // Load up an external stream to get external entity information
   nsresult OpenInputStreamFromExternalDTD(const char16_t* aFPIStr,
                                           const char16_t* aURLStr,
                                           const char16_t* aBaseURL,
                                           nsIInputStream** aStream,
                                           nsAString& aAbsURL);
deleted file mode 100644
--- a/testing/web-platform/meta/2dcontext/shadows/2d.shadow.enable.x.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[2d.shadow.enable.x.html]
-  [Shadows are drawn if shadowOffsetX is set]
-    expected:
-      if not debug and not webrender and not e10s and (os == "android") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-size-multicol-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-size-multicol-001.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/css-properties-values-api/registered-property-initial.html.ini
+++ b/testing/web-platform/meta/css/css-properties-values-api/registered-property-initial.html.ini
@@ -81,8 +81,14 @@
     expected: FAIL
 
   [Initial value for <length> correctly computed [6pc\]]
     expected: FAIL
 
   [Initial value for <length> correctly computed [1in\]]
     expected: FAIL
 
+  [Initial value for <url> correctly computed [url(a)\]]
+    expected: FAIL
+
+  [Initial value for <url>+ correctly computed [url(a) url(a)\]]
+    expected: FAIL
+
--- a/testing/web-platform/meta/dom/events/Event-timestamp-safe-resolution.html.ini
+++ b/testing/web-platform/meta/dom/events/Event-timestamp-safe-resolution.html.ini
@@ -1,6 +1,4 @@
 [Event-timestamp-safe-resolution.html]
   [Event timestamp should not have a resolution better than 5 microseconds]
-    expected:
-      if os == "android" and not e10s: PASS
-      FAIL
+    expected: FAIL
 
--- a/testing/web-platform/meta/fetch/sec-metadata/iframe.tentative.https.sub.html.ini
+++ b/testing/web-platform/meta/fetch/sec-metadata/iframe.tentative.https.sub.html.ini
@@ -3,8 +3,26 @@
     expected: FAIL
 
   [Same-site iframe]
     expected: FAIL
 
   [Cross-site iframe]
     expected: FAIL
 
+  [web-platform.test -> www.not-web-platform.test:8443 iframe: user-activated]
+    expected: FAIL
+
+  [web-platform.test -> web-platform.test:8443 iframe: user-activated]
+    expected: FAIL
+
+  [web-platform.test -> www.web-platform.test:8443 iframe: forced]
+    expected: FAIL
+
+  [web-platform.test -> web-platform.test:8443 iframe: forced]
+    expected: FAIL
+
+  [web-platform.test -> www.web-platform.test:8443 iframe: user-activated]
+    expected: FAIL
+
+  [web-platform.test -> www.not-web-platform.test:8443 iframe: forced]
+    expected: FAIL
+
--- a/testing/web-platform/meta/mozilla-sync
+++ b/testing/web-platform/meta/mozilla-sync
@@ -1,2 +1,2 @@
-local: d14e2042e7e706c3ba305d5a517bbaeba3f610af
-upstream: add24188a1226f3598ad6b455e71641c9ac6a5fd
+local: 8d443e94452a1f0780c9d92cb2fd1bbb69e74f53
+upstream: 2f2bf34086414fb3bd8e01e92aca1aa18e7ea730
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/notifications/__dir__.ini
@@ -0,0 +1,1 @@
+lsan-allowed: [Alloc, NS_GetXPTCallStub, NewPage, nsXPCWrappedJS::GetNewOrUsed]
--- a/testing/web-platform/meta/performance-timeline/idlharness.any.js.ini
+++ b/testing/web-platform/meta/performance-timeline/idlharness.any.js.ini
@@ -1,70 +1,46 @@
 [idlharness.https.any.serviceworker.html]
   expected: TIMEOUT
 
 [idlharness.any.serviceworker.html]
-  [PerformanceObserver interface: calling observe(PerformanceObserverInit) on observer with too few arguments must throw TypeError]
-    expected: FAIL
-  
-  [PerformanceObserver interface: operation observe(PerformanceObserverInit)]
-    expected: FAIL
-
   [PerformanceMark interface: attribute detail]
     expected: FAIL
 
   [PerformanceMark interface: mark must inherit property "detail" with the proper type]
     expected: FAIL
 
   [PerformanceMark interface object length]
     expected: FAIL
 
 
 [idlharness.any.sharedworker.html]
   [PerformanceMark interface: attribute detail]
     expected: FAIL
 
-  [PerformanceObserver interface: calling observe(PerformanceObserverInit) on observer with too few arguments must throw TypeError]
-    expected: FAIL
-  
-  [PerformanceObserver interface: operation observe(PerformanceObserverInit)]
-    expected: FAIL
-
   [PerformanceMark interface: mark must inherit property "detail" with the proper type]
     expected: FAIL
 
   [PerformanceMark interface object length]
     expected: FAIL
 
 
 [idlharness.any.html]
   [PerformanceMark interface: attribute detail]
     expected: FAIL
 
-  [PerformanceObserver interface: calling observe(PerformanceObserverInit) on observer with too few arguments must throw TypeError]
-    expected: FAIL
-  
-  [PerformanceObserver interface: operation observe(PerformanceObserverInit)]
-    expected: FAIL
-
   [PerformanceMark interface: mark must inherit property "detail" with the proper type]
     expected: FAIL
 
   [PerformanceMark interface object length]
     expected: FAIL
 
 
 [idlharness.any.worker.html]
   [PerformanceMark interface: attribute detail]
     expected: FAIL
 
-  [PerformanceObserver interface: calling observe(PerformanceObserverInit) on observer with too few arguments must throw TypeError]
-    expected: FAIL
-  
-  [PerformanceObserver interface: operation observe(PerformanceObserverInit)]
-    expected: FAIL
-
   [PerformanceMark interface: mark must inherit property "detail" with the proper type]
     expected: FAIL
 
   [PerformanceMark interface object length]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/performance-timeline/po-observer.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[po-observe.html]
-  expected: TIMEOUT
-
--- a/testing/web-platform/meta/resource-timing/resource-timing-level1.sub.html.ini
+++ b/testing/web-platform/meta/resource-timing/resource-timing-level1.sub.html.ini
@@ -1,4 +1,4 @@
 [resource-timing-level1.sub.html]
   disabled:
-    if (os == "mac"): https://bugzilla.mozilla.org/show_bug.cgi?id=1543604
-    if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1543604
+    if os == "mac": https://bugzilla.mozilla.org/show_bug.cgi?id=1543604
+    if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1543604
--- a/testing/web-platform/meta/screen-capture/getdisplaymedia.https.html.ini
+++ b/testing/web-platform/meta/screen-capture/getdisplaymedia.https.html.ini
@@ -1,64 +1,64 @@
 [getdisplaymedia.https.html]
   [getDisplayMedia({"video":true}) must succeed with video]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":true,"audio":false}) must succeed with video]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"audio":false}) must succeed with video]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({}) must succeed with video]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia(undefined) must succeed with video]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":true,"audio":true}) must succeed with video maybe audio]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"audio":true}) must succeed with video maybe audio]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
       PASS
 
   [getDisplayMedia({"video":{"width":{"max":360}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"height":{"max":240}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"width":{"max":360},"height":{"max":240}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"frameRate":{"max":4}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"frameRate":{"max":4},"width":{"max":360}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"frameRate":{"max":4},"height":{"max":240}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia({"video":{"frameRate":{"max":4},"width":{"max":360},"height":{"max":240}}}) must be constrained]
     expected:
-      if os == "android" and not e10s: FAIL
+      if (os == "android") and not e10s: FAIL
 
   [getDisplayMedia() with getSettings]
     expected: FAIL
 
   [getDisplayMedia({"audio":true}) must fail with TypeError]
     expected: FAIL
 
--- a/testing/web-platform/meta/service-workers/service-worker/__dir__.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/__dir__.ini
@@ -1,3 +1,3 @@
 prefs: [dom.serviceWorkers.enabled:true]
-lsan-allowed: [Alloc, CompareNetwork, Create, EntrySlotOrCreate, MakeUnique, Malloc, NS_NewLoadGroup, NewChannelFromURIWithProxyFlagsInternal, NewPage, PLDHashTable::Add, Realloc, SharedMutex, Then, createTable, mozilla::BasePrincipal::CreateCodebasePrincipal, mozilla::SchedulerGroup::CreateEventTargetFor, mozilla::ThrottledEventQueue::Create, mozilla::detail::UniqueSelector, mozilla::dom::PerformanceStorageWorker::Create, mozilla::dom::ServiceWorkerJobQueue::RunJob, mozilla::dom::ServiceWorkerManager::Unregister, mozilla::dom::ServiceWorkerRegistrationMainThread::Unregister, mozilla::dom::UnregisterCallback::UnregisterCallback, mozilla::dom::WorkerCSPEventListener::Create, mozilla::dom::WorkerPrivate::EnsurePerformanceCounter, mozilla::dom::WorkerPrivate::WorkerPrivate, mozilla::dom::cache::CacheOpChild::Recv__delete__, mozilla::dom::serviceWorkerScriptCache::, mozilla::net::HttpBaseChannel::HttpBaseChannel, mozilla::net::HttpChannelChild::HttpChannelChild, mozilla::net::nsHttpHandler::NewProxiedChannel, mozilla::net::nsIOService::NewChannelFromURIWithProxyFlagsInternal, mozilla::net::nsStandardURL::TemplatedMutator, nsPermission::Create, nsTimer, nsTimer::WithEventTarget, operator]
+lsan-allowed: [Alloc, CompareNetwork, Create, EntrySlotOrCreate, MakeUnique, Malloc, NS_NewLoadGroup, NewChannelFromURIWithProxyFlagsInternal, NewPage, PLDHashTable::Add, Realloc, SharedMutex, Then, createTable, mozilla::BasePrincipal::CreateCodebasePrincipal, mozilla::SchedulerGroup::CreateEventTargetFor, mozilla::ThrottledEventQueue::Create, mozilla::WeakPtr, mozilla::detail::UniqueSelector, mozilla::dom::PerformanceStorageWorker::Create, mozilla::dom::ServiceWorkerJobQueue::RunJob, mozilla::dom::ServiceWorkerManager::Unregister, mozilla::dom::ServiceWorkerRegistrationMainThread::Unregister, mozilla::dom::UnregisterCallback::UnregisterCallback, mozilla::dom::WorkerCSPEventListener::Create, mozilla::dom::WorkerPrivate::EnsurePerformanceCounter, mozilla::dom::WorkerPrivate::WorkerPrivate, mozilla::dom::cache::CacheOpChild::Recv__delete__, mozilla::dom::serviceWorkerScriptCache::, mozilla::net::HttpBaseChannel::HttpBaseChannel, mozilla::net::HttpChannelChild::HttpChannelChild, mozilla::net::nsHttpHandler::NewProxiedChannel, mozilla::net::nsIOService::NewChannelFromURIWithProxyFlagsInternal, mozilla::net::nsStandardURL::TemplatedMutator, nsPermission::Create, nsTimer, nsTimer::WithEventTarget, operator]
 leak-threshold: [default:51200, tab:51200]
--- a/testing/web-platform/meta/service-workers/service-worker/navigate-window.https.html.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/navigate-window.https.html.ini
@@ -1,4 +1,6 @@
 [navigate-window.https.html]
+  expected:
+    if sw-e10s: CRASH
   [Clients.matchAll() should not show an old window after it navigates.]
     expected: FAIL
 
--- a/testing/web-platform/meta/service-workers/service-worker/unregister-then-register.https.html.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/unregister-then-register.https.html.ini
@@ -1,3 +1,3 @@
 [unregister-then-register.https.html]
   disabled:
-    if os == "android" and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499972
+    if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499972
--- a/testing/web-platform/meta/service-workers/service-worker/update-not-allowed.https.html.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/update-not-allowed.https.html.ini
@@ -1,3 +1,3 @@
 [update-not-allowed.https.html]
   disabled:
-    if os == "android" and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499972
+    if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499972
--- a/testing/web-platform/meta/webrtc/__dir__.ini
+++ b/testing/web-platform/meta/webrtc/__dir__.ini
@@ -1,1 +1,2 @@
 prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, privacy.resistFingerprinting.reduceTimerPrecision.jitter:false, privacy.reduceTimerPrecision:false]
+leak-threshold: [default:51200]
--- a/testing/web-platform/tests/css/css-contain/contain-size-multicol-001.html
+++ b/testing/web-platform/tests/css/css-contain/contain-size-multicol-001.html
@@ -13,16 +13,17 @@ div {
 #red {
   background: red;
   width: 100px;
   height: 100px;
 }
 #test {
   background: green;
 
+  contain: size;
   columns: 2 40px;
   column-gap: 20px;
   min-height: 100px;
 
   color: transparent;
 }
 </style>
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-flexbox/flex-minimum-height-flex-items-014.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Flexbox: min-height: auto with nested flexboxes and justify-content</title>
+<link rel="author" title="Google LLC" href="https://www.google.com/" />
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#min-size-auto" />
+<link rel="issue" href="https://bugs.chromium.org/p/chromium/issues/detail?id=945214" />
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
+
+<style>
+.overlapped-green {
+  position: absolute;
+  background-color: green;
+  width: 100px;
+  height: 100px;
+  z-index: 1;
+}
+
+.outer {
+  display: flex;
+  flex-direction: column;
+  height: 100px;
+  width: 100px;
+  background: green;
+}
+
+.inner {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-end;
+  height: 100%;
+  background-color: red;
+}
+
+.spacer {
+  height: 30px;
+  width: 100px;
+  background: red;
+  flex: none;
+}
+</style>
+<body>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+<div class="overlapped-green"></div>
+<div class="outer">
+  <div class="spacer"></div>
+  <div class="inner">
+    <div class="spacer"></div>
+  </div>
+</div>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-position/position-absolute-crash-chrome-004.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>CSS Position Absolute: Chrome crash</title>
+<link rel="author" href="mailto:atotic@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=946986">
+<meta name="assert" content="Nested abs/fixed/flex do not crash">
+<style>
+  body { overflow: scroll;}
+  .container {
+    position: relative;
+    contain: paint;
+  }
+  .flex {
+    display: flex;
+  }
+  .fixed {
+    position: fixed;
+  }
+  .abs {
+    position: absolute;
+  }
+</style>
+<!-- LayoutNG currently does not support display:flex.
+  Propagation of descendants across flex boundaries is error prone -->
+<div id="one" class="container" style="">
+  <div class="flex">
+    <div class="abs">
+      <div class="flex">
+        <div id="fixed1" class="fixed">
+          <div id="fixed2" class="fixed"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+test(() => {
+}, 'test passes if it does not crash');
+</script>
+
--- a/testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html
@@ -25,16 +25,20 @@ test_initial_value({ syntax: '<length>',
 test_initial_value({ syntax: '<length>', initialValue: '72pt' }, '96px');
 test_initial_value({ syntax: '<percentage>', initialValue: 'calc(10% + 20%)' }, '30%');
 test_initial_value({ syntax: '<length-percentage>', initialValue: 'calc(1in + 10% + 4px)' }, 'calc(10% + 100px)');
 test_initial_value({ syntax: '<color>', initialValue: 'pink', inherits: true }, 'rgb(255, 192, 203)');
 test_initial_value({ syntax: '<color>', initialValue: 'purple' }, 'rgb(128, 0, 128)');
 test_initial_value({ syntax: '<transform-function>', initialValue: 'rotate(42deg)' }, 'rotate(42deg)');
 test_initial_value({ syntax: '<transform-list>', initialValue: 'scale(calc(2 + 2))' }, 'scale(4)');
 test_initial_value({ syntax: '<transform-list>', initialValue: 'scale(calc(2 + 1)) translateX(calc(3px + 1px))' }, 'scale(3) translateX(4px)');
+test_initial_value({ syntax: '<url>', initialValue: 'url(a)' },
+    `url("${new URL('a', document.baseURI)}")`);
+test_initial_value({ syntax: '<url>+', initialValue: 'url(a) url(a)' },
+    `url("${new URL('a', document.baseURI)}") url("${new URL('a', document.baseURI)}")`);
 
 // Test that the initial value of the custom property 'reg' is successfully
 // substituted into 'property'.
 function test_substituted_value(reg, property, expected) {
     let inherits_text = reg.inherits === true ? 'inherited' : 'non-inherited';
     test(function(){
         try {
             let name = generate_property(reg);
--- a/testing/web-platform/tests/css/css-values/calc-numbers.html
+++ b/testing/web-platform/tests/css/css-values/calc-numbers.html
@@ -66,17 +66,17 @@ https://chromium.googlesource.com/chromi
     the declaration to become invalid. The value resulting
     from an expression must be clamped to the range
     allowed in the target context.
     https://www.w3.org/TR/css-values-3/#calc-range
     */
 
     verifyComputedStyle("opacity", "initial", "calc(2 / 4)", "0.5", "testing opacity: calc(2 / 4)");
 
-    verifyComputedStyle("tab-size", "12345", "calc(2 / 4)", "1", "testing tab-size: calc(2 / 4)");
+    verifyComputedStyle("tab-size", "12345", "calc(2 / 4)", "0.5", "testing tab-size: calc(2 / 4)");
     /*
     'tab-size' accepts <number> values.
     */
 
     verifyComputedStyle("opacity", "0.9", "calc(2 / 4) * 1px", "0.9", "testing opacity: calc(2 / 4) * 1px");
 
     verifyComputedStyle("tab-size", "12345", "calc(1 + 1px)", "12345", "testing tab-size: calc(1 + 1px)");
 
--- a/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reftest.list
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reftest.list
@@ -33,9 +33,9 @@
 == border-image-repeat-space-6.html border-image-repeat-space-6-ref.html
 == border-image-repeat-space-7.html border-image-repeat-space-7-ref.html
 == border-image-repeat-1.html border-image-repeat-1-ref.html
 
 # background-attachment test cases
 == background-attachment-fixed-inside-transform-1.html background-attachment-fixed-inside-transform-1-ref.html
 
 # box-shadow with currentcolor test cases
-== box-shadow-currentcolor.html box-shadow-currentcolor-ref.html
\ No newline at end of file
+== box-shadow-currentcolor.html box-shadow-currentcolor-ref.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: scroll;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:scroll' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-002-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: scroll;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: auto;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:auto' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-003-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: auto;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: hidden;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow. -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:hidden' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-004-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: hidden;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: scroll;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:scroll' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-002-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: scroll;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: auto;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:auto' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-003-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: auto;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: hidden;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow. -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:hidden' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-004-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: hidden;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
--- a/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list
@@ -14,17 +14,23 @@
 == contain-paint-ignored-cases-internal-table-001b.html contain-paint-ignored-cases-internal-table-001-ref.html
 == contain-paint-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html
 == contain-paint-ignored-cases-ruby-containing-block-001.html contain-paint-ignored-cases-ruby-containing-block-001-ref.html
 == contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html
 == contain-paint-stacking-context-001a.html contain-paint-stacking-context-001-ref.html
 == contain-paint-stacking-context-001b.html contain-paint-stacking-context-001-ref.html
 == contain-size-button-001.html contain-size-button-001-ref.html
 == contain-size-block-001.html contain-size-block-001-ref.html
+== contain-size-block-002.html contain-size-block-002-ref.html
+== contain-size-block-003.html contain-size-block-003-ref.html
+== contain-size-block-004.html contain-size-block-004-ref.html
 == contain-size-inline-block-001.html contain-size-inline-block-001-ref.html
+== contain-size-inline-block-002.html contain-size-inline-block-002-ref.html
+== contain-size-inline-block-003.html contain-size-inline-block-003-ref.html
+== contain-size-inline-block-004.html contain-size-inline-block-004-ref.html
 == contain-size-flex-001.html contain-size-flex-001-ref.html
 == contain-size-inline-flex-001.html contain-size-inline-flex-001-ref.html
 == contain-size-grid-001.html contain-size-grid-001-ref.html
 == contain-size-multicol-001.html contain-size-multicol-001-ref.html
 == contain-size-fieldset-001.html contain-size-fieldset-001-ref.html
 == contain-size-fieldset-002.html contain-size-fieldset-002-ref.html
 == contain-size-multicol-002.html contain-size-multicol-002-ref.html
 == contain-size-multicol-003.html contain-size-multicol-003-ref.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<style>
+  .outer {
+    margin-bottom: 2px;
+    height: 60px;
+  }
+  .inner {
+    background: lime;
+    height: 100%;
+  }
+</style>
+<p>Test passes if you see four 60px-tall lime rows (with platform-appropriate scrollbars on the last one).</p>
+
+<div class="outer"><div class="inner"></div></div>
+<div class="outer"><div class="inner"></div></div>
+<div class="outer"><div class="inner"></div></div>
+<div class="outer" style="overflow: scroll"><div class="inner"></div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with definite max-height</title>
+<link rel="match" href="flexbox-definite-sizes-005-ref.html">
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1503173">
+<style>
+  .horizFlex {
+    display: flex;
+    margin-bottom: 2px;
+  }
+  .vertFlex {
+    display: flex;
+    flex-grow: 1;
+    flex-direction: column;
+    min-height: 60px;
+  }
+  .item {
+    background: lime;
+    height: 100%;
+  }
+</style>
+<p>Test passes if you see four 60px-tall lime rows (with platform-appropriate scrollbars on the last one).</p>
+
+<div class="horizFlex">
+  <div class="vertFlex">
+    <div class="item"></div>
+  </div>
+</div>
+
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: hidden">
+    <div class="item"></div>
+  </div>
+</div>
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: auto">
+    <div class="item"></div>
+  </div>
+</div>
+
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: scroll">
+    <div class="item"></div>
+  </div>
+</div>
--- a/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list
+++ b/testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list
@@ -149,16 +149,17 @@
 == flexbox-intrinsic-ratio-007.html flexbox-intrinsic-ratio-007-ref.html
 == flexbox-intrinsic-ratio-007v.html flexbox-intrinsic-ratio-007-ref.html
 
 # Test for definite and indefinite sizes.
 == flexbox-definite-sizes-001.html flexbox-definite-sizes-001-ref.html
 == flexbox-definite-sizes-002.html flexbox-definite-sizes-001-ref.html
 == flexbox-definite-sizes-003.html flexbox-definite-sizes-001-ref.html
 == flexbox-definite-sizes-004.html flexbox-definite-sizes-001-ref.html
+== flexbox-definite-sizes-005.html flexbox-definite-sizes-005-ref.html
 
 # Tests for flex items as (pseudo) stacking contexts
 == flexbox-items-as-stacking-contexts-001.xhtml flexbox-items-as-stacking-contexts-001-ref.xhtml
 == flexbox-items-as-stacking-contexts-002.html flexbox-items-as-stacking-contexts-002-ref.html
 == flexbox-items-as-stacking-contexts-003.html flexbox-items-as-stacking-contexts-003-ref.html
 
 # Tests for main-axis alignment (jusify-content property)
 == flexbox-justify-content-horiz-001a.xhtml flexbox-justify-content-horiz-001-ref.xhtml
--- a/testing/web-platform/tests/fetch/sec-metadata/embed.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/embed.tentative.https.sub.html
@@ -11,17 +11,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "embed-same-origin" + nonce;
 
       let e = document.createElement('embed');
       e.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"same-origin", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"same-origin", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -30,17 +30,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "embed-same-site" + nonce;
 
       let e = document.createElement('embed');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"same-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"same-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -49,17 +49,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "embed-cross-site" + nonce;
 
       let e = document.createElement('embed');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"cross-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"cross-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
--- a/testing/web-platform/tests/fetch/sec-metadata/fetch.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/fetch.tentative.https.sub.html
@@ -6,80 +6,80 @@
   // Site
   promise_test(t => {
     return fetch("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Same-origin fetch");
 
   promise_test(t => {
     return fetch("https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Same-site fetch");
 
   promise_test(t => {
     return fetch("https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Cross-site fetch");
 
   // Mode
   promise_test(t => {
     return fetch("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py", {mode: "same-origin"})
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "same-origin",
           });
         });
   }, "Same-origin mode");
 
   promise_test(t => {
     return fetch("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py", {mode: "cors"})
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "CORS mode");
 
   promise_test(t => {
     return fetch("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py", {mode: "no-cors"})
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "no-cors",
           });
         });
   }, "no-CORS mode");
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/font.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/font.tentative.https.sub.html
@@ -41,41 +41,41 @@
     }
   </style>
 </body>
 <script>
   document.fonts.ready.then(function () {
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-same-origin";
-        let expected = {"dest":"font", "site":"same-origin", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"same-origin", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
         });
     }, "Same-Origin font");
 
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-same-site";
-        let expected = {"dest":"font", "site":"same-site", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"same-site", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
         });
     }, "Same-Site font");
 
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-cross-site";
-        let expected = {"dest":"font", "site":"cross-site", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"cross-site", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
         });
     }, "Cross-Site font");
 
--- a/testing/web-platform/tests/fetch/sec-metadata/iframe.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/iframe.tentative.https.sub.html
@@ -1,63 +1,85 @@
 <!DOCTYPE html>
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
 <script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/common/utils.js></script>
 <body>
 <script>
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+  const USER = true;
+  const FORCED = false;
+
+  function create_test(host, user_activated, expectations) {
+    async_test(t => {
+      let i = document.createElement('iframe');
+      window.addEventListener('message', t.step_func(e => {
+        if (e.source != i.contentWindow)
+          return;
+
+        assert_header_equals(e.data, expectations);
+        t.done();
+      }));
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "same-origin",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+      let url = `https://${host}/fetch/sec-metadata/resources/post-to-owner.py`;
+      if (user_activated == FORCED) {
+        i.src = url;
+        document.body.appendChild(i);
+      } else if (user_activated == USER) {
+        let uuid = token();
+        i.name = uuid;
+        let a = document.createElement('a');
+        a.href = url;
+        a.target = uuid;
+        a.text = "This is a link!";
 
-    document.body.appendChild(i);
-  }, "Same-origin iframe");
+        document.body.appendChild(i);
+        document.body.appendChild(a);
 
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+        test_driver.click(a);
+      }
+    }, `{{host}} -> ${host} iframe: ${user_activated ? "user-activated" : "forced"}`);
+  }
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "same-site",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+  create_test("{{host}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "same-origin",
+    "user": "",
+    "mode": "nested-navigate"
+  });
 
-    document.body.appendChild(i);
-  }, "Same-site iframe");
+  create_test("{{hosts[][www]}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "same-site",
+    "user": "",
+    "mode": "nested-navigate"
+  });
+
+  create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "cross-site",
+    "user": "",
+    "mode": "nested-navigate"
+  });
 
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+  create_test("{{host}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "same-origin",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "cross-site",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+  create_test("{{hosts[][www]}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "same-site",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
 
-    document.body.appendChild(i);
-  }, "Cross-site iframe");
+  create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "cross-site",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/img.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/img.tentative.https.sub.html
@@ -18,17 +18,19 @@
           "dest": headers["sec-fetch-dest"],
           "mode": headers["sec-fetch-mode"],
           "site": headers["sec-fetch-site"],
           "user": headers["sec-fetch-user"]
         };
         assert_header_equals(got, {
           "dest": "image",
           "site": "same-origin",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
     "Same-origin image");
 
   promise_test(() =>
     loadImageInWindow(
       "https://{{hosts[][www]}}:{{ports[https][0]}}/referrer-policy/generic/subresource/image.py",
@@ -40,17 +42,19 @@
           "dest": headers["sec-fetch-dest"],
           "mode": headers["sec-fetch-mode"],
           "site": headers["sec-fetch-site"],
           "user": headers["sec-fetch-user"]
         };
         assert_header_equals(got, {
           "dest": "image",
           "site": "same-site",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
     "Same-site image");
 
   promise_test(() =>
     loadImageInWindow(
       "https://{{hosts[alt][www]}}:{{ports[https][0]}}/referrer-policy/generic/subresource/image.py",
@@ -62,14 +66,16 @@
           "dest": headers["sec-fetch-dest"],
           "mode": headers["sec-fetch-mode"],
           "site": headers["sec-fetch-site"],
           "user": headers["sec-fetch-user"]
         };
         assert_header_equals(got, {
           "dest": "image",
           "site": "cross-site",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
     "Cross-site image");
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/object.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/object.tentative.https.sub.html
@@ -11,17 +11,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "object-same-origin" + nonce;
 
       let e = document.createElement('object');
       e.data = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"same-origin", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"same-origin", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -30,17 +30,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "object-same-site" + nonce;
 
       let e = document.createElement('object');
       e.data = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"same-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"same-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -49,17 +49,17 @@
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "object-cross-site" + nonce;
 
       let e = document.createElement('object');
       e.data = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"cross-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"cross-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
--- a/testing/web-platform/tests/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html
@@ -10,17 +10,17 @@
   let nonce = token();
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-cross-site-same-origin" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
       e.onerror = e => {
@@ -36,17 +36,17 @@
   }, "Cross-Site -> Same-Origin redirect");
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-cross-site-same-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
       e.onerror = e => {
@@ -62,17 +62,17 @@
   }, "Cross-Site -> Same-Site redirect");
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-cross-site-cross-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
       e.onerror = e => {
--- a/testing/web-platform/tests/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
@@ -12,17 +12,17 @@
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-multiple-cross-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{host}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-origin
       "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// cross-site
       "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;// same-origin
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
--- a/testing/web-platform/tests/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html
@@ -12,17 +12,17 @@
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-multiple-same-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{host}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-origin
       "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-site
       "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;// same-origin
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
--- a/testing/web-platform/tests/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html
@@ -10,17 +10,17 @@
   let nonce = token();
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-origin-same-origin" + nonce;
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-origin", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-origin", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
@@ -37,17 +37,17 @@
   }, "Same-Origin -> Same-Origin redirect");
 
 promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-origin-same-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
@@ -64,17 +64,17 @@ promise_test(t => {
   }, "Same-Origin -> Same-Site redirect");
 
 promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-origin-cross-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
--- a/testing/web-platform/tests/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html
@@ -10,17 +10,17 @@
   let nonce = token();
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-site-same-origin" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
@@ -37,17 +37,17 @@
   }, "Same-Site -> Same-Origin redirect");
 
 promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-site-same-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
@@ -64,17 +64,17 @@ promise_test(t => {
   }, "Same-Site -> Same-Site redirect");
 
 promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "redirect-same-site-cross-site" + nonce;
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
--- a/testing/web-platform/tests/fetch/sec-metadata/report.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/report.tentative.https.sub.html
@@ -17,16 +17,16 @@
       }, 1000);
     }, name + " report");
   }
 
   let counter = 0;
   document.addEventListener("securitypolicyviolation", (e) => {
     counter++;
     if (counter == 3) {
-      generate_test({"dest":"report", "site":"same-origin", "user":"?F", "mode": "no-cors"}, "same-origin");
-      generate_test({"dest":"report", "site":"same-site", "user":"?F", "mode": "no-cors"}, "same-site");
-      generate_test({"dest":"report", "site":"cross-site", "user":"?F", "mode": "no-cors"}, "cross-site");
+      generate_test({"dest":"report", "site":"same-origin", "user":"", "mode": "no-cors"}, "same-origin");
+      generate_test({"dest":"report", "site":"same-site", "user":"", "mode": "no-cors"}, "same-site");
+      generate_test({"dest":"report", "site":"cross-site", "user":"", "mode": "no-cors"}, "cross-site");
 
       done();
     }
   });
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/script.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/script.tentative.https.sub.html
@@ -7,58 +7,58 @@
 <script src="https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-script.py"></script>
 <script>
   test(t => {
     t.add_cleanup(_ => { header = null; });
 
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-origin",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Same-origin script");
 </script>
 
 <!-- Same-site script -->
 <script src="https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-script.py"></script>
 <script>
   test(t => {
     t.add_cleanup(_ => { header = null; });
 
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-site",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Same-site script");
 </script>
 
 <!-- Cross-site script -->
 <script src="https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-script.py"></script>
 <script>
   test(t => {
     t.add_cleanup(_ => { header = null; });
 
     assert_header_equals(header, {
       "dest": "script",
       "site": "cross-site",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Cross-site script");
 </script>
 
 <!-- Same-origin script, CORS mode -->
 <script src="https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-script.py" crossorigin="anonymous"></script>
 <script>
   test(t => {
     t.add_cleanup(_ => { header = null; });
 
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-origin",
-      "user": "?F",
+      "user": "",
       "mode": "cors",
     });
   }, "Same-origin CORS script");
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/serviceworker.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/serviceworker.tentative.https.sub.html
@@ -33,17 +33,17 @@
       }
   </script>
 </body>
 
 <script>
   function test_same_origin(){
     promise_test(t => {
     return new Promise((resolve, reject) => {
-      let expected = {"dest":"serviceworker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+      let expected = {"dest":"serviceworker", "site":"same-origin", "user":"", "mode": "same-origin"};
       fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
         .then(response => response.text())
         .then(text => assert_header_equals(text, expected))
         .then(_ => resolve())
         .catch(e => reject(e));
       })
     })
   }
--- a/testing/web-platform/tests/fetch/sec-metadata/sharedworker.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/sharedworker.tentative.https.sub.html
@@ -23,17 +23,17 @@
         test_same_origin();
       }
       sharedWorker.port.postMessage("Ready");
     }
 
   function test_same_origin(){
     promise_test(t => {
       return new Promise((resolve, reject) => {
-        let expected = {"dest":"sharedworker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+        let expected = {"dest":"sharedworker", "site":"same-origin", "user":"", "mode": "same-origin"};
 
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       })
     }, "Same-Origin sharedworker")
--- a/testing/web-platform/tests/fetch/sec-metadata/style.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/style.tentative.https.sub.html
@@ -12,17 +12,17 @@
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "style-same-origin" + nonce;
 
       let e = document.createElement('link');
       e.rel = "stylesheet";
       e.href = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-origin", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"same-origin", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -32,17 +32,17 @@
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "style-same-site" + nonce;
 
       let e = document.createElement('link');
       e.rel = "stylesheet";
       e.href = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-site", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"same-site", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -52,17 +52,17 @@
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "style-cross-site" + nonce;
 
       let e = document.createElement('link');
       e.rel = "stylesheet";
       e.href = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"cross-site", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
@@ -73,17 +73,17 @@
     return new Promise((resolve, reject) => {
       let key = "style-same-origin-cors" + nonce;
 
       let e = document.createElement('link');
       e.rel = "stylesheet";
       e.href = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.crossOrigin = "anonymous";
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-origin", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"style", "site":"same-origin", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
 
       document.body.appendChild(e);
--- a/testing/web-platform/tests/fetch/sec-metadata/track.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/track.tentative.https.sub.html
@@ -31,17 +31,17 @@
       let key = "track-same-origin" + nonce;
       let video = createVideoElement();
       let el = createTrack();
       el.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       el.onload = t.step_func(_ => {
         expected = {
           "dest": "track",
           "site": "same-origin",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
             .then(response => response.text())
             .then(text => assert_header_equals(text, expected))
             .then(_ => resolve());
       });
       video.appendChild(el);
@@ -54,17 +54,17 @@
       let key = "track-same-site" + nonce;
       let video = createVideoElement();
       let el = createTrack();
       el.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       el.onload = t.step_func(_ => {
         expected = {
           "dest": "track",
           "site": "same-site",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
             .then(response => response.text())
             .then(text => assert_header_equals(text, expected))
             .then(resolve)
             .catch(reject);
 
@@ -79,17 +79,17 @@
       let key = "track-cross-site" + nonce;
       let video = createVideoElement();
       let el = createTrack();
       el.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       el.onload = t.step_func(_ => {
         expected = {
           "dest": "track",
           "site": "cross-site",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
             .then(response => response.text())
             .then(text => assert_header_equals(text, expected))
             .then(resolve)
             .catch(reject);
       });
@@ -107,17 +107,17 @@
       video.crossOrigin = undefined;
 
       let el = createTrack();
       el.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       el.onload = t.step_func(_ => {
         expected = {
           "dest":"track",
           "site":"same-origin",
-          "user":"?F",
+          "user":"",
           "mode": "same-origin"
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
             .then(response => response.text())
             .then(text => assert_header_equals(text, expected))
             .then(_ => resolve());
       });
       video.appendChild(el);
--- a/testing/web-platform/tests/fetch/sec-metadata/trailing-dot.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/trailing-dot.tentative.https.sub.html
@@ -6,40 +6,40 @@
   // Site
   promise_test(t => {
     return fetch("https://{{host}}.:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Fetching a resource from the same origin, but spelled with a trailing dot.");
 
   promise_test(t => {
     return fetch("https://{{hosts[][www]}}.:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Fetching a resource from the same site, but spelled with a trailing dot.");
 
   promise_test(t => {
     return fetch("https://{{hosts[alt][www]}}.:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
         .then(r => r.json())
         .then(j => {
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
   }, "Fetching a resource from a cross-site host, spelled with a trailing dot.");
 </script>
--- a/testing/web-platform/tests/fetch/sec-metadata/window-open.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/window-open.tentative.https.sub.html
@@ -12,51 +12,51 @@
     t.add_cleanup(_ => w.close());
     window.addEventListener('message', t.step_func(e => {
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-origin",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
     }));
   }, "Same-origin window, forced");
 
   async_test(t => {
     let w = window.open("https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py");
     t.add_cleanup(_ => w.close());
     window.addEventListener('message', t.step_func(e => {
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
     }));
   }, "Same-site window, forced");
 
   async_test(t => {
     let w = window.open("https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py");
     t.add_cleanup(_ => w.close());
     window.addEventListener('message', t.step_func(e => {
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "cross-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
     }));
   }, "Cross-site window, forced");
 
   async_test(t => {
     let w = window.open("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py");
@@ -65,17 +65,17 @@
     window.addEventListener('message', t.step_func(e => {
       messages++;
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-origin",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
       if (messages == 1) {
         w.location.reload();
       } else {
         t.done();
       }
@@ -89,17 +89,17 @@
     window.addEventListener('message', t.step_func(e => {
       messages++;
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
       if (messages == 1) {
         w.location.reload();
       } else {
         t.done();
       }
@@ -113,17 +113,17 @@
     window.addEventListener('message', t.step_func(e => {
       messages++;
       if (e.source != w)
         return;
 
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "cross-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
       if (messages == 1) {
         w.location.reload();
       } else {
         t.done();
       }
--- a/testing/web-platform/tests/fetch/sec-metadata/worker.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/worker.tentative.https.sub.html
@@ -8,17 +8,17 @@
 <script>
   let nonce = token();
 
   promise_test(t => {
     return new Promise((resolve, reject) => {
       let key = "worker-same-origin" + nonce;
       let w = new Worker("/fetch/sec-metadata/resources/record-header.py?file=" + key);
       w.onmessage = e => {
-        let expected = {"dest":"worker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+        let expected = {"dest":"worker", "site":"same-origin", "user":"", "mode": "same-origin"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
           .then(_ => resolve())
           .catch(e => reject(e));
       };
     });
   }, "Same-Origin worker");
--- a/testing/web-platform/tests/fetch/sec-metadata/xslt.tentative.https.sub.html
+++ b/testing/web-platform/tests/fetch/sec-metadata/xslt.tentative.https.sub.html
@@ -7,31 +7,31 @@
 <script>
   // Open a window with XML document which loads resources via <?xml-stylesheet/> tag
   let w = window.open("resources/xslt-test.sub.xml");
   window.addEventListener('message', function(e) {
     if (e.source != w)
       return;
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+      let expected = {"dest":"xslt", "site":"same-origin", "user":"", "mode": "same-origin"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-origin")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-Origin xslt");
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"xslt", "site":"same-site", "user":"", "mode": "no-cors"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-site")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-site xslt");
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"xslt", "site":"cross-site", "user":"", "mode": "no-cors"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-cross-site")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Cross-site xslt");
 
     w.close();
   });
 
--- a/testing/web-platform/tests/interfaces/performance-timeline.idl
+++ b/testing/web-platform/tests/interfaces/performance-timeline.idl
@@ -18,17 +18,17 @@ interface PerformanceEntry {
   readonly    attribute DOMHighResTimeStamp duration;
   [Default] object toJSON();
 };
 
 callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries,
                                              PerformanceObserver observer);
 [Constructor(PerformanceObserverCallback callback), Exposed=(Window,Worker)]
 interface PerformanceObserver {
-  void observe(PerformanceObserverInit options);
+  void observe(optional PerformanceObserverInit options);
   void disconnect();
   PerformanceEntryList takeRecords();
   static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
 };
 
 dictionary PerformanceObserverInit {
   sequence<DOMString> entryTypes;
   DOMString type;
--- a/toolkit/components/corroborator/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/corroborator/test/xpcshell/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 tags = corroborator
 support-files =
   data/**
 
 [test_verify_jar.js]
+skip-if = true # Bug 1549147 - Disable temporily until non-expired cert is available to sign test XPI.
--- a/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
@@ -458,16 +458,17 @@ function getGfxAdapter(aSuffix = "") {
 
   return {
     description: getGfxField("adapterDescription" + aSuffix, null),
     vendorID: getGfxField("adapterVendorID" + aSuffix, null),
     deviceID: getGfxField("adapterDeviceID" + aSuffix, null),
     subsysID: getGfxField("adapterSubsysID" + aSuffix, null),
     RAM: memoryMB,
     driver: getGfxField("adapterDriver" + aSuffix, null),
+    driverVendor: getGfxField("adapterDriverVendor" + aSuffix, null),
     driverVersion: getGfxField("adapterDriverVersion" + aSuffix, null),
     driverDate: getGfxField("adapterDriverDate" + aSuffix, null),
   };
 }
 
 /**
  * Gets the service pack and build information on Windows platforms. The initial version
  * was copied from nsUpdateService.js.
@@ -1767,17 +1768,17 @@ EnvironmentCache.prototype = {
       // The following line is disabled due to main thread jank and will be enabled
       // again as part of bug 1154500.
       // DWriteVersion: getGfxField("DWriteVersion", null),
       adapters: [],
       monitors: [],
       features: {},
     };
 
-    if (!["android", "linux"].includes(AppConstants.platform)) {
+    if (AppConstants.platform !== "android") {
       let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
       try {
         gfxData.monitors = gfxInfo.getMonitors();
       } catch (e) {
         this._log.error("nsIGfxInfo.getMonitors() caught error", e);
       }
     }
 
--- a/toolkit/components/telemetry/docs/data/environment.rst
+++ b/toolkit/components/telemetry/docs/data/environment.rst
@@ -159,24 +159,25 @@ Structure:
             adapters: [
               {
                 description: <string>, // e.g. "Intel(R) HD Graphics 4600", null on failure
                 vendorID: <string>, // null on failure
                 deviceID: <string>, // null on failure
                 subsysID: <string>, // null on failure
                 RAM: <number>, // in MB, null on failure
                 driver: <string>, // null on failure
+                driverVendor: <string>, // null on failure
                 driverVersion: <string>, // null on failure
                 driverDate: <string>, // null on failure
                 GPUActive: <bool>, // currently always true for the first adapter
               },
               ...
             ],
             // Note: currently only added on Desktop. On Linux, only a single
-            // monitor is returned representing the entire virtual screen.
+            // monitor is returned for the primary screen.
             monitors: [
               {
                 screenWidth: <number>,  // screen width in pixels
                 screenHeight: <number>, // screen height in pixels
                 refreshRate: <number>,  // refresh rate in hertz (present on Windows only).
                                         //  (values <= 1 indicate an unknown value)
                 pseudoDisplay: <bool>,  // networked screen (present on Windows only)
                 scale: <number>,        // backing scale factor (present on Mac only)
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -501,16 +501,17 @@ function checkPartnerSection(data, isIni
 function checkGfxAdapter(data) {
   const EXPECTED_ADAPTER_FIELDS_TYPES = {
     description: "string",
     vendorID: "string",
     deviceID: "string",
     subsysID: "string",
     RAM: "number",
     driver: "string",
+    driverVendor: "string",
     driverVersion: "string",
     driverDate: "string",
     GPUActive: "boolean",
   };
 
   for (let f in EXPECTED_ADAPTER_FIELDS_TYPES) {
     Assert.ok(f in data, f + " must be available.");
 
@@ -626,17 +627,17 @@ function checkSystemSection(data) {
   Assert.ok(gfxData.adapters.length > 0, "There must be at least one GFX adapter.");
   for (let adapter of gfxData.adapters) {
     checkGfxAdapter(adapter);
   }
   Assert.equal(typeof gfxData.adapters[0].GPUActive, "boolean");
   Assert.ok(gfxData.adapters[0].GPUActive, "The first GFX adapter must be active.");
 
   Assert.ok(Array.isArray(gfxData.monitors));
-  if (gIsWindows || gIsMac) {
+  if (gIsWindows || gIsMac || gIsLinux) {
     Assert.ok(gfxData.monitors.length >= 1, "There is at least one monitor.");
     Assert.equal(typeof gfxData.monitors[0].screenWidth, "number");
     Assert.equal(typeof gfxData.monitors[0].screenHeight, "number");
     if (gIsWindows) {
       Assert.equal(typeof gfxData.monitors[0].refreshRate, "number");
       Assert.equal(typeof gfxData.monitors[0].pseudoDisplay, "boolean");
     }
     if (gIsMac) {
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -502,16 +502,17 @@ var snapshotFormatters = {
       delete data.directWriteVersion;
     }
 
     // Adapter tbodies.
     let adapterKeys = [
       ["adapterDescription", "gpu-description"],
       ["adapterVendorID", "gpu-vendor-id"],
       ["adapterDeviceID", "gpu-device-id"],
+      ["driverVendor", "gpu-driver-vendor"],
       ["driverVersion", "gpu-driver-version"],
       ["driverDate", "gpu-driver-date"],
       ["adapterDrivers", "gpu-drivers"],
       ["adapterSubsysID", "gpu-subsys-id"],
       ["adapterRAM", "gpu-ram"],
     ];
 
     function showGpu(id, suffix) {
--- a/toolkit/crashreporter/CrashAnnotations.yaml
+++ b/toolkit/crashreporter/CrashAnnotations.yaml
@@ -44,16 +44,21 @@ ActualStreamLen:
     Actual length of an IPC proxy stream.
   type: integer
 
 AdapterDeviceID:
   description: >
     Graphics adapter name.
   type: string
 
+AdapterDriverVendor:
+  description: >
+    Graphics adapter driver vendor.
+  type: string
+
 AdapterDriverVersion:
   description: >
     Graphics adapter driver version.
   type: string
 
 AdapterSubsysID:
   description: >
     Graphics adapter subsystem ID.
--- a/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
+++ b/toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
@@ -201,16 +201,17 @@ missing = Missing
 gpu-process-pid = GPUProcessPid
 gpu-process = GPUProcess
 gpu-description = Description
 gpu-vendor-id = Vendor ID
 gpu-device-id = Device ID
 gpu-subsys-id = Subsys ID
 gpu-drivers = Drivers
 gpu-ram = RAM
+gpu-driver-vendor = Driver Vendor
 gpu-driver-version = Driver Version
 gpu-driver-date = Driver Date
 gpu-active = Active
 webgl1-wsiinfo = WebGL 1 Driver WSI Info
 webgl1-renderer = WebGL 1 Driver Renderer
 webgl1-version = WebGL 1 Driver Version
 webgl1-driver-extensions = WebGL 1 Driver Extensions
 webgl1-extensions = WebGL 1 Extensions
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -438,25 +438,27 @@ var dataProviders = {
     // object are the same as the names of keys in aboutSupport.properties.
     let gfxInfoProps = {
       adapterDescription: null,
       adapterVendorID: null,
       adapterDeviceID: null,
       adapterSubsysID: null,
       adapterRAM: null,
       adapterDriver: "adapterDrivers",
+      adapterDriverVendor: "driverVendor",
       adapterDriverVersion: "driverVersion",
       adapterDriverDate: "driverDate",
 
       adapterDescription2: null,
       adapterVendorID2: null,
       adapterDeviceID2: null,
       adapterSubsysID2: null,
       adapterRAM2: null,
       adapterDriver2: "adapterDrivers2",
+      adapterDriverVendor2: "driverVendor2",
       adapterDriverVersion2: "driverVersion2",
       adapterDriverDate2: "driverDate2",
       isGPU2Active: null,
 
       D2DEnabled: "direct2DEnabled",
       DWriteEnabled: "directWriteEnabled",
       DWriteVersion: "directWriteVersion",
       cleartypeParameters: "clearTypeParameters",
--- a/toolkit/modules/tests/browser/browser_Troubleshoot.js
+++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js
@@ -321,16 +321,19 @@ const SNAPSHOT_SCHEMA = {
           type: "string",
         },
         adapterRAM: {
           type: "string",
         },
         adapterDrivers: {
           type: "string",
         },
+        driverVendor: {
+          type: "string",
+        },
         driverVersion: {
           type: "string",
         },
         driverDate: {
           type: "string",
         },
         adapterDescription2: {
           type: "string",
@@ -345,16 +348,19 @@ const SNAPSHOT_SCHEMA = {
           type: "string",
         },
         adapterRAM2: {
           type: "string",
         },
         adapterDrivers2: {
           type: "string",
         },
+        driverVendor2: {
+          type: "string",
+        },
         driverVersion2: {
           type: "string",
         },
         driverDate2: {
           type: "string",
         },
         isGPU2Active: {
           type: "boolean",
--- a/toolkit/mozapps/extensions/components.conf
+++ b/toolkit/mozapps/extensions/components.conf
@@ -7,17 +7,17 @@
 Classes = [
     {
         'cid': '{66354bc9-7ed1-4692-ae1d-8da97d6b205e}',
         'contract_ids': ['@mozilla.org/extensions/blocklist;1'],
         'jsm': 'resource://gre/modules/addonManager.js',
         'constructor': 'BlocklistService',
         'processes': ProcessSelector.MAIN_PROCESS_ONLY,
         'categories': ({'profile-after-change': 'nsBlocklistService'}
-                       if buildconfig.substs['MOZ_BUILD_APP'] == 'browser'
+                       if buildconfig.substs['MOZ_BUILD_APP'] != 'browser'
                        else {}),
     },
     {
         'cid': '{4399533d-08d1-458c-a87a-235f74451cfa}',
         'contract_ids': ['@mozilla.org/addons/integration;1'],
         'jsm': 'resource://gre/modules/addonManager.js',
         'constructor': 'amManager',
     },
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -2405,19 +2405,17 @@ this.XPIDatabaseReconcile = {
       // Do not allow third party installs if xpinstall is disabled by policy
       if (isDetectedInstall && Services.policies &&
           !Services.policies.isAllowed("xpinstall")) {
         throw new Error("Extension installs are disabled by enterprise policy.");
       }
 
       if (!aNewAddon) {
         // Load the manifest from the add-on.
-        let file = new nsIFile(aAddonState.path);
-        aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aLocation);
-        aNewAddon.rootURI = XPIInternal.getURIForResourceInFile(file, "").spec;
+        aNewAddon = XPIInstall.syncLoadManifest(aAddonState, aLocation);
       }
       // The add-on in the manifest should match the add-on ID.
       if (aNewAddon.id != aId) {
         throw new Error(`Invalid addon ID: expected addon ID ${aId}, found ${aNewAddon.id} in manifest`);
       }
 
       unsigned = XPIDatabase.mustSign(aNewAddon.type) && !aNewAddon.isCorrectlySigned;
       if (unsigned) {
@@ -2501,19 +2499,17 @@ this.XPIDatabaseReconcile = {
    *        changing this add-on
    */
   updateMetadata(aLocation, aOldAddon, aAddonState, aNewAddon) {
     logger.debug(`Add-on ${aOldAddon.id} modified in ${aLocation.name}`);
 
     try {
       // If there isn't an updated install manifest for this add-on then load it.
       if (!aNewAddon) {
-        let file = new nsIFile(aAddonState.path);
-        aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aLocation, aOldAddon);
-        aNewAddon.rootURI = XPIInternal.getURIForResourceInFile(file, "").spec;
+        aNewAddon = XPIInstall.syncLoadManifest(aAddonState, aLocation, aOldAddon);
       } else {
         aNewAddon.rootURI = aOldAddon.rootURI;
       }
 
       // The ID in the manifest that was loaded must match the ID of the old
       // add-on.
       if (aNewAddon.id != aOldAddon.id)
         throw new Error(`Incorrect id in install manifest for existing add-on ${aOldAddon.id}`);
@@ -2581,19 +2577,17 @@ this.XPIDatabaseReconcile = {
     logger.debug(`Updating compatibility for add-on ${aOldAddon.id} in ${aLocation.name}`);
 
     let checkSigning = (aOldAddon.signedState === undefined &&
                         SIGNED_TYPES.has(aOldAddon.type));
 
     let manifest = null;
     if (checkSigning || aReloadMetadata) {
       try {
-        let file = new nsIFile(aAddonState.path);
-        manifest = XPIInstall.syncLoadManifestFromFile(file, aLocation);
-        manifest.rootURI = aOldAddon.rootURI;
+        manifest = XPIInstall.syncLoadManifest(aAddonState, aLocation);
       } catch (err) {
         // If we can no longer read the manifest, it is no longer compatible.
         aOldAddon.brokenManifest = true;
         aOldAddon.appDisabled = true;
         return aOldAddon;
       }
     }
 
@@ -2834,16 +2828,19 @@ this.XPIDatabaseReconcile = {
     }
 
     if (promises.some(p => p)) {
       XPIInternal.awaitPromise(Promise.all(promises));
     }
 
     for (let [id, addon] of previousVisible) {
       if (addon.location) {
+        if (addon.location.name == KEY_APP_BUILTINS) {
+          continue;
+        }
         if (addonExists(addon)) {
           XPIInternal.BootstrapScope.get(addon).uninstall();
         }
         addon.location.removeAddon(id);
         addon.visible = false;
         addon.active = false;
       }
 
@@ -2904,18 +2901,19 @@ this.XPIDatabaseReconcile = {
     let isActive = !currentAddon.disabled;
     let wasActive = previousAddon ? previousAddon.active : currentAddon.active;
 
     if (previousAddon) {
       if (previousAddon !== currentAddon) {
         AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
 
         if (previousAddon.location &&
-            previousAddon._sourceBundle.exists() &&
-            !previousAddon._sourceBundle.equals(currentAddon._sourceBundle)) {
+            (!previousAddon._sourceBundle ||
+             (previousAddon._sourceBundle.exists() &&
+              !previousAddon._sourceBundle.equals(currentAddon._sourceBundle)))) {
           promise = XPIInternal.BootstrapScope.get(previousAddon).update(
             currentAddon);
         } else if (this.isSystemAddonLocation(currentAddon.location) &&
                    previousAddon.version == currentAddon.version &&
                    previousAddon.userDisabled != currentAddon.userDisabled) {
           // A system addon change, no need for install or update events.
         } else {
           let reason = XPIInstall.newVersionReason(previousAddon.version, currentAddon.version);
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -335,16 +335,46 @@ XPIPackage = class XPIPackage extends Pa
   }
 
   flushCache() {
     flushJarCache(this.file);
   }
 };
 
 /**
+ * Return an object that implements enough of the Package interface
+ * to allow loadManifest() to work for a built-in addon (ie, one loaded
+ * from a resource: url)
+ *
+ * @param {nsIURL} baseURL The URL for the root of the add-on.
+ * @returns {object}
+ */
+function builtinPackage(baseURL) {
+  return {
+    rootURI: baseURL,
+    filePath: baseURL.spec,
+    file: null,
+    verifySignedState() {
+      return {
+        signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+        cert: null,
+      };
+    },
+    async hasResource(path) {
+      try {
+        let response = await fetch(this.rootURI.resolve(path));
+        return response.ok;
+      } catch (e) {
+        return false;
+      }
+    },
+  };
+}
+
+/**
  * Determine the reason to pass to an extension's bootstrap methods when
  * switch between versions.
  *
  * @param {string} oldVersion The version of the existing extension instance.
  * @param {string} newVersion The version of the extension being installed.
  *
  * @returns {integer}
  *        BOOSTRAP_REASONS.ADDON_UPGRADE or BOOSTRAP_REASONS.ADDON_DOWNGRADE
@@ -605,18 +635,33 @@ var loadManifestFromFile = async functio
     pkg.close();
   }
 };
 
 /*
  * A synchronous method for loading an add-on's manifest. Do not use
  * this.
  */
-function syncLoadManifestFromFile(aFile, aLocation, aOldAddon) {
-  return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aLocation, aOldAddon));
+function syncLoadManifest(state, location, oldAddon) {
+  if (location.name == "app-builtin") {
+    let pkg = builtinPackage(Services.io.newURI(state.rootURI));
+    return XPIInternal.awaitPromise(loadManifest(pkg, location, oldAddon));
+  }
+
+  let file = new nsIFile(state.path);
+  let pkg = Package.get(file);
+  return XPIInternal.awaitPromise((async () => {
+    try {
+      let addon = await loadManifest(pkg, location, oldAddon);
+      addon.rootURI = getURIForResourceInFile(file, "").spec;
+      return addon;
+    } finally {
+      pkg.close();
+    }
+  })());
 }
 
 /**
  * Creates and returns a new unique temporary file. The caller should delete
  * the file when it is no longer needed.
  *
  * @returns {nsIFile}
  *       An nsIFile that points to a randomly named, initially empty file in
@@ -3221,17 +3266,17 @@ class SystemAddonInstaller extends Direc
 var XPIInstall = {
   // An array of currently active AddonInstalls
   installs: new Set(),
 
   createLocalInstall,
   flushJarCache,
   newVersionReason,
   recursiveRemove,
-  syncLoadManifestFromFile,
+  syncLoadManifest,
 
   // Keep track of in-progress operations that support cancel()
   _inProgress: [],
 
   doing(aCancellable) {
     this._inProgress.push(aCancellable);
   },
 
@@ -3690,37 +3735,17 @@ var XPIInstall = {
     // WebExtensions need to be able to iterate through the contents of
     // an extension (for localization).  It knows how to do this with
     // jar: and file: URLs, so translate the provided base URL to
     // something it can use.
     if (baseURL.scheme !== "resource") {
       throw new Error("Built-in addons must use resource: URLS");
     }
 
-    // Enough of the Package interface to allow loadManifest() to work.
-    let pkg = {
-      rootURI: baseURL,
-      filePath: baseURL.spec,
-      file: null,
-      verifySignedState() {
-        return {
-          signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
-          cert: null,
-        };
-      },
-      async hasResource(path) {
-        try {
-          let response = await fetch(this.rootURI.resolve(path));
-          return response.ok;
-        } catch (e) {
-          return false;
-        }
-      },
-    };
-
+    let pkg = builtinPackage(baseURL);
     let addon = await loadManifest(pkg, XPIInternal.BuiltInLocation);
     addon.rootURI = base;
 
     // If this is a theme, decide whether to enable it. Themes are
     // disabled by default. However:
     //
     // If a lightweight theme was selected in the last session, and this
     // theme has the same ID, then we clearly want to enable it.
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -101,17 +101,17 @@ const STARTUP_MTIME_SCOPES = [KEY_APP_GL
                               KEY_APP_SYSTEM_SHARE,
                               KEY_APP_SYSTEM_USER];
 
 const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
 const XPI_PERMISSION                  = "install";
 
 const XPI_SIGNATURE_CHECK_PERIOD      = 24 * 60 * 60;
 
-const DB_SCHEMA = 29;
+const DB_SCHEMA = 30;
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "enabledScopesPref",
                                       PREF_EM_ENABLED_SCOPES,
                                       AddonManager.SCOPE_ALL);
 
 Object.defineProperty(this, "enabledScopes", {
   get() {
     // The profile location is always enabled
@@ -1982,16 +1982,39 @@ class BootstrapScope {
       await updateCallback();
     }
 
     this.addon = newAddon;
     return this._install(reason, callUpdate, startup, extraArgs);
   }
 }
 
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1548973
+const MISSING_INTERMEDIATE_CERTIFICATE = "MIIHLTCCBRWgAwIBAgIDEAAIMA0GCSqGSIb3DQEBDAUAMH0xCzAJBgNVBAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTEfMB0GA1UEAxMWcm9vdC1jYS1wcm9kdWN0aW9uLWFtbzAeFw0xNTA0MDQwMDAwMDBaFw0yNTA0MDQwMDAwMDBaMIGnMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxJjAkBgNVBAMTHXNpZ25pbmdjYTEuYWRkb25zLm1vemlsbGEub3JnMSEwHwYJKoZIhvcNAQkBFhJmb3hzZWNAbW96aWxsYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/qluiiI+wO6qGA4vH7cHvWvXpdju9JnvbwnrbYmxhtUpfS68LbdjGGtv7RP6F1XhHT4MU3v4GuMulH0E4Wfalm8evsb3tBJRMJPICJX5UCLi6VJ6J2vipXSWBf8xbcOB+PY5Kk6L+EZiWaepiM23CdaZjNOJCAB6wFHlGe+zUk87whpLa7GrtrHjTb8u9TSS+mwjhvgfP8ILZrWhzb5H/ybgmD7jYaJGIDY/WDmq1gVe03fShxD09Ml1P7H38o5kbFLnbbqpqC6n8SfUI31MiJAXAN2e6rAOM8EmocAY0EC5KUooXKRsYvHzhwwHkwIbbe6QpTUlIqvw1MPlQPs7Zu/MBnVmyGTSqJxtYoklr0MaEXnJNY3g3FDf1R0Opp2/BEY9Vh3Fc9Pq6qWIhGoMyWdueoSYa+GURqDbsuYnk7ZkysxK+yRoFJu4x3TUBmMKM14jQKLgxvuIzWVn6qg6cw7ye/DYNufc+DSPSTSakSsWJ9IPxiAU7xJ+GCMzaZ10Y3VGOybGLuPxDlSd6KALAoMcl9ghB2mvfB0N3wv6uWnbKuxihq/qDps+FjliNvr7C66mIVH+9rkyHIy6GgIUlwr7E88Qqw+SQeNeph6NIY85PL4p0Y8KivKP4J928tpp18wLuHNbIG+YaUk5WUDZ6/2621pi19UZQ8iiHxN/XKQIDAQABo4IBiTCCAYUwDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFBY++xz/DCuT+JsV1y2jwuZ4YdztMIGoBgNVHSMEgaAwgZ2AFLO86lh0q+FueCqyq5wjHqhjLJe3oYGBpH8wfTELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rpb24tYW1vggEBMDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRkb25zLm1vemlsbGEub3JnL2NhL2NybC5wZW0wTgYDVR0eBEcwRaFDMCCCHi5jb250ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzAfgh1jb250ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzANBgkqhkiG9w0BAQwFAAOCAgEAX1PNli/zErw3tK3S9Bv803RV4tHkrMa5xztxzlWja0VAUJKEQx7f1yM8vmcQJ9g5RE8WFc43IePwzbAoum5F4BTM7tqM//+e476F1YUgB7SnkDTVpBOnV5vRLz1Si4iJ/U0HUvMUvNJEweXvKg/DNbXuCreSvTEAawmRIxqNYoaigQD8x4hCzGcVtIi5Xk2aMCJW2K/6JqkN50pnLBNkPx6FeiYMJCP8z0FIz3fv53FHgu3oeDhi2u3VdONjK3aaFWTlKNiGeDU0/lr0suWfQLsNyphTMbYKyTqQYHxXYJno9PuNi7e1903PvM47fKB5bFmSLyzB1hB1YIVLj0/YqD4nz3lADDB91gMBB7vR2h5bRjFqLOxuOutNNcNRnv7UPqtVCtLF2jVb4/AmdJU78jpfDs+BgY/t2bnGBVFBuwqS2Kult/2kth4YMrL5DrURIM8oXWVQRBKxzr843yDmHo8+2rqxLnZcmWoe8yQ41srZ4IB+V3w2TIAd4gxZAB0Xa6KfnR4D8RgE5sgmgQoK7Y/hdvd9Ahu0WEZI8Eg+mDeCeojWcyjF+dt6c2oERiTmFTIFUoojEjJwLyIqHKt+eApEYpF7imaWcumFN1jR+iUjE4ZSUoVxGtZ/Jdnkf8VVQMhiBA+i7r5PsfrHq+lqTTGOg+GzYx7OmoeJAT0zo4c=";
+
+function addMissingIntermediateCertificate() {
+  const PREF_SIGNER_HOTFIXED = "extensions.signer.hotfixed";
+  let hotfixApplied = Services.prefs.getBoolPref(PREF_SIGNER_HOTFIXED, false);
+  if (hotfixApplied) {
+    return;
+  }
+  logger.debug("hotfix for addon signing cert has not been applied; applying");
+
+  try {
+    let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+    certDB.addCertFromBase64(MISSING_INTERMEDIATE_CERTIFICATE, ",,");
+    logger.debug("new intermediate certificate added");
+  } catch (e) {
+    logger.error("failed to add new intermediate certificate:", e);
+    return;
+  }
+
+  Services.prefs.setBoolPref(PREF_SIGNER_HOTFIXED, true);
+}
+
 let resolveDBReady;
 let dbReadyPromise = new Promise(resolve => {
   resolveDBReady = resolve;
 });
 let resolveProviderReady;
 let providerReadyPromise = new Promise(resolve => {
   resolveProviderReady = resolve;
 });
@@ -2227,16 +2250,21 @@ var XPIProvider = {
    * @param {string?} [aOldAppVersion]
    *        The version of the application last run with this profile or null
    *        if it is a new profile or the version is unknown
    * @param {string?} [aOldPlatformVersion]
    *        The version of the platform last run with this profile or null
    *        if it is a new profile or the version is unknown
    */
   startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
+    // Add missing certificate (bug 1548973). Mistakenly disabled add-ons are
+    // going to be re-enabled because the schema version bump forces a new
+    // signature verification check.
+    addMissingIntermediateCertificate();
+
     try {
       AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
 
       logger.debug("startup");
 
       this.builtInAddons = {};
       try {
         let url = Services.io.newURI(BUILT_IN_ADDONS_URI);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_builtin_location.js
@@ -38,16 +38,39 @@ add_task(async function test_builtin_loc
   await wrapper.awaitStartup();
   await wrapper.awaitMessage("started");
   ok(true, "Extension in built-in location ran after restart");
 
   addon = await promiseAddonByID(id);
   notEqual(addon, null, "Addon is installed");
   ok(addon.isActive, "Addon is active");
 
+  // After a restart that causes a database rebuild, it should still work
+  await promiseRestartManager("2");
+  await wrapper.awaitStartup();
+  await wrapper.awaitMessage("started");
+  ok(true, "Extension in built-in location ran after restart");
+
+  addon = await promiseAddonByID(id);
+  notEqual(addon, null, "Addon is installed");
+  ok(addon.isActive, "Addon is active");
+
+  // After a restart that changes the schema version, it should still work
+  await promiseShutdownManager();
+  Services.prefs.setIntPref("extensions.databaseSchema", 0);
+  await promiseStartupManager();
+
+  await wrapper.awaitStartup();
+  await wrapper.awaitMessage("started");
+  ok(true, "Extension in built-in location ran after restart");
+
+  addon = await promiseAddonByID(id);
+  notEqual(addon, null, "Addon is installed");
+  ok(addon.isActive, "Addon is active");
+
   await wrapper.unload();
 
   addon = await promiseAddonByID(id);
   equal(addon, null, "Addon is gone after uninstall");
   await AddonTestUtils.promiseShutdownManager();
 });
 
 // Tests installing a hidden extension from the built-in location.
--- a/widget/GfxDriverInfo.cpp
+++ b/widget/GfxDriverInfo.cpp
@@ -11,57 +11,62 @@
 using namespace mozilla::widget;
 
 int32_t GfxDriverInfo::allFeatures = 0;
 uint64_t GfxDriverInfo::allDriverVersions = ~(uint64_t(0));
 GfxDeviceFamily* const GfxDriverInfo::allDevices = nullptr;
 
 GfxDeviceFamily* GfxDriverInfo::sDeviceFamilies[DeviceFamilyMax];
 nsAString* GfxDriverInfo::sDeviceVendors[DeviceVendorMax];
+nsAString* GfxDriverInfo::sDriverVendors[DriverVendorMax];
 
 GfxDriverInfo::GfxDriverInfo()
     : mOperatingSystem(OperatingSystem::Unknown),
       mOperatingSystemVersion(0),
       mAdapterVendor(GfxDriverInfo::GetDeviceVendor(VendorAll)),
+      mDriverVendor(GfxDriverInfo::GetDriverVendor(DriverVendorAll)),
       mDevices(allDevices),
       mDeleteDevices(false),
       mFeature(allFeatures),
       mFeatureStatus(nsIGfxInfo::FEATURE_STATUS_OK),
       mComparisonOp(DRIVER_COMPARISON_IGNORED),
       mDriverVersion(0),
       mDriverVersionMax(0),
       mSuggestedVersion(nullptr),
       mRuleId(nullptr),
       mGpu2(false) {}
 
-GfxDriverInfo::GfxDriverInfo(OperatingSystem os, nsAString& vendor,
+GfxDriverInfo::GfxDriverInfo(OperatingSystem os, const nsAString& vendor,
+                             const nsAString& driverVendor,
                              GfxDeviceFamily* devices, int32_t feature,
                              int32_t featureStatus, VersionComparisonOp op,
                              uint64_t driverVersion, const char* ruleId,
                              const char* suggestedVersion /* = nullptr */,
                              bool ownDevices /* = false */,
                              bool gpu2 /* = false */)
     : mOperatingSystem(os),
       mOperatingSystemVersion(0),
       mAdapterVendor(vendor),
+      mDriverVendor(driverVendor),
       mDevices(devices),
       mDeleteDevices(ownDevices),
       mFeature(feature),
       mFeatureStatus(featureStatus),
       mComparisonOp(op),
       mDriverVersion(driverVersion),
       mDriverVersionMax(0),
       mSuggestedVersion(suggestedVersion),
       mRuleId(ruleId),
       mGpu2(gpu2) {}
 
 GfxDriverInfo::GfxDriverInfo(const GfxDriverInfo& aOrig)
     : mOperatingSystem(aOrig.mOperatingSystem),
       mOperatingSystemVersion(aOrig.mOperatingSystemVersion),
       mAdapterVendor(aOrig.mAdapterVendor),
+      mDriverVendor(aOrig.mDriverVendor),
       mFeature(aOrig.mFeature),
       mFeatureStatus(aOrig.mFeatureStatus),
       mComparisonOp(aOrig.mComparisonOp),
       mDriverVersion(aOrig.mDriverVersion),
       mDriverVersionMax(aOrig.mDriverVersionMax),
       mSuggestedVersion(aOrig.mSuggestedVersion),
       mRuleId(aOrig.mRuleId),
       mGpu2(aOrig.mGpu2) {
@@ -376,19 +381,42 @@ const nsAString& GfxDriverInfo::GetDevic
     DECLARE_VENDOR_ID(VendorNVIDIA, "0x10de");
     DECLARE_VENDOR_ID(VendorAMD, "0x1022");
     DECLARE_VENDOR_ID(VendorATI, "0x1002");
     DECLARE_VENDOR_ID(VendorMicrosoft, "0x1414");
     DECLARE_VENDOR_ID(VendorParallels, "0x1ab8");
     // Choose an arbitrary Qualcomm PCI VENdor ID for now.
     // TODO: This should be "QCOM" when Windows device ID parsing is reworked.
     DECLARE_VENDOR_ID(VendorQualcomm, "0x5143");
-    DECLARE_VENDOR_ID(VendorMesaAll, "mesa/all");
-    DECLARE_VENDOR_ID(VendorMesaLLVMPipe, "mesa/llvmpipe");
-    DECLARE_VENDOR_ID(VendorMesaSoftPipe, "mesa/softpipe");
-    DECLARE_VENDOR_ID(VendorMesaSWRast, "mesa/swrast");
-    DECLARE_VENDOR_ID(VendorMesaUnknown, "mesa/unknown");
     // Suppress a warning.
     DECLARE_VENDOR_ID(DeviceVendorMax, "");
   }
 
   return *sDeviceVendors[id];
 }
+
+// Macro for assigning a driver vendor id to a string.
+#define DECLARE_DRIVER_VENDOR_ID(name, driverVendorId) \
+  case name:                                           \
+    sDriverVendors[id]->AssignLiteral(driverVendorId); \
+    break;
+
+const nsAString& GfxDriverInfo::GetDriverVendor(DriverVendor id) {
+  NS_ASSERTION(id >= 0 && id < DriverVendorMax,
+               "DriverVendor id is out of range");
+
+  if (sDriverVendors[id]) return *sDriverVendors[id];
+
+  sDriverVendors[id] = new nsString();
+
+  switch (id) {
+    DECLARE_DRIVER_VENDOR_ID(DriverVendorAll, "");
+    DECLARE_DRIVER_VENDOR_ID(DriverMesaAll, "mesa/all");
+    DECLARE_DRIVER_VENDOR_ID(DriverMesaLLVMPipe, "mesa/llvmpipe");
+    DECLARE_DRIVER_VENDOR_ID(DriverMesaSoftPipe, "mesa/softpipe");
+    DECLARE_DRIVER_VENDOR_ID(DriverMesaSWRast, "mesa/swrast");
+    DECLARE_DRIVER_VENDOR_ID(DriverMesaUnknown, "mesa/unknown");
+    // Suppress a warning.
+    DECLARE_DRIVER_VENDOR_ID(DriverVendorMax, "");
+  }
+
+  return *sDriverVendors[id];
+}
--- a/widget/GfxDriverInfo.h
+++ b/widget/GfxDriverInfo.h
@@ -4,55 +4,57 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __mozilla_widget_GfxDriverInfo_h__
 #define __mozilla_widget_GfxDriverInfo_h__
 
 #include "nsString.h"
 
 // Macros for adding a blocklist item to the static list.
-#define APPEND_TO_DRIVER_BLOCKLIST(os, vendor, devices, feature,            \
-                                   featureStatus, driverComparator,         \
-                                   driverVersion, ruleId, suggestedVersion) \
-  sDriverInfo->AppendElement(GfxDriverInfo(                                 \
-      os, vendor, devices, feature, featureStatus, driverComparator,        \
-      driverVersion, ruleId, suggestedVersion))
-#define APPEND_TO_DRIVER_BLOCKLIST2(os, vendor, devices, feature,           \
-                                    featureStatus, driverComparator,        \
-                                    driverVersion, ruleId)                  \
-  sDriverInfo->AppendElement(GfxDriverInfo(os, vendor, devices, feature,    \
-                                           featureStatus, driverComparator, \
-                                           driverVersion, ruleId))
+#define APPEND_TO_DRIVER_BLOCKLIST(os, vendor, driverVendor, devices, feature, \
+                                   featureStatus, driverComparator,            \
+                                   driverVersion, ruleId, suggestedVersion)    \
+  sDriverInfo->AppendElement(GfxDriverInfo(                                    \
+      os, vendor, driverVendor, devices, feature, featureStatus,               \
+      driverComparator, driverVersion, ruleId, suggestedVersion))
+#define APPEND_TO_DRIVER_BLOCKLIST2(os, vendor, driverVendor, devices,         \
+                                    feature, featureStatus, driverComparator,  \
+                                    driverVersion, ruleId)                     \
+  sDriverInfo->AppendElement(                                                  \
+      GfxDriverInfo(os, vendor, driverVendor, devices, feature, featureStatus, \
+                    driverComparator, driverVersion, ruleId))
 
-#define APPEND_TO_DRIVER_BLOCKLIST_RANGE(                           \
-    os, vendor, devices, feature, featureStatus, driverComparator,  \
-    driverVersion, driverVersionMax, ruleId, suggestedVersion)      \
-  do {                                                              \
-    MOZ_ASSERT(driverComparator == DRIVER_BETWEEN_EXCLUSIVE ||      \
-               driverComparator == DRIVER_BETWEEN_INCLUSIVE ||      \
-               driverComparator == DRIVER_BETWEEN_INCLUSIVE_START); \
-    GfxDriverInfo info(os, vendor, devices, feature, featureStatus, \
-                       driverComparator, driverVersion, ruleId,     \
-                       suggestedVersion);                           \
-    info.mDriverVersionMax = driverVersionMax;                      \
-    sDriverInfo->AppendElement(info);                               \
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE(                                      \
+    os, vendor, driverVendor, devices, feature, featureStatus,                 \
+    driverComparator, driverVersion, driverVersionMax, ruleId,                 \
+    suggestedVersion)                                                          \
+  do {                                                                         \
+    MOZ_ASSERT(driverComparator == DRIVER_BETWEEN_EXCLUSIVE ||                 \
+               driverComparator == DRIVER_BETWEEN_INCLUSIVE ||                 \
+               driverComparator == DRIVER_BETWEEN_INCLUSIVE_START);            \
+    GfxDriverInfo info(os, vendor, driverVendor, devices, feature,             \
+                       featureStatus, driverComparator, driverVersion, ruleId, \
+                       suggestedVersion);                                      \
+    info.mDriverVersionMax = driverVersionMax;                                 \
+    sDriverInfo->AppendElement(info);                                          \
   } while (false)
 
-#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(                      \
-    os, vendor, devices, feature, featureStatus, driverComparator,  \
-    driverVersion, driverVersionMax, ruleId, suggestedVersion)      \
-  do {                                                              \
-    MOZ_ASSERT(driverComparator == DRIVER_BETWEEN_EXCLUSIVE ||      \
-               driverComparator == DRIVER_BETWEEN_INCLUSIVE ||      \
-               driverComparator == DRIVER_BETWEEN_INCLUSIVE_START); \
-    GfxDriverInfo info(os, vendor, devices, feature, featureStatus, \
-                       driverComparator, driverVersion, ruleId,     \
-                       suggestedVersion, false, true);              \
-    info.mDriverVersionMax = driverVersionMax;                      \
-    sDriverInfo->AppendElement(info);                               \
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(                                 \
+    os, vendor, driverVendor, devices, feature, featureStatus,                 \
+    driverComparator, driverVersion, driverVersionMax, ruleId,                 \
+    suggestedVersion)                                                          \
+  do {                                                                         \
+    MOZ_ASSERT(driverComparator == DRIVER_BETWEEN_EXCLUSIVE ||                 \
+               driverComparator == DRIVER_BETWEEN_INCLUSIVE ||                 \
+               driverComparator == DRIVER_BETWEEN_INCLUSIVE_START);            \
+    GfxDriverInfo info(os, vendor, driverVendor, devices, feature,             \
+                       featureStatus, driverComparator, driverVersion, ruleId, \
+                       suggestedVersion, false, true);                         \
+    info.mDriverVersionMax = driverVersionMax;                                 \
+    sDriverInfo->AppendElement(info);                                          \
   } while (false)
 
 namespace mozilla {
 namespace widget {
 
 enum class OperatingSystem {
   Unknown,
   Windows,
@@ -123,50 +125,57 @@ enum DeviceVendor {
   VendorIntel,
   VendorNVIDIA,
   VendorAMD,
   VendorATI,
   VendorMicrosoft,
   VendorParallels,
   VendorQualcomm,
 
+  DeviceVendorMax
+};
+
+enum DriverVendor {
+  DriverVendorAll,  // There is an assumption that this is the first enum
   // Wildcard for all Mesa drivers.
-  VendorMesaAll,
+  DriverMesaAll,
   // Note that the following list of Mesa drivers is not comprehensive; we pull
   // the DRI driver at runtime. These drivers are provided for convenience when
   // populating the local blocklist.
-  VendorMesaLLVMPipe,
-  VendorMesaSoftPipe,
-  VendorMesaSWRast,
+  DriverMesaLLVMPipe,
+  DriverMesaSoftPipe,
+  DriverMesaSWRast,
   // A generic ID to be provided when we can't determine the DRI driver on Mesa.
-  VendorMesaUnknown,
+  DriverMesaUnknown,
 
-  DeviceVendorMax
+  DriverVendorMax
 };
 
 /* Array of devices to match, or an empty array for all devices */
 typedef nsTArray<nsString> GfxDeviceFamily;
 
 struct GfxDriverInfo {
   // If |ownDevices| is true, you are transferring ownership of the devices
   // array, and it will be deleted when this GfxDriverInfo is destroyed.
-  GfxDriverInfo(OperatingSystem os, nsAString& vendor, GfxDeviceFamily* devices,
+  GfxDriverInfo(OperatingSystem os, const nsAString& vendor,
+                const nsAString& driverVendor, GfxDeviceFamily* devices,
                 int32_t feature, int32_t featureStatus, VersionComparisonOp op,
                 uint64_t driverVersion, const char* ruleId,
                 const char* suggestedVersion = nullptr, bool ownDevices = false,
                 bool gpu2 = false);
 
   GfxDriverInfo();
   GfxDriverInfo(const GfxDriverInfo&);
   ~GfxDriverInfo();
 
   OperatingSystem mOperatingSystem;
   uint32_t mOperatingSystemVersion;
 
   nsString mAdapterVendor;
+  nsString mDriverVendor;
 
   static GfxDeviceFamily* const allDevices;
   GfxDeviceFamily* mDevices;
 
   // Whether the mDevices array should be deleted when this structure is
   // deallocated. False by default.
   bool mDeleteDevices;
 
@@ -188,16 +197,19 @@ struct GfxDriverInfo {
   nsCString mRuleId;
 
   static const GfxDeviceFamily* GetDeviceFamily(DeviceFamily id);
   static GfxDeviceFamily* sDeviceFamilies[DeviceFamilyMax];
 
   static const nsAString& GetDeviceVendor(DeviceVendor id);
   static nsAString* sDeviceVendors[DeviceVendorMax];
 
+  static const nsAString& GetDriverVendor(DriverVendor id);
+  static nsAString* sDriverVendors[DriverVendorMax];
+
   nsString mModel, mHardware, mProduct, mManufacturer;
 
   bool mGpu2;
 };
 
 #define GFX_DRIVER_VERSION(a, b, c, d)                               \
   ((uint64_t(a) << 48) | (uint64_t(b) << 32) | (uint64_t(c) << 16) | \
    uint64_t(d))
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -66,16 +66,21 @@ class ShutdownObserver : public nsIObser
       GfxDriverInfo::sDeviceFamilies[i] = nullptr;
     }
 
     for (uint32_t i = 0; i < DeviceVendorMax; i++) {
       delete GfxDriverInfo::sDeviceVendors[i];
       GfxDriverInfo::sDeviceVendors[i] = nullptr;
     }
 
+    for (uint32_t i = 0; i < DriverVendorMax; i++) {
+      delete GfxDriverInfo::sDriverVendors[i];
+      GfxDriverInfo::sDriverVendors[i] = nullptr;
+    }
+
     GfxInfoBase::sShutdownOccurred = true;
 
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
 
@@ -466,16 +471,18 @@ static bool BlacklistEntryToDriverInfo(n
           NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_") + value;
       aDriverInfo.mRuleId = blockIdStr.get();
     } else if (key.EqualsLiteral("os")) {
       aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
     } else if (key.EqualsLiteral("osversion")) {
       aDriverInfo.mOperatingSystemVersion = strtoul(value.get(), nullptr, 10);
     } else if (key.EqualsLiteral("vendor")) {
       aDriverInfo.mAdapterVendor = dataValue;
+    } else if (key.EqualsLiteral("driverVendor")) {
+      aDriverInfo.mDriverVendor = dataValue;
     } else if (key.EqualsLiteral("feature")) {
       aDriverInfo.mFeature = BlacklistFeatureToGfxFeature(dataValue);
       if (aDriverInfo.mFeature < 0) {
         // If we don't recognize the feature, we do not want to proceed.
         gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false))
             << "Unrecognized feature " << value.get();
         return false;
       }
@@ -674,26 +681,29 @@ inline bool MatchingOperatingSystems(Ope
 int32_t GfxInfoBase::FindBlocklistedDeviceInList(
     const nsTArray<GfxDriverInfo>& info, nsAString& aSuggestedVersion,
     int32_t aFeature, nsACString& aFailureId, OperatingSystem os) {
   int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
 
   // Get the adapters once then reuse below
   nsAutoString adapterVendorID[2];
   nsAutoString adapterDeviceID[2];
+  nsAutoString adapterDriverVendor[2];
   nsAutoString adapterDriverVersionString[2];
   bool adapterInfoFailed[2];
 
   adapterInfoFailed[0] =
       (NS_FAILED(GetAdapterVendorID(adapterVendorID[0])) ||
        NS_FAILED(GetAdapterDeviceID(adapterDeviceID[0])) ||
+       NS_FAILED(GetAdapterDriverVendor(adapterDriverVendor[0])) ||
        NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString[0])));
   adapterInfoFailed[1] =
       (NS_FAILED(GetAdapterVendorID2(adapterVendorID[1])) ||
        NS_FAILED(GetAdapterDeviceID2(adapterDeviceID[1])) ||
+       NS_FAILED(GetAdapterDriverVendor2(adapterDriverVendor[1])) ||
        NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString[1])));
   // No point in going on if we don't have adapter info
   if (adapterInfoFailed[0] && adapterInfoFailed[1]) {
     return 0;
   }
 
 #if defined(XP_WIN) || defined(ANDROID) || defined(MOZ_X11)
   uint64_t driverVersion[2] = {0, 0};
@@ -727,16 +737,21 @@ int32_t GfxInfoBase::FindBlocklistedDevi
         info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
       continue;
     }
 
     if (!DoesVendorMatch(info[i].mAdapterVendor, adapterVendorID[infoIndex])) {
       continue;
     }
 
+    if (!DoesDriverVendorMatch(info[i].mDriverVendor,
+                               adapterDriverVendor[infoIndex])) {
+      continue;
+    }
+
     if (info[i].mDevices != GfxDriverInfo::allDevices &&
         info[i].mDevices->Length()) {
       bool deviceMatches = false;
       for (uint32_t j = 0; j < info[i].mDevices->Length(); j++) {
         if ((*info[i].mDevices)[j].Equals(
                 adapterDeviceID[infoIndex],
                 nsCaseInsensitiveStringComparator())) {
           deviceMatches = true;
@@ -882,16 +897,25 @@ void GfxInfoBase::SetFeatureStatus(
 bool GfxInfoBase::DoesVendorMatch(const nsAString& aBlocklistVendor,
                                   const nsAString& aAdapterVendor) {
   return aBlocklistVendor.Equals(aAdapterVendor,
                                  nsCaseInsensitiveStringComparator()) ||
          aBlocklistVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll),
                                  nsCaseInsensitiveStringComparator());
 }
 
+bool GfxInfoBase::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+                                        const nsAString& aDriverVendor) {
+  return aBlocklistVendor.Equals(aDriverVendor,
+                                 nsCaseInsensitiveStringComparator()) ||
+         aBlocklistVendor.Equals(
+             GfxDriverInfo::GetDriverVendor(DriverVendorAll),
+             nsCaseInsensitiveStringComparator());
+}
+
 nsresult GfxInfoBase::GetFeatureStatusImpl(
     int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedVersion,
     const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
     OperatingSystem* aOS /* = nullptr */) {
   if (aFeature <= 0) {
     gfxWarning() << "Invalid feature <= 0";
     return NS_OK;
   }
@@ -1156,16 +1180,40 @@ void GfxInfoBase::RemoveCollector(GfxInf
     }
   }
   if (sCollectors->IsEmpty()) {
     delete sCollectors;
     sCollectors = nullptr;
   }
 }
 
+nsresult GfxInfoBase::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) {
+  // If we have no platform specific implementation for detecting monitors, we
+  // can just get the screen size from gfxPlatform as the best guess.
+  if (!gfxPlatform::Initialized()) {
+    return NS_OK;
+  }
+
+  // If the screen size is empty, we are probably in xpcshell.
+  gfx::IntSize screenSize = gfxPlatform::GetPlatform()->GetScreenSize();
+
+  JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+  JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(screenSize.width));
+  JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+  JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(screenSize.height));
+  JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+  JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+  JS_SetElement(aCx, aOutArray, 0, element);
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 GfxInfoBase::GetMonitors(JSContext* aCx, JS::MutableHandleValue aResult) {
   JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
 
   nsresult rv = FindMonitors(aCx, array);
   if (NS_FAILED(rv)) {
     return rv;
   }
--- a/widget/GfxInfoBase.h
+++ b/widget/GfxInfoBase.h
@@ -99,19 +99,17 @@ class GfxInfoBase : public nsIGfxInfo,
   virtual nsString Hardware() { return EmptyString(); }
   virtual nsString Product() { return EmptyString(); }
   virtual nsString Manufacturer() { return EmptyString(); }
   virtual uint32_t OperatingSystemVersion() { return 0; }
 
   // Convenience to get the application version
   static const nsCString& GetApplicationVersion();
 
-  virtual nsresult FindMonitors(JSContext* cx, JS::HandleObject array) {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
+  virtual nsresult FindMonitors(JSContext* cx, JS::HandleObject array);
 
   static void SetFeatureStatus(
       const nsTArray<mozilla::dom::GfxInfoFeatureStatus>& aFS);
 
  protected:
   virtual ~GfxInfoBase();
 
   virtual nsresult GetFeatureStatusImpl(
@@ -120,18 +118,21 @@ class GfxInfoBase : public nsIGfxInfo,
       OperatingSystem* aOS = nullptr);
 
   // Gets the driver info table. Used by GfxInfoBase to check for general cases
   // (while subclasses check for more specific ones).
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() = 0;
 
   virtual void DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> obj);
 
-  virtual bool DoesVendorMatch(const nsAString& aBlocklistVendor,
-                               const nsAString& aAdapterVendor);
+  bool DoesVendorMatch(const nsAString& aBlocklistVendor,
+                       const nsAString& aAdapterVendor);
+
+  virtual bool DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+                                     const nsAString& aDriverVendor);
 
   bool InitFeatureObject(JSContext* aCx, JS::Handle<JSObject*> aContainer,
                          const char* aName,
                          mozilla::gfx::FeatureStatus& aKnownStatus,
                          JS::MutableHandle<JSObject*> aOutObj);
 
   NS_IMETHOD ControlGPUProcessForXPCShell(bool aEnable, bool* _retval) override;
 
--- a/widget/GfxInfoX11.cpp
+++ b/widget/GfxInfoX11.cpp
@@ -44,16 +44,18 @@ nsresult GfxInfo::Init() {
 }
 
 void GfxInfo::AddCrashReportAnnotations() {
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
                                      mVendorId);
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
                                      mDeviceId);
   CrashReporter::AnnotateCrashReport(
+      CrashReporter::Annotation::AdapterDriverVendor, mDriverVendor);
+  CrashReporter::AnnotateCrashReport(
       CrashReporter::Annotation::AdapterDriverVersion, mDriverVersion);
 }
 
 void GfxInfo::GetData() {
   // to understand this function, see bug 639842. We retrieve the OpenGL driver
   // information in a separate process to protect against bad drivers.
 
   // if glxtest_pipe == -1, that means that we already read the information
@@ -216,45 +218,52 @@ void GfxInfo::GetData() {
     NS_WARNING("Failed to parse GL version!");
     return;
   }
 
   // Mesa always exposes itself in the GL_VERSION string, but not always the
   // GL_VENDOR string.
   mIsMesa = glVersion.Find("Mesa") != -1;
 
-  // We need to use custom vendor IDs for mesa so we can treat them
+  // We need to use custom driver vendor IDs for mesa so we can treat them
   // differently than the proprietary drivers.
   if (mIsMesa) {
     mIsAccelerated = !mesaAccelerated.Equals("FALSE");
     // Process software rasterizers before the DRI driver string; we may be
     // forcing software rasterization on a DRI-accelerated X server by using
     // LIBGL_ALWAYS_SOFTWARE or a similar restriction.
     if (strcasestr(glRenderer.get(), "llvmpipe")) {
-      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaLLVMPipe),
-                      mVendorId);
+      CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverMesaLLVMPipe),
+                      mDriverVendor);
       mIsAccelerated = false;
     } else if (strcasestr(glRenderer.get(), "softpipe")) {
-      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaSoftPipe),
-                      mVendorId);
+      CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverMesaSoftPipe),
+                      mDriverVendor);
       mIsAccelerated = false;
     } else if (strcasestr(glRenderer.get(), "software rasterizer") ||
                !mIsAccelerated) {
       // Fallback to reporting swrast if GLX_MESA_query_renderer tells us
       // we're using an unaccelerated context.
-      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaSWRast),
-                      mVendorId);
+      CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverMesaSWRast),
+                      mDriverVendor);
       mIsAccelerated = false;
     } else if (!driDriver.IsEmpty()) {
-      mVendorId = nsPrintfCString("mesa/%s", driDriver.get());
+      mDriverVendor = nsPrintfCString("mesa/%s", driDriver.get());
     } else {
       // Some other mesa configuration where we couldn't get enough info.
       NS_WARNING("Failed to detect Mesa driver being used!");
-      CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorMesaUnknown),
-                      mVendorId);
+      CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverMesaUnknown),
+                      mDriverVendor);
+    }
+
+    if (!mesaVendor.IsEmpty()) {
+      mVendorId = mesaVendor;
+    } else {
+      NS_WARNING(
+          "Failed to get Mesa vendor ID! GLX_MESA_query_renderer unsupported?");
     }
 
     if (!mesaDevice.IsEmpty()) {
       mDeviceId = mesaDevice;
     } else {
       NS_WARNING(
           "Failed to get Mesa device ID! GLX_MESA_query_renderer unsupported?");
     }
@@ -282,76 +291,81 @@ void GfxInfo::GetData() {
 }
 
 const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
   if (!sDriverInfo->Length()) {
     // Mesa 10.0 provides the GLX_MESA_query_renderer extension, which allows us
     // to query device IDs backing a GL context for blacklisting.
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
-        (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorMesaAll),
+        (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAll),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverMesaAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(10, 0, 0, 0), "FEATURE_FAILURE_OLD_MESA", "Mesa 10.0");
 
     // NVIDIA baseline (ported from old blocklist)
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(257, 21, 0, 0), "FEATURE_FAILURE_OLD_NVIDIA", "NVIDIA 257.21");
 
     // fglrx baseline (chosen arbitrarily as 2013-07-22 release).
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(13, 15, 100, 1), "FEATURE_FAILURE_OLD_FGLRX", "fglrx 13.15.100.1");
 
     ////////////////////////////////////
     // FEATURE_WEBRENDER
 
-    // Mesa baseline (chosen arbitrarily as that which ships with
-    // Ubuntu 18.04 LTS).
+    // Intel Mesa baseline, chosen arbitrarily.
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
-        (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorMesaAll),
+        (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
-        V(18, 2, 8, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.2.8.0");
+        V(18, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.0.0.0");
 
     // Disable on all NVIDIA devices for now.
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
         V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_NO_LINUX_NVIDIA", "");
 
     // Disable on all ATI devices for now.
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
         V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_NO_LINUX_ATI", "");
   }
   return *sDriverInfo;
 }
 
-bool GfxInfo::DoesVendorMatch(const nsAString& aBlocklistVendor,
-                              const nsAString& aAdapterVendor) {
+bool GfxInfo::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+                                    const nsAString& aDriverVendor) {
   if (mIsMesa &&
-      aBlocklistVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorMesaAll),
+      aBlocklistVendor.Equals(GfxDriverInfo::GetDriverVendor(DriverMesaAll),
                               nsCaseInsensitiveStringComparator())) {
     return true;
   }
-  return GfxInfoBase::DoesVendorMatch(aBlocklistVendor, aAdapterVendor);
+  return GfxInfoBase::DoesDriverVendorMatch(aBlocklistVendor, aDriverVendor);
 }
 
 nsresult GfxInfo::GetFeatureStatusImpl(
     int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
     const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
     OperatingSystem* aOS /* = nullptr */)
 
 {
@@ -441,16 +455,28 @@ GfxInfo::GetAdapterDriver(nsAString& aAd
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+  GetData();
+  CopyASCIItoUTF16(mDriverVendor, aAdapterDriverVendor);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
   GetData();
   CopyASCIItoUTF16(mDriverVersion, aAdapterDriverVersion);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
@@ -508,19 +534,17 @@ GfxInfo::GetIsGPU2Active(bool* aIsGPU2Ac
 #ifdef DEBUG
 
 // Implement nsIGfxInfoDebug
 // We don't support spoofing anything on Linux
 
 NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
   GetData();
   CopyUTF16toUTF8(aVendorID, mVendorId);
-  mIsAccelerated = !(mVendorId.EqualsLiteral("mesa/llvmpipe") ||
-                     mVendorId.EqualsLiteral("mesa/softpipe") ||
-                     mVendorId.EqualsLiteral("mesa/swrast"));
+  mIsAccelerated = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
   GetData();
   CopyUTF16toUTF8(aDeviceID, mDeviceId);
   return NS_OK;
 }
--- a/widget/GfxInfoX11.h
+++ b/widget/GfxInfoX11.h
@@ -23,24 +23,26 @@ class GfxInfo final : public GfxInfoBase
   NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
   NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
   NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM2(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion2(
       nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
 
   virtual nsresult Init() override;
@@ -56,22 +58,23 @@ class GfxInfo final : public GfxInfoBase
   ~GfxInfo() {}
 
   virtual nsresult GetFeatureStatusImpl(
       int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
       const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
       OperatingSystem* aOS = nullptr) override;
   virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
 
-  virtual bool DoesVendorMatch(const nsAString& aBlocklistVendor,
-                               const nsAString& aAdapterVendor) override;
+  virtual bool DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+                                     const nsAString& aDriverVendor) override;
 
  private:
   nsCString mVendorId;
   nsCString mDeviceId;
+  nsCString mDriverVendor;
   nsCString mDriverVersion;
   nsCString mAdapterDescription;
   nsCString mAdapterRAM;
   nsCString mOS;
   nsCString mOSRelease;
   bool mHasTextureFromPixmap;
   unsigned int mGLMajorVersion, mGLMinorVersion;
   bool mIsMesa;
--- a/widget/android/GfxInfo.cpp
+++ b/widget/android/GfxInfo.cpp
@@ -237,16 +237,29 @@ GfxInfo::GetAdapterDriver(nsAString& aAd
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
   EnsureInitialized();
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+  EnsureInitialized();
+  aAdapterDriverVendor.Truncate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+  EnsureInitialized();
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
   EnsureInitialized();
   aAdapterDriverVersion = NS_ConvertASCIItoUTF16(mGLStrings->Version());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
@@ -320,16 +333,17 @@ void GfxInfo::AddCrashReportAnnotations(
       CrashReporter::Annotation::AdapterDriverVersion, mGLStrings->Version());
 }
 
 const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
   if (sDriverInfo->IsEmpty()) {
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Android,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAll),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_OPENGL_LAYERS,
         nsIGfxInfo::FEATURE_STATUS_OK, DRIVER_COMPARISON_IGNORED,
         GfxDriverInfo::allDriverVersions, "FEATURE_OK_FORCE_OPENGL");
   }
 
   return *sDriverInfo;
 }
 
--- a/widget/android/GfxInfo.h
+++ b/widget/android/GfxInfo.h
@@ -32,24 +32,26 @@ class GfxInfo : public GfxInfoBase {
   NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
   NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
   NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM2(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion2(
       nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
 
   void EnsureInitialized();
--- a/widget/cocoa/GfxInfo.h
+++ b/widget/cocoa/GfxInfo.h
@@ -25,24 +25,26 @@ class GfxInfo : public GfxInfoBase {
   NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
   NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
   NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM2(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion2(
       nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
 
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
 
--- a/widget/cocoa/GfxInfo.mm
+++ b/widget/cocoa/GfxInfo.mm
@@ -146,16 +146,27 @@ GfxInfo::GetAdapterDriver(nsAString& aAd
   aAdapterDriver.AssignLiteral("");
   return NS_OK;
 }
 
 /* readonly attribute DOMString adapterDriver2; */
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) { return NS_ERROR_FAILURE; }
 
+/* readonly attribute DOMString adapterDriverVendor; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+  aAdapterDriverVendor.AssignLiteral("");
+  return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVendor2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) { return NS_ERROR_FAILURE; }
+
 /* readonly attribute DOMString adapterDriverVersion; */
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
   aAdapterDriverVersion.AssignLiteral("");
   return NS_OK;
 }
 
 /* readonly attribute DOMString adapterDriverVersion2; */
@@ -220,33 +231,36 @@ void GfxInfo::AddCrashReportAnnotations(
 
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID, narrowVendorID);
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID, narrowDeviceID);
   CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDriverVersion,
                                      narrowDriverVersion);
 }
 
 // We don't support checking driver versions on Mac.
-#define IMPLEMENT_MAC_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, ruleId)          \
-  APPEND_TO_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, DRIVER_COMPARISON_IGNORED, \
+#define IMPLEMENT_MAC_DRIVER_BLOCKLIST(os, vendor, driverVendor, device, features, blockOn, ruleId)          \
+  APPEND_TO_DRIVER_BLOCKLIST(os, vendor, driverVendor, device, features, blockOn, DRIVER_COMPARISON_IGNORED, \
                              V(0, 0, 0, 0), ruleId, "")
 
 const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
   if (!sDriverInfo->Length()) {
     IMPLEMENT_MAC_DRIVER_BLOCKLIST(
         OperatingSystem::OSX, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBGL_MSAA,
         nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION, "FEATURE_FAILURE_MAC_ATI_NO_MSAA");
     IMPLEMENT_MAC_DRIVER_BLOCKLIST(
         OperatingSystem::OSX, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(RadeonX1000),
         nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         "FEATURE_FAILURE_MAC_RADEONX1000_NO_TEXTURE2D");
     IMPLEMENT_MAC_DRIVER_BLOCKLIST(
         OperatingSystem::OSX, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Geforce7300GT),
         nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         "FEATURE_FAILURE_MAC_7300_NO_WEBGL");
   }
   return *sDriverInfo;
 }
 
 nsresult GfxInfo::GetFeatureStatusImpl(int32_t aFeature, int32_t* aStatus,
--- a/widget/nsIGfxInfo.idl
+++ b/widget/nsIGfxInfo.idl
@@ -53,16 +53,19 @@ interface nsIGfxInfo : nsISupports
   readonly attribute AString adapterSubsysID2;
 
   /**
    * The amount of RAM in MB in the display adapter.
    */
   readonly attribute AString adapterRAM;
   readonly attribute AString adapterRAM2;
 
+  readonly attribute AString adapterDriverVendor;
+  readonly attribute AString adapterDriverVendor2;
+
   readonly attribute AString adapterDriverVersion;
   readonly attribute AString adapterDriverVersion2;
 
   readonly attribute AString adapterDriverDate;
   readonly attribute AString adapterDriverDate2;
 
   readonly attribute boolean isGPU2Active;
 
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -781,28 +781,40 @@ GfxInfo::GetAdapterDriver2(nsAString& aA
                                    L"InstalledDisplayDrivers", aAdapterDriver,
                                    REG_MULTI_SZ))) {
     aAdapterDriver = L"Unknown";
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+  aAdapterDriverVendor.Truncate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
   aAdapterDriverVersion = mDriverVersion[mActiveGPUIndex];
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
   aAdapterDriverDate = mDriverDate[mActiveGPUIndex];
   return NS_OK;
 }
 
 NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+  aAdapterDriverVendor.Truncate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
   aAdapterDriverVersion = mDriverVersion[1 - mActiveGPUIndex];
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
   aAdapterDriverDate = mDriverDate[1 - mActiveGPUIndex];
@@ -1022,149 +1034,165 @@ const nsTArray<GfxDriverInfo>& GfxInfo::
      * October 2009+ drivers across all these minor versions.
      *
      * 187.45 (late October 2009) and earlier contain a bug which can cause us
      * to crash on shutdown.
      */
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
         V(8, 15, 11, 8745), "FEATURE_FAILURE_NV_W7_15",
         "nVidia driver > 187.45");
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(8, 16, 10, 0000), V(8, 16, 11, 8745),
         "FEATURE_FAILURE_NV_W7_16", "nVidia driver > 187.45");
     // Telemetry doesn't show any driver in this range so it might not even be
     // required.
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(8, 17, 10, 0000), V(8, 17, 11, 8745),
         "FEATURE_FAILURE_NV_W7_17", "nVidia driver > 187.45");
 
     /*
      * AMD/ATI entries. 8.56.1.15 is the driver that shipped with Windows 7 RTM
      */
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(8, 56, 1, 15), "FEATURE_FAILURE_AMD1", "8.56.1.15");
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(8, 56, 1, 15), "FEATURE_FAILURE_AMD2", "8.56.1.15");
 
     // Bug 1099252
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
         V(8, 832, 0, 0), "FEATURE_FAILURE_BUG_1099252");
 
     // Bug 1118695
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
         V(8, 783, 2, 2000), "FEATURE_FAILURE_BUG_1118695");
 
     // Bug 1198815
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(15, 200, 0, 0), V(15, 200, 1062, 1004), "FEATURE_FAILURE_BUG_1198815",
         "15.200.0.0-15.200.1062.1004");
 
     // Bug 1267970
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(15, 200, 0, 0), V(15, 301, 2301, 1002), "FEATURE_FAILURE_BUG_1267970",
         "15.200.0.0-15.301.2301.1002");
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(16, 100, 0, 0), V(16, 300, 2311, 0), "FEATURE_FAILURE_BUG_1267970",
         "16.100.0.0-16.300.2311.0");
 
     /*
      * Bug 783517 - crashes in AMD driver on Windows 8
      */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows8,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(8, 982, 0, 0), V(8, 983, 0, 0),
         "FEATURE_FAILURE_BUG_783517_AMD", "!= 8.982.*.*");
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows8,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(8, 982, 0, 0), V(8, 983, 0, 0),
         "FEATURE_FAILURE_BUG_783517_ATI", "!= 8.982.*.*");
 
     /* OpenGL on any ATI/AMD hardware is discouraged
      * See:
      *  bug 619773 - WebGL: Crash with blue screen : "NMI: Parity Check / Memory
      * Parity Error" bugs 584403, 584404, 620924 - crashes in atioglxx
      *  + many complaints about incorrect rendering
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_OPENGL_LAYERS,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_ATI_DIS");
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_OPENGL_LAYERS,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_AMD_DIS");
 
 /*
  * Intel entries
  */
 
 /* The driver versions used here come from bug 594877. They might not
  * be particularly relevant anymore.
  */
 #define IMPLEMENT_INTEL_DRIVER_BLOCKLIST(winVer, devFamily, driverVer, ruleId) \
   APPEND_TO_DRIVER_BLOCKLIST2(                                                 \
       winVer, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),         \
+      (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),             \
       (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devFamily),             \
       GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,  \
       DRIVER_LESS_THAN, driverVer, ruleId)
 
 #define IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(winVer, devFamily, driverVer,   \
                                              ruleId)                         \
   APPEND_TO_DRIVER_BLOCKLIST2(                                               \
       winVer, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),       \
+      (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),           \
       (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(devFamily),           \
       nsIGfxInfo::FEATURE_DIRECT2D,                                          \
       nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BUILD_ID_LESS_THAN, \
       driverVer, ruleId)
 
     IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA500,
                                          2026, "FEATURE_FAILURE_594877_7");
     IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA900,
@@ -1185,16 +1213,17 @@ const nsTArray<GfxDriverInfo>& GfxInfo::
     /* Disable Direct2D on Intel GMAX4500 devices because of rendering
      * corruption discovered in bug 1180379. These seems to affect even the most
      * recent drivers. We're black listing all of the devices to be safe even
      * though we've only confirmed the issue on the G45
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
         nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_FAILURE_1180379");
 
     IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA500,
                                      V(5, 0, 0, 2026),
                                      "FEATURE_FAILURE_INTEL_16");
@@ -1216,436 +1245,481 @@ const nsTArray<GfxDriverInfo>& GfxInfo::
     IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
         OperatingSystem::Windows7, IntelHDGraphicsToSandyBridge,
         V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_22");
 
     // Bug 1074378
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_EQUAL, V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_1",
         "8.15.10.2342");
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToSandyBridge),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_EQUAL, V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_2",
         "8.15.10.2342");
 
     /* OpenGL on any Intel hardware is discouraged */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_OPENGL_LAYERS,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_OGL_DIS");
 
     /**
      * Disable acceleration on Intel HD 3000 for graphics drivers
      * <= 8.15.10.2321. See bug 1018278 and bug 1060736.
      */
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelHD3000),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2321, "FEATURE_FAILURE_BUG_1018278",
         "X.X.X.2342");
 
     /**
      * Disable D2D on Win7 on Intel Haswell for graphics drivers build id <=
      * 4578. See bug 1432610
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToHaswell),
         nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4578,
         "FEATURE_FAILURE_BUG_1432610");
 
     /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302
      * See bug 806786
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics),
         nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
         V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_806786");
 
     /* Disable D2D on Win8 on Intel HD Graphics on driver <= 8.15.10.2302
      * See bug 804144 and 863683
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows8,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics),
         nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
         V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_804144");
 
     /* Disable D2D on Win7 on Intel HD Graphics on driver == 8.15.10.2418
      * See bug 1433790
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToSandyBridge),
         nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
         V(8, 15, 10, 2418), "FEATURE_FAILURE_BUG_1433790");
 
     /* Disable D3D11 layers on Intel G41 express graphics and Intel GM965, Intel
      * X3100, for causing device resets. See bug 1116812.
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1116812),
         nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1116812");
 
     /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared
      * handle for textures. See bug 1207665. Additionally block D2D so we don't
      * accidentally use WARP.
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1207665),
         nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_1");
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1207665),
         nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_FAILURE_BUG_1207665_2");
 
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorQualcomm),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_QUALCOMM");
 
     // Bug 1548410. Disable hardware accelerated video decoding on
     // Qualcomm drivers used on Windows on ARM64 which are known to
     // cause BSOD's and output suprious green frames while decoding video.
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorQualcomm),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
         V(23, 18, 9310, 0), "FEATURE_FAILURE_BUG_1548410");
 
     /* Disable D2D on AMD Catalyst 14.4 until 14.6
      * See bug 984488
      */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(14, 1, 0, 0), V(14, 2, 0, 0),
         "FEATURE_FAILURE_BUG_984488_1", "ATI Catalyst 14.6+");
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DIRECT2D,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE_START, V(14, 1, 0, 0), V(14, 2, 0, 0),
         "FEATURE_FAILURE_BUG_984488_2", "ATI Catalyst 14.6+");
 
     /* Disable D3D9 layers on NVIDIA 6100/6150/6200 series due to glitches
      * whilst scrolling. See bugs: 612007, 644787 & 645872.
      */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(NvidiaBlockD3D9Layers),
         nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_612007");
 
     /* Microsoft RemoteFX; blocked less than 6.2.0.0 */
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorMicrosoft),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(6, 2, 0, 0), "< 6.2.0.0", "FEATURE_FAILURE_REMOTE_FX");
 
     /* Bug 1008759: Optimus (NVidia) crash.  Disable D2D on NV 310M. */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia310M),
         nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_FAILURE_BUG_1008759");
 
     /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
         V(15, 200, 1006, 0), "FEATURE_FAILURE_BUG_1139503");
 
     /* Bug 1213107: D3D9 crashes with ATI cards on Windows 7. */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_1",
         "Radeon driver > 8.862.6.5000");
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBGL_ANGLE,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_2",
         "Radeon driver > 8.862.6.5000");
 
     /* This may not be needed at all */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1155608),
         nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         V(8, 15, 10, 2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING");
 
     /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2849,
         "FEATURE_FAILURE_BUG_1203199_1", "Intel driver > X.X.X.2849");
 
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS),
         nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
         V(9, 18, 13, 4052), "FEATURE_FAILURE_BUG_1203199_2");
 
     /* Bug 1137716: XXX this should really check for the matching Intel piece as
      * well. Unfortunately, we don't have the infrastructure to do that */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1137716),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BETWEEN_INCLUSIVE, V(8, 17, 12, 5730), V(8, 17, 12, 6901),
         "FEATURE_FAILURE_BUG_1137716", "Nvidia driver > 8.17.12.6901");
 
     /* Bug 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed
      * by an ANGLE update. */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
         nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1153381");
 
     /* Bug 1336710: Crash in rx::Blit9::initialize. */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::WindowsXP,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
         nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_FAILURE_BUG_1336710");
 
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::WindowsXP,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToSandyBridge),
         nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_FAILURE_BUG_1336710");
 
     /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX3000),
         nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749,
         "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS");
 
 #if defined(_M_X64)
     if (DetectBrokenAVX()) {
       APPEND_TO_DRIVER_BLOCKLIST2(
           OperatingSystem::Windows7,
           (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+          (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
           GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
           nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
           GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1403353");
     }
 #endif
 
     ////////////////////////////////////
     // WebGL
 
     // Older than 5-15-2016
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBGL_OPENGL,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN,
         V(16, 200, 1010, 1002), "WEBGL_NATIVE_GL_OLD_AMD");
 
     // Older than 11-18-2015
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBGL_OPENGL,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_BUILD_ID_LESS_THAN, 4331,
         "WEBGL_NATIVE_GL_OLD_INTEL");
 
     // Older than 2-23-2016
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBGL_OPENGL,
         nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN, V(10, 18, 13, 6200),
         "WEBGL_NATIVE_GL_OLD_NVIDIA");
 
     ////////////////////////////////////
     // FEATURE_DX_INTEROP2
 
     // All AMD.
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAMD),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DX_INTEROP2,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH");
 
     ////////////////////////////////////
     // FEATURE_D3D11_KEYED_MUTEX
 
     // bug 1359416
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToSandyBridge),
         nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1359416");
 
     // bug 1419264
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_ADVANCED_LAYERS,
         nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
         V(23, 21, 13, 8569), V(23, 21, 13, 9135), "FEATURE_FAILURE_BUG_1419264",
         "Windows 10");
 
     // Bug 1447141, for causing device creation crashes.
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1447141),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_EQUAL, V(15, 201, 2201, 0), "FEATURE_FAILURE_BUG_1447141_1");
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1447141),
         GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_EQUAL, V(15, 201, 1701, 0), "FEATURE_FAILURE_BUG_1447141_1");
 
     // bug 1457758
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, GfxDriverInfo::allFeatures,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_EQUAL, V(24, 21, 13, 9731),
         "FEATURE_FAILURE_BUG_1457758");
 
     ////////////////////////////////////
     // FEATURE_DX_NV12
 
     // Bug 1437334
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(
             IntelHDGraphicsToSandyBridge),
         nsIGfxInfo::FEATURE_DX_NV12, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
         DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4459,
         "FEATURE_BLOCKED_DRIVER_VERSION");
 
     ////////////////////////////////////
     // FEATURE_DX_P010
 
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_DX_P010,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions, "FEATURE_UNQUALIFIED_P010_NVIDIA");
 
     ////////////////////////////////////
     // FEATURE_WEBRENDER
 
     // We are blocking most hardware explicitly in gfxPlatform.cpp where we
     // check for the WEBRENDER_QUALIFIED feature. However we also want to block
     // some specific Nvidia cards for being too low-powered, so we do that here.
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows10,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(NvidiaBlockWebRender),
         nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
         DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
         "FEATURE_UNQUALIFIED_WEBRENDER_NVIDIA_BLOCKED");
 
     // Block all windows versions other than windows 10
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows7,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAll),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions,
         "FEATURE_UNQUALIFIED_WEBRENDER_WINDOWS_7");
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows8,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAll),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions,
         "FEATURE_UNQUALIFIED_WEBRENDER_WINDOWS_8");
     APPEND_TO_DRIVER_BLOCKLIST2(
         OperatingSystem::Windows8_1,
         (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorAll),
+        (nsAString&)GfxDriverInfo::GetDriverVendor(DriverVendorAll),
         GfxDriverInfo::allDevices, nsIGfxInfo::FEATURE_WEBRENDER,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
         GfxDriverInfo::allDriverVersions,
         "FEATURE_UNQUALIFIED_WEBRENDER_WINDOWS_8_1");
   }
   return *sDriverInfo;
 }
 
--- a/widget/windows/GfxInfo.h
+++ b/widget/windows/GfxInfo.h
@@ -26,24 +26,26 @@ class GfxInfo : public GfxInfoBase {
   NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
   NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
   NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
   NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
   NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
   NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
   NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
   NS_IMETHOD GetAdapterRAM2(nsAString& aAdapterRAM) override;
+  NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
   NS_IMETHOD GetAdapterDriverVersion2(
       nsAString& aAdapterDriverVersion) override;
   NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
   NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
   using GfxInfoBase::GetFeatureStatus;
   using GfxInfoBase::GetFeatureSuggestedDriverVersion;
 
   virtual nsresult Init() override;