Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Thu, 14 Feb 2019 06:40:41 +0200
changeset 458991 f0ea53f47215
parent 458972 c6829642e2d0 (current diff)
parent 458990 f300070152e9 (diff)
child 459003 468e03d7e33a
child 459119 a0752d7e8073
push id35553
push usershindli@mozilla.com
push dateThu, 14 Feb 2019 04:41:18 +0000
treeherdermozilla-central@f0ea53f47215 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
f0ea53f47215 / 67.0a1 / 20190214044118 / files
nightly linux64
f0ea53f47215 / 67.0a1 / 20190214044118 / files
nightly mac
f0ea53f47215 / 67.0a1 / 20190214044118 / files
nightly win32
f0ea53f47215 / 67.0a1 / 20190214044118 / files
nightly win64
f0ea53f47215 / 67.0a1 / 20190214044118 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
js/src/devtools/rootAnalysis/run-test.py
--- a/browser/base/content/test/performance/browser_appmenu.js
+++ b/browser/base/content/test/performance/browser_appmenu.js
@@ -21,17 +21,17 @@ const EXPECTED_APPMENU_OPEN_REFLOWS = [
   },
 
   {
     stack: [
       "adjustArrowPosition@chrome://global/content/bindings/popup.xml",
       "onxblpopuppositioned@chrome://global/content/bindings/popup.xml",
     ],
 
-    maxCount: 17, // This number should only ever go down - never up.
+    maxCount: 22, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_calculateMaxHeight@resource:///modules/PanelMultiView.jsm",
       "handleEvent@resource:///modules/PanelMultiView.jsm",
     ],
 
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -112,27 +112,29 @@ AutofillProfileAutoCompleteSearch.protot
     let isAddressField = FormAutofillUtils.isAddressField(activeFieldDetail.fieldName);
     let isInputAutofilled = activeFieldDetail.state == FIELD_STATES.AUTO_FILLED;
     let allFieldNames = activeSection.allFieldNames;
     let filledRecordGUID = activeSection.filledRecordGUID;
     let searchPermitted = isAddressField ?
                           FormAutofill.isAutofillAddressesEnabled :
                           FormAutofill.isAutofillCreditCardsEnabled;
     let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult;
+    let isFormAutofillSearch = true;
     let pendingSearchResult = null;
 
     ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = activeInput;
     // Fallback to form-history if ...
     //   - specified autofill feature is pref off.
     //   - no profile can fill the currently-focused input.
     //   - the current form has already been populated.
     //   - (address only) less than 3 inputs are covered by all saved fields in the storage.
     if (!searchPermitted || !savedFieldNames.has(activeFieldDetail.fieldName) ||
         (!isInputAutofilled && filledRecordGUID) || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
+      isFormAutofillSearch = false;
       if (activeInput.autocomplete == "off") {
         // Create a dummy result as an empty search result.
         pendingSearchResult = new AutocompleteResult("", "", [], [], {});
       } else {
         pendingSearchResult = new Promise(resolve => {
           let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
                             .createInstance(Ci.nsIAutoCompleteSearch);
           formHistory.startSearch(searchString, searchParam, previousResult, {
@@ -168,21 +170,25 @@ AutofillProfileAutoCompleteSearch.protot
                                       allFieldNames,
                                       adaptedRecords,
                                       {isSecure, isInputAutofilled});
       });
     }
 
     Promise.resolve(pendingSearchResult).then((result) => {
       listener.onSearchResult(this, result);
-      ProfileAutocomplete.lastProfileAutoCompleteResult = result;
-      // Reset AutoCompleteController's state at the end of startSearch to ensure that
-      // none of form autofill result will be cached in other places and make the
-      // result out of sync.
-      autocompleteController.resetInternalState();
+      // Don't save cache results or reset state when returning non-autofill results such as the
+      // form history fallback above.
+      if (isFormAutofillSearch) {
+        ProfileAutocomplete.lastProfileAutoCompleteResult = result;
+        // Reset AutoCompleteController's state at the end of startSearch to ensure that
+        // none of form autofill result will be cached in other places and make the
+        // result out of sync.
+        autocompleteController.resetInternalState();
+      }
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
    */
   stopSearch() {
     ProfileAutocomplete.lastProfileAutoCompleteResult = null;
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -32,17 +32,16 @@ let MOCK_STORAGE = [{
   "address-level1": "CA",
 }];
 
 async function setupAddressStorage() {
   await addAddress(MOCK_STORAGE[0]);
   await addAddress(MOCK_STORAGE[1]);
 }
 
-// TODO: Add form history fallback test cases for credit card fields. (bug 1393374)
 async function setupFormHistory() {
   await updateFormHistory([
     {op: "add", fieldname: "tel", value: "+1234567890"},
     {op: "add", fieldname: "email", value: "foo@mozilla.com"},
   ]);
 }
 
 initPopupListener();
@@ -154,34 +153,36 @@ add_task(async function check_fields_aft
   await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]);
   synthesizeKey("KEY_Escape");
   is(focusedInput.value, "Mozilla", "Filled field shouldn't be reverted by ESC key");
 });
 
 // Fallback to history search after autofill address.
 add_task(async function check_fallback_after_form_autofill() {
   await setInput("#tel", "", true);
-  synthesizeKey("KEY_ArrowDown");
-  await expectPopup();
+  await triggerPopupAndHoverItem("#tel", 0);
   checkMenuEntries(["+1234567890"], false);
+  await triggerAutofillAndCheckProfile({
+    tel: "+1234567890",
+  });
 });
 
 // Resume form autofill once all the autofilled fileds are changed.
 add_task(async function check_form_autofill_resume() {
   document.querySelector("#tel").blur();
   document.querySelector("#form1").reset();
   await setInput("#tel", "");
-  synthesizeKey("KEY_ArrowDown");
-  await expectPopup();
+  await triggerPopupAndHoverItem("#tel", 0);
   checkMenuEntries(MOCK_STORAGE.map(address =>
     JSON.stringify({
       primary: address.tel,
       secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
     })
   ));
+  await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]);
 });
 
 </script>
 
 <p id="display"></p>
 
 <div id="content">
 
--- 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.8
+Current extension version is: 2.1.266
 
-Taken from upstream commit: dfe7d9bc
+Taken from upstream commit: 81f5835c
--- 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.8';
-var pdfjsBuild = 'dfe7d9bc';
+var pdfjsVersion = '2.1.266';
+var pdfjsBuild = '81f5835c';
 
 var pdfjsSharedUtil = __w_pdfjs_require__(1);
 
 var pdfjsDisplayAPI = __w_pdfjs_require__(6);
 
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(18);
 
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(19);
@@ -1406,17 +1406,17 @@ function _fetchDocument(worker, source, 
 
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
   }
 
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.2.8',
+    apiVersion: '2.1.266',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -2334,17 +2334,20 @@ const PDFWorker = function PDFWorkerClos
 
 exports.PDFWorker = PDFWorker;
 
 class WorkerTransport {
   constructor(messageHandler, loadingTask, networkStream, params) {
     this.messageHandler = messageHandler;
     this.loadingTask = loadingTask;
     this.commonObjs = new PDFObjects();
-    this.fontLoader = new _font_loader.FontLoader(loadingTask.docId);
+    this.fontLoader = new _font_loader.FontLoader({
+      docId: loadingTask.docId,
+      onUnsupportedFeature: this._onUnsupportedFeature.bind(this)
+    });
     this._params = params;
     this.CMapReaderFactory = new params.CMapReaderFactory({
       baseUrl: params.cMapUrl,
       isCompressed: params.cMapPacked
     });
     this.destroyed = false;
     this.destroyCapability = null;
     this._passwordCapability = null;
@@ -2595,22 +2598,25 @@ class WorkerTransport {
 
           const font = new _font_loader.FontFaceObject(exportedData, {
             isEvalSupported: params.isEvalSupported,
             disableFontFace: params.disableFontFace,
             ignoreErrors: params.ignoreErrors,
             onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
             fontRegistry
           });
-
-          const fontReady = fontObjs => {
+          this.fontLoader.bind(font).then(() => {
             this.commonObjs.resolve(id, font);
-          };
-
-          this.fontLoader.bind([font], fontReady);
+          }, reason => {
+            messageHandler.sendWithPromise('FontFallback', {
+              id
+            }).finally(() => {
+              this.commonObjs.resolve(id, font);
+            });
+          });
           break;
 
         case 'FontPath':
           this.commonObjs.resolve(id, exportedData);
           break;
 
         default:
           throw new Error(`Got unknown common object type ${type}`);
@@ -3137,19 +3143,19 @@ const InternalRenderTask = function Inte
       }
     }
 
   }
 
   return InternalRenderTask;
 }();
 
-const version = '2.2.8';
+const version = '2.1.266';
 exports.version = version;
-const build = 'dfe7d9bc';
+const build = '81f5835c';
 exports.build = build;
 
 /***/ }),
 /* 7 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
@@ -3554,28 +3560,28 @@ function loadScript(src) {
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.FontLoader = exports.FontFaceObject = void 0;
 
 var _util = __w_pdfjs_require__(1);
 
 class BaseFontLoader {
-  constructor(docId) {
+  constructor({
+    docId,
+    onUnsupportedFeature
+  }) {
     if (this.constructor === BaseFontLoader) {
       (0, _util.unreachable)('Cannot initialize BaseFontLoader.');
     }
 
     this.docId = docId;
+    this._onUnsupportedFeature = onUnsupportedFeature;
     this.nativeFontFaces = [];
     this.styleElement = null;
-    this.loadingContext = {
-      requests: [],
-      nextRequestId: 0
-    };
   }
 
   addNativeFontFace(nativeFontFace) {
     this.nativeFontFaces.push(nativeFontFace);
     document.fonts.add(nativeFontFace);
   }
 
   insertRule(rule) {
@@ -3598,83 +3604,64 @@ class BaseFontLoader {
     this.nativeFontFaces.length = 0;
 
     if (this.styleElement) {
       this.styleElement.remove();
       this.styleElement = null;
     }
   }
 
-  bind(fonts, callback) {
-    const rules = [];
-    const fontsToLoad = [];
-    const fontLoadPromises = [];
-
-    const getNativeFontPromise = function (nativeFontFace) {
-      return nativeFontFace.loaded.catch(function (reason) {
-        (0, _util.warn)(`Failed to load font "${nativeFontFace.family}": ${reason}`);
-      });
-    };
-
-    for (const font of fonts) {
-      if (font.attached || font.missingFile) {
-        continue;
-      }
-
-      font.attached = true;
-
-      if (this.isFontLoadingAPISupported) {
-        const nativeFontFace = font.createNativeFontFace();
-
-        if (nativeFontFace) {
-          this.addNativeFontFace(nativeFontFace);
-          fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
-        }
-      } else {
-        const rule = font.createFontFaceRule();
-
-        if (rule) {
-          this.insertRule(rule);
-          rules.push(rule);
-          fontsToLoad.push(font);
-        }
-      }
-    }
-
-    const request = this._queueLoadingCallback(callback);
+  async bind(font) {
+    if (font.attached || font.missingFile) {
+      return;
+    }
+
+    font.attached = true;
 
     if (this.isFontLoadingAPISupported) {
-      Promise.all(fontLoadPromises).then(request.complete);
-    } else if (rules.length > 0 && !this.isSyncFontLoadingSupported) {
-      this._prepareFontLoadEvent(rules, fontsToLoad, request);
-    } else {
-      request.complete();
+      const nativeFontFace = font.createNativeFontFace();
+
+      if (nativeFontFace) {
+        this.addNativeFontFace(nativeFontFace);
+
+        try {
+          await nativeFontFace.loaded;
+        } catch (ex) {
+          this._onUnsupportedFeature({
+            featureId: _util.UNSUPPORTED_FEATURES.font
+          });
+
+          (0, _util.warn)(`Failed to load font '${nativeFontFace.family}': '${ex}'.`);
+          font.disableFontFace = true;
+          throw ex;
+        }
+      }
+
+      return;
+    }
+
+    const rule = font.createFontFaceRule();
+
+    if (rule) {
+      this.insertRule(rule);
+
+      if (this.isSyncFontLoadingSupported) {
+        return;
+      }
+
+      return new Promise(resolve => {
+        const request = this._queueLoadingCallback(resolve);
+
+        this._prepareFontLoadEvent([rule], [font], request);
+      });
     }
   }
 
   _queueLoadingCallback(callback) {
-    function completeRequest() {
-      (0, _util.assert)(!request.done, 'completeRequest() cannot be called twice.');
-      request.done = true;
-
-      while (context.requests.length > 0 && context.requests[0].done) {
-        const otherRequest = context.requests.shift();
-        setTimeout(otherRequest.callback, 0);
-      }
-    }
-
-    const context = this.loadingContext;
-    const request = {
-      id: `pdfjs-font-loading-${context.nextRequestId++}`,
-      done: false,
-      complete: completeRequest,
-      callback
-    };
-    context.requests.push(request);
-    return request;
+    (0, _util.unreachable)('Abstract method `_queueLoadingCallback`.');
   }
 
   get isFontLoadingAPISupported() {
     (0, _util.unreachable)('Abstract method `isFontLoadingAPISupported`.');
   }
 
   get isSyncFontLoadingSupported() {
     (0, _util.unreachable)('Abstract method `isSyncFontLoadingSupported`.');
--- 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.8';
-const pdfjsBuild = 'dfe7d9bc';
+const pdfjsVersion = '2.1.266';
+const pdfjsBuild = '81f5835c';
 
 const pdfjsCoreWorker = __w_pdfjs_require__(1);
 
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
@@ -370,17 +370,17 @@ var WorkerMessageHandler = {
   },
 
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.2.8';
+    let workerVersion = '2.1.266';
 
     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';
@@ -761,16 +761,19 @@ var WorkerMessageHandler = {
             return;
           }
 
           sink.error(reason);
           throw reason;
         });
       });
     });
+    handler.on('FontFallback', function (data) {
+      return pdfManager.fontFallback(data.id, handler);
+    });
     handler.on('Cleanup', function wphCleanup(data) {
       return pdfManager.cleanup();
     });
     handler.on('Terminate', function wphTerminate(data) {
       terminated = true;
 
       if (pdfManager) {
         pdfManager.terminate();
@@ -1900,16 +1903,20 @@ class BasePdfManager {
   ensureCatalog(prop, args) {
     return this.ensure(this.pdfDocument.catalog, prop, args);
   }
 
   getPage(pageIndex) {
     return this.pdfDocument.getPage(pageIndex);
   }
 
+  fontFallback(id, handler) {
+    return this.pdfDocument.fontFallback(id, handler);
+  }
+
   cleanup() {
     return this.pdfDocument.cleanup();
   }
 
   async ensure(obj, prop, args) {
     (0, _util.unreachable)('Abstract method `ensure` called');
   }
 
@@ -3264,16 +3271,20 @@ class PDFDocument {
       if (reason instanceof _util.XRefEntryException) {
         this._pagePromises.length = 0;
         this.cleanup();
         throw new _util.XRefParseException();
       }
     });
   }
 
+  fontFallback(id, handler) {
+    return this.catalog.fontFallback(id, handler);
+  }
+
   cleanup() {
     return this.catalog.cleanup();
   }
 
 }
 
 exports.PDFDocument = PDFDocument;
 
@@ -3809,16 +3820,31 @@ class Catalog {
       } else {
         appendIfJavaScriptDict(openActionDict);
       }
     }
 
     return (0, _util.shadow)(this, 'javaScript', javaScript);
   }
 
+  fontFallback(id, handler) {
+    const promises = [];
+    this.fontCache.forEach(function (promise) {
+      promises.push(promise);
+    });
+    return Promise.all(promises).then(translatedFonts => {
+      for (const translatedFont of translatedFonts) {
+        if (translatedFont.loadedName === id) {
+          translatedFont.fallback(handler);
+          return;
+        }
+      }
+    });
+  }
+
   cleanup() {
     this.pageKidsCountCache.clear();
     const promises = [];
     this.fontCache.forEach(function (promise) {
       promises.push(promise);
     });
     return Promise.all(promises).then(translatedFonts => {
       for (let i = 0, ii = translatedFonts.length; i < ii; i++) {
@@ -20062,42 +20088,32 @@ var PartialEvaluator = function PartialE
           return new TranslatedFont('g_font_error', new _fonts.ErrorFont('Type3 font load error: ' + reason), translated.font);
         });
       }).then(translated => {
         state.font = translated.font;
         translated.send(this.handler);
         return translated.loadedName;
       });
     },
-    handleText: function PartialEvaluator_handleText(chars, state) {
-      var font = state.font;
-      var glyphs = font.charsToGlyphs(chars);
-      var isAddToPathSet = !!(state.textRenderingMode & _util.TextRenderingMode.ADD_TO_PATH_FLAG);
-
-      if (font.data && (isAddToPathSet || this.options.disableFontFace || state.fillColorSpace.name === 'Pattern')) {
-        var buildPath = fontChar => {
-          if (!font.renderer.hasBuiltPath(fontChar)) {
-            var path = font.renderer.getPathJs(fontChar);
-            this.handler.send('commonobj', [font.loadedName + '_path_' + fontChar, 'FontPath', path]);
-          }
-        };
-
-        for (var i = 0, ii = glyphs.length; i < ii; i++) {
-          var glyph = glyphs[i];
-          buildPath(glyph.fontChar);
-          var accent = glyph.accent;
-
-          if (accent && accent.fontChar) {
-            buildPath(accent.fontChar);
-          }
+
+    handleText(chars, state) {
+      const font = state.font;
+      const glyphs = font.charsToGlyphs(chars);
+
+      if (font.data) {
+        const isAddToPathSet = !!(state.textRenderingMode & _util.TextRenderingMode.ADD_TO_PATH_FLAG);
+
+        if (isAddToPathSet || state.fillColorSpace.name === 'Pattern' || font.disableFontFace || this.options.disableFontFace) {
+          PartialEvaluator.buildFontPaths(font, glyphs, this.handler);
         }
       }
 
       return glyphs;
     },
+
     setGState: function PartialEvaluator_setGState(resources, gState, operatorList, task, stateManager) {
       var gStateObj = [];
       var gStateKeys = gState.getKeys();
       var promise = Promise.resolve();
 
       for (var i = 0, ii = gStateKeys.length; i < ii; i++) {
         let key = gStateKeys[i];
         let value = gState.get(key);
@@ -22006,16 +22022,36 @@ var PartialEvaluator = function PartialE
         if (type === 'Type3') {
           properties.isType3Font = true;
         }
 
         return new _fonts.Font(fontName.name, fontFile, properties);
       });
     }
   };
+
+  PartialEvaluator.buildFontPaths = function (font, glyphs, handler) {
+    function buildPath(fontChar) {
+      if (font.renderer.hasBuiltPath(fontChar)) {
+        return;
+      }
+
+      handler.send('commonobj', [`${font.loadedName}_path_${fontChar}`, 'FontPath', font.renderer.getPathJs(fontChar)]);
+    }
+
+    for (const glyph of glyphs) {
+      buildPath(glyph.fontChar);
+      const accent = glyph.accent;
+
+      if (accent && accent.fontChar) {
+        buildPath(accent.fontChar);
+      }
+    }
+  };
+
   return PartialEvaluator;
 }();
 
 exports.PartialEvaluator = PartialEvaluator;
 
 var TranslatedFont = function TranslatedFontClosure() {
   function TranslatedFont(loadedName, font, dict) {
     this.loadedName = loadedName;
@@ -22026,19 +22062,28 @@ var TranslatedFont = function Translated
   }
 
   TranslatedFont.prototype = {
     send(handler) {
       if (this.sent) {
         return;
       }
 
-      var fontData = this.font.exportData();
-      handler.send('commonobj', [this.loadedName, 'Font', fontData]);
       this.sent = true;
+      handler.send('commonobj', [this.loadedName, 'Font', this.font.exportData()]);
+    },
+
+    fallback(handler) {
+      if (!this.font.data) {
+        return;
+      }
+
+      this.font.disableFontFace = true;
+      const glyphs = this.font.glyphCacheValues;
+      PartialEvaluator.buildFontPaths(this.font, glyphs, handler);
     },
 
     loadType3Data(evaluator, resources, parentOperatorList, task) {
       if (!this.font.isType3Font) {
         throw new Error('Must be a Type3 font.');
       }
 
       if (this.type3Loaded) {
@@ -24592,16 +24637,17 @@ var Font = function FontClosure() {
     return nameTable;
   }
 
   Font.prototype = {
     name: null,
     font: null,
     mimetype: null,
     encoding: null,
+    disableFontFace: false,
 
     get renderer() {
       var renderer = _font_renderer.FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
 
       return (0, _util.shadow)(this, 'renderer', renderer);
     },
 
     exportData: function Font_exportData() {
@@ -26283,17 +26329,22 @@ var Font = function FontClosure() {
         for (i = 0, ii = chars.length; i < ii; ++i) {
           charcode = chars.charCodeAt(i);
           glyph = this.charToGlyph(charcode, charcode === 0x20);
           glyphs.push(glyph);
         }
       }
 
       return charsCache[charsCacheKey] = glyphs;
-    }
+    },
+
+    get glyphCacheValues() {
+      return Object.values(this.glyphCache);
+    }
+
   };
   return Font;
 }();
 
 exports.Font = Font;
 
 var ErrorFont = function ErrorFontClosure() {
   function ErrorFont(error) {
--- 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.8
+  release: version 2.1.266
 
   # 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/html/HTMLTableElement.cpp
+++ b/dom/html/HTMLTableElement.cpp
@@ -584,21 +584,20 @@ already_AddRefed<nsGenericHTMLElement> H
     }
 
     nsINode::InsertBefore(*head, refNode, IgnoreErrors());
   }
   return head.forget();
 }
 
 void HTMLTableElement::DeleteTHead() {
-  HTMLTableSectionElement* tHead = GetTHead();
+  RefPtr<HTMLTableSectionElement> tHead = GetTHead();
   if (tHead) {
-    mozilla::ErrorResult rv;
+    mozilla::IgnoredErrorResult rv;
     nsINode::RemoveChild(*tHead, rv);
-    MOZ_ASSERT(!rv.Failed());
   }
 }
 
 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateTFoot() {
   RefPtr<nsGenericHTMLElement> foot = GetTFoot();
   if (!foot) {
     // create a new foot rowgroup
     RefPtr<mozilla::dom::NodeInfo> nodeInfo;
@@ -611,21 +610,20 @@ already_AddRefed<nsGenericHTMLElement> H
     }
     AppendChildTo(foot, true);
   }
 
   return foot.forget();
 }
 
 void HTMLTableElement::DeleteTFoot() {
-  HTMLTableSectionElement* tFoot = GetTFoot();
+  RefPtr<HTMLTableSectionElement> tFoot = GetTFoot();
   if (tFoot) {
-    mozilla::ErrorResult rv;
+    mozilla::IgnoredErrorResult rv;
     nsINode::RemoveChild(*tFoot, rv);
-    MOZ_ASSERT(!rv.Failed());
   }
 }
 
 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateCaption() {
   RefPtr<nsGenericHTMLElement> caption = GetCaption();
   if (!caption) {
     // Create a new caption.
     RefPtr<mozilla::dom::NodeInfo> nodeInfo;
@@ -639,21 +637,20 @@ already_AddRefed<nsGenericHTMLElement> H
 
     nsCOMPtr<nsINode> firsChild = nsINode::GetFirstChild();
     nsINode::InsertBefore(*caption, firsChild, IgnoreErrors());
   }
   return caption.forget();
 }
 
 void HTMLTableElement::DeleteCaption() {
-  HTMLTableCaptionElement* caption = GetCaption();
+  RefPtr<HTMLTableCaptionElement> caption = GetCaption();
   if (caption) {
-    mozilla::ErrorResult rv;
+    mozilla::IgnoredErrorResult rv;
     nsINode::RemoveChild(*caption, rv);
-    MOZ_ASSERT(!rv.Failed());
   }
 }
 
 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateTBody() {
   RefPtr<mozilla::dom::NodeInfo> nodeInfo =
       OwnerDoc()->NodeInfoManager()->GetNodeInfo(
           nsGkAtoms::tbody, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
   MOZ_ASSERT(nodeInfo);
--- a/dom/html/HTMLTableSectionElement.cpp
+++ b/dom/html/HTMLTableSectionElement.cpp
@@ -100,17 +100,17 @@ void HTMLTableSectionElement::DeleteRow(
       return;
     }
 
     --refIndex;
   } else {
     refIndex = (uint32_t)aValue;
   }
 
-  nsINode* row = rows->Item(refIndex);
+  nsCOMPtr<nsINode> row = rows->Item(refIndex);
   if (!row) {
     aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   nsINode::RemoveChild(*row, aError);
 }
 
--- a/js/public/GCAnnotations.h
+++ b/js/public/GCAnnotations.h
@@ -6,16 +6,18 @@
 
 #ifndef js_GCAnnotations_h
 #define js_GCAnnotations_h
 
 // Set of annotations for the rooting hazard analysis, used to categorize types
 // and functions.
 #ifdef XGILL_PLUGIN
 
+#  define JS_EXPECT_HAZARDS __attribute__((annotate("Expect Hazards")))
+
 // Mark a type as being a GC thing (eg js::gc::Cell has this annotation).
 #  define JS_HAZ_GC_THING __attribute__((annotate("GC Thing")))
 
 // Mark a type as holding a pointer to a GC thing (eg JS::Value has this
 // annotation.) "Inherited" by templatized types with
 // MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS.
 #  define JS_HAZ_GC_POINTER __attribute__((annotate("GC Pointer")))
 
@@ -58,16 +60,17 @@
 
 // Mark a function as able to call JSNatives. Otherwise, JSNatives don't show
 // up in the callgraph. This doesn't matter for the can-GC analysis, but it is
 // very nice for other uses of the callgraph.
 #  define JS_HAZ_JSNATIVE_CALLER __attribute__((annotate("Calls JSNatives")))
 
 #else
 
+#  define JS_EXPECT_HAZARDS
 #  define JS_HAZ_GC_THING
 #  define JS_HAZ_GC_POINTER
 #  define JS_HAZ_ROOTED
 #  define JS_HAZ_GC_INVALIDATED
 #  define JS_HAZ_ROOTED_BASE
 #  define JS_HAZ_NON_GC_POINTER
 #  define JS_HAZ_GC_CALL
 #  define JS_HAZ_GC_SUPPRESSED
--- a/js/public/GCHashTable.h
+++ b/js/public/GCHashTable.h
@@ -86,17 +86,17 @@ class GCHashMap : public js::HashMap<Key
     MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
     Base::operator=(std::move(rhs));
   }
 
  private:
   // GCHashMap is not copyable or assignable
   GCHashMap(const GCHashMap& hm) = delete;
   GCHashMap& operator=(const GCHashMap& hm) = delete;
-};
+} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS;
 
 }  // namespace JS
 
 namespace js {
 
 // HashMap that supports rekeying.
 //
 // If your keys are pointers to something like JSObject that can be tenured or
@@ -127,17 +127,17 @@ class GCRekeyableHashMap : public JS::GC
   }
 
   // GCRekeyableHashMap is movable
   GCRekeyableHashMap(GCRekeyableHashMap&& rhs) : Base(std::move(rhs)) {}
   void operator=(GCRekeyableHashMap&& rhs) {
     MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
     Base::operator=(std::move(rhs));
   }
-};
+} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS;
 
 template <typename Wrapper, typename... Args>
 class WrappedPtrOperations<JS::GCHashMap<Args...>, Wrapper> {
   using Map = JS::GCHashMap<Args...>;
   using Lookup = typename Map::Lookup;
 
   const Map& map() const { return static_cast<const Wrapper*>(this)->get(); }
 
@@ -258,17 +258,17 @@ class GCHashSet : public js::HashSet<T, 
     MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
     Base::operator=(std::move(rhs));
   }
 
  private:
   // GCHashSet is not copyable or assignable
   GCHashSet(const GCHashSet& hs) = delete;
   GCHashSet& operator=(const GCHashSet& hs) = delete;
-};
+} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS;
 
 }  // namespace JS
 
 namespace js {
 
 template <typename Wrapper, typename... Args>
 class WrappedPtrOperations<JS::GCHashSet<Args...>, Wrapper> {
   using Set = JS::GCHashSet<Args...>;
@@ -528,17 +528,17 @@ class WeakCache<GCHashMap<Key, Value, Ha
   bool put(KeyInput&& k, ValueInput&& v) {
     return map.put(std::forward<KeyInput>(k), std::forward<ValueInput>(v));
   }
 
   template <typename KeyInput, typename ValueInput>
   bool putNew(KeyInput&& k, ValueInput&& v) {
     return map.putNew(std::forward<KeyInput>(k), std::forward<ValueInput>(v));
   }
-};
+} JS_HAZ_NON_GC_POINTER;
 
 // Specialize WeakCache for GCHashSet to provide a barriered set that does not
 // need to be swept immediately.
 template <typename T, typename HashPolicy, typename AllocPolicy>
 class WeakCache<GCHashSet<T, HashPolicy, AllocPolicy>>
     : protected detail::WeakCacheBase {
   using Set = GCHashSet<T, HashPolicy, AllocPolicy>;
   using Self = WeakCache<Set>;
@@ -713,13 +713,13 @@ class WeakCache<GCHashSet<T, HashPolicy,
   bool putNew(TInput&& t) {
     return set.putNew(std::forward<TInput>(t));
   }
 
   template <typename TInput>
   bool putNew(const Lookup& l, TInput&& t) {
     return set.putNew(l, std::forward<TInput>(t));
   }
-};
+} JS_HAZ_NON_GC_POINTER;
 
 }  // namespace JS
 
 #endif /* GCHashTable_h */
--- a/js/public/SweepingAPI.h
+++ b/js/public/SweepingAPI.h
@@ -69,13 +69,13 @@ class WeakCache : protected detail::Weak
   T& get() { return cache; }
 
   size_t sweep() override {
     GCPolicy<T>::sweep(&cache);
     return 0;
   }
 
   bool needsSweep() override { return cache.needsSweep(); }
-};
+} JS_HAZ_NON_GC_POINTER;
 
 }  // namespace JS
 
 #endif  // js_SweepingAPI_h
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -121,24 +121,30 @@ function expressionUsesVariableContents(
                 return true;
         } else if (expressionUsesVariableContents(childExp, variable)) {
             return true;
         }
     }
     return false;
 }
 
+function isImmobileValue(exp) {
+    if (exp.Kind == "Int" && exp.String == "0") {
+        return true;
+    }
+    return false;
+}
+
 // Detect simple |return nullptr;| statements.
 function isReturningImmobileValue(edge, variable)
 {
     if (variable.Kind == "Return") {
         if (edge.Exp[0].Kind == "Var" && sameVariable(edge.Exp[0].Variable, variable)) {
-            if (edge.Exp[1].Kind == "Int" && edge.Exp[1].String == "0") {
+            if (isImmobileValue(edge.Exp[1]))
                 return true;
-            }
         }
     }
     return false;
 }
 
 // If the edge uses the given variable's value, return the earliest point at
 // which the use is definite. Usually, that means the source of the edge
 // (anything that reaches that source point will end up using the variable, but
@@ -261,29 +267,30 @@ function edgeTakesVariableAddress(edge, 
     }
 }
 
 function expressionIsVariable(exp, variable)
 {
     return exp.Kind == "Var" && sameVariable(exp.Variable, variable);
 }
 
-// Return whether the edge kills (overwrites) the variable's incoming value.
-// Examples of killing 'obj':
+// Return whether the edge terminates the live range of a variable's value when
+// searching in reverse through the CFG, by setting it to some new value.
+// Examples of killing 'obj's live range:
 //
 //     obj = foo;
 //     obj = foo();
-//     obj = foo(obj);         // uses previous value but then kills it
+//     obj = foo(obj);         // uses previous value but then sets to new value
 //     SomeClass obj(true, 1); // constructor
 //
 function edgeKillsVariable(edge, variable)
 {
     // Direct assignments kill their lhs: var = value
     if (edge.Kind == "Assign") {
-        const [lhs] = edge.Exp;
+        const [lhs, rhs] = edge.Exp;
         return (expressionIsVariable(lhs, variable) &&
                 !isReturningImmobileValue(edge, variable));
     }
 
     if (edge.Kind != "Call")
         return false;
 
     // Assignments of call results kill their lhs.
@@ -329,16 +336,173 @@ function edgeKillsVariable(edge, variabl
 
         if (calleeName.endsWith(constructorName))
             return true;
     }
 
     return false;
 }
 
+function edgeMovesVariable(edge, variable)
+{
+    if (edge.Kind != 'Call')
+        return false;
+    const callee = edge.Exp[0];
+    if (callee.Kind == 'Var' &&
+        callee.Variable.Kind == 'Func')
+    {
+        const { Variable: { Name: [ fullname, shortname ] } } = callee;
+        const [ mangled, unmangled ] = splitFunction(fullname);
+        // Match a UniquePtr move constructor.
+        if (unmangled.match(/::UniquePtr<[^>]*>::UniquePtr\((\w+::)*UniquePtr<[^>]*>&&/))
+            return true;
+    }
+
+    return false;
+}
+
+// Scan forward through the given 'body', starting at 'startpoint', looking for
+// a call that passes 'variable' to a move constructor that "consumes" it (eg
+// UniquePtr::UniquePtr(UniquePtr&&)).
+function bodyEatsVariable(variable, body, startpoint)
+{
+    const successors = getSuccessors(body);
+    const work = [startpoint];
+    while (work.length > 0) {
+        const point = work.shift();
+        if (!(point in successors))
+            continue;
+        for (const edge of successors[point]) {
+            if (edgeMovesVariable(edge, variable))
+                return true;
+            // edgeKillsVariable will find places where 'variable' is given a
+            // new value. Never observed in practice, since this function is
+            // only called with a temporary resulting from std::move(), which
+            // is used immediately for a call. But just to be robust to future
+            // uses:
+            if (!edgeKillsVariable(edge, variable))
+                work.push(edge.Index[1]);
+        }
+    }
+    return false;
+}
+
+// Return whether an edge "clears out" a variable's value. A simple example
+// would be
+//
+//     var = nullptr;
+//
+// for analyses for which nullptr is a "safe" value (eg GC rooting hazards; you
+// can't get in trouble by holding a nullptr live across a GC.) A more complex
+// example is a Maybe<T> that gets reset:
+//
+//     Maybe<AutoCheckCannotGC> nogc;
+//     nogc.emplace(cx);
+//     nogc.reset();
+//     gc();             // <-- not a problem; nogc is invalidated by prev line
+//     nogc.emplace(cx);
+//     foo(nogc);
+//
+// Yet another example is a UniquePtr being passed by value, which means the
+// receiver takes ownership:
+//
+//     UniquePtr<JSObject*> uobj(obj);
+//     foo(uobj);
+//     gc();
+//
+// Compare to edgeKillsVariable: killing (in backwards direction) means the
+// variable's value was live and is no longer. Invalidating means it wasn't
+// actually live after all.
+//
+function edgeInvalidatesVariable(edge, variable, body)
+{
+    // var = nullptr;
+    if (edge.Kind == "Assign") {
+        const [lhs, rhs] = edge.Exp;
+        return expressionIsVariable(lhs, variable) && isImmobileValue(rhs);
+    }
+
+    if (edge.Kind != "Call")
+        return false;
+
+    var callee = edge.Exp[0];
+
+    if (edge.Type.Kind == 'Function' &&
+        edge.Exp[0].Kind == 'Var' &&
+        edge.Exp[0].Variable.Kind == 'Func' &&
+        edge.Exp[0].Variable.Name[1] == 'move' &&
+        edge.Exp[0].Variable.Name[0].includes('std::move(') &&
+        expressionIsVariable(edge.PEdgeCallArguments.Exp[0], variable) &&
+        edge.Exp[1].Kind == 'Var' &&
+        edge.Exp[1].Variable.Kind == 'Temp')
+    {
+        // temp = std::move(var)
+        //
+        // If var is a UniquePtr, and we pass it into something that takes
+        // ownership, then it should be considered to be invalid. It really
+        // ought to be invalidated at the point of the function call that calls
+        // the move constructor, but given that we're creating a temporary here
+        // just for the purpose of passing it in, this edge is good enough.
+        const lhs = edge.Exp[1].Variable;
+        if (bodyEatsVariable(lhs, body, edge.Index[1]))
+            return true;
+    }
+
+    if (edge.Type.Kind == 'Function' &&
+        edge.Type.TypeFunctionCSU &&
+        edge.PEdgeCallInstance &&
+        edge.PEdgeCallInstance.Exp.Kind == 'Var' &&
+        expressionIsVariable(edge.PEdgeCallInstance.Exp, variable))
+    do {
+        const typeName = edge.Type.TypeFunctionCSU.Type.Name;
+        const m = typeName.match(/^(((\w|::)+?)(\w+))</);
+        if (!m)
+            break;
+        const [, type, namespace,, classname] = m;
+
+        // special-case: the initial constructor that doesn't provide a value.
+        // Useful for things like Maybe<T>.
+        if (callee.Kind == 'Var' &&
+            typesWithSafeConstructors.has(type) &&
+            callee.Variable.Name[0].includes(`${namespace}${classname}<T>::${classname}()`))
+        {
+            return true;
+        }
+
+        // special-case: UniquePtr::reset() and similar.
+        if (callee.Kind == 'Var' &&
+            type in resetterMethods &&
+            resetterMethods[type].has(callee.Variable.Name[1]))
+        {
+            return true;
+        }
+
+    } while(0);
+
+    // special-case: passing UniquePtr<T> by value.
+    if (edge.Type.Kind == 'Function' &&
+        edge.Type.TypeFunctionArgument &&
+        edge.PEdgeCallArguments)
+    {
+        for (const i in edge.Type.TypeFunctionArgument) {
+            const param = edge.Type.TypeFunctionArgument[i];
+            if (param.Type.Kind != 'CSU')
+                continue;
+            if (!param.Type.Name.startsWith("mozilla::UniquePtr<"))
+                continue;
+            const arg = edge.PEdgeCallArguments.Exp[i];
+            if (expressionIsVariable(arg, variable)) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 function edgeCanGC(edge)
 {
     if (edge.Kind != "Call")
         return false;
 
     var callee = edge.Exp[0];
 
     while (callee.Kind == "Drf")
@@ -478,16 +642,23 @@ function findGCBeforeValueUse(start_body
 
         var predecessors = getPredecessors(body);
         if (!(ppoint in predecessors))
             continue;
 
         for (var edge of predecessors[ppoint]) {
             var source = edge.Index[0];
 
+            if (edgeInvalidatesVariable(edge, variable, body)) {
+                // Terminate the search through this point; we thought we were
+                // within the live range, but it turns out that the variable
+                // was set to a value that we don't care about.
+                continue;
+            }
+
             var edge_kills = edgeKillsVariable(edge, variable);
             var edge_uses = edgeUsesVariable(edge, variable, body);
 
             if (edge_kills || edge_uses) {
                 if (!body.minimumUse || source < body.minimumUse)
                     body.minimumUse = source;
             }
 
@@ -609,16 +780,20 @@ function variableLiveAcrossGC(suppressed
             //   obj = CopyObject(obj);
             //
             // This is not a hazard, because even though CopyObject can GC, obj
             // is not live across it. (obj is live before CopyObject, and
             // probably after, but not across.) There may be a hazard within
             // CopyObject, of course.
             //
 
+            // Ignore uses that are just invalidating the previous value.
+            if (edgeInvalidatesVariable(edge, variable, body))
+                continue;
+
             var usePoint = edgeUsesVariable(edge, variable, body);
             if (usePoint) {
                 var call = findGCBeforeValueUse(body, usePoint, suppressed, variable);
                 if (!call)
                     continue;
 
                 call.afterGCUse = usePoint;
                 return call;
@@ -785,25 +960,81 @@ function typeDesc(type)
     }
 }
 
 function processBodies(functionName)
 {
     if (!("DefineVariable" in functionBodies[0]))
         return;
     var suppressed = Boolean(limitedFunctions[mangled(functionName)] & LIMIT_CANNOT_GC);
-    for (var variable of functionBodies[0].DefineVariable) {
+
+    // Look for the JS_EXPECT_HAZARDS annotation, and output a different
+    // message in that case that won't be counted as a hazard.
+    var annotations = new Set();
+    for (const variable of functionBodies[0].DefineVariable) {
+        if (variable.Variable.Kind == "Func" && variable.Variable.Name[0] == functionName) {
+            for (const { Name: [tag, value] } of (variable.Type.Annotation || [])) {
+                if (tag == 'annotate')
+                    annotations.add(value);
+            }
+        }
+    }
+
+    var missingExpectedHazard = annotations.has("Expect Hazards");
+
+    // Awful special case, hopefully temporary:
+    //
+    // The DOM bindings code generator uses "holders" to externally root
+    // variables. So for example:
+    //
+    //       StringObjectRecordOrLong arg0;
+    //       StringObjectRecordOrLongArgument arg0_holder(arg0);
+    //       arg0_holder.TrySetToStringObjectRecord(cx, args[0]);
+    //       GC();
+    //       self->PassUnion22(cx, arg0);
+    //
+    // This appears to be a rooting hazard on arg0, but it is rooted by
+    // arg0_holder if you set it to any of its union types that requires
+    // rooting.
+    //
+    // Additionally, the holder may be reported as a hazard because it's not
+    // itself a Rooted or a subclass of AutoRooter; it contains a
+    // Maybe<RecordRooter<T>> that will get emplaced if rooting is required.
+    //
+    // Hopefully these will be simplified at some point (see bug 1517829), but
+    // for now we special-case functions in the mozilla::dom namespace that
+    // contain locals with types ending in "Argument". Or
+    // Maybe<SomethingArgument>. It's a harsh world.
+    const ignoreVars = new Set();
+    if (functionName.match(/mozilla::dom::/)) {
+        const vars = functionBodies[0].DefineVariable.filter(
+            v => v.Type.Kind == 'CSU' && v.Variable.Kind == 'Local'
+        ).map(
+            v => [ v.Variable.Name[0], v.Type.Name ]
+        );
+
+        const holders = vars.filter(([n, t]) => n.match(/^arg\d+_holder$/) && t.match(/Argument\b/));
+        for (const [holder,] of holders) {
+            ignoreVars.add(holder); // Ignore the older.
+            ignoreVars.add(holder.replace("_holder", "")); // Ignore the "managed" arg.
+        }
+    }
+
+    for (const variable of functionBodies[0].DefineVariable) {
         var name;
         if (variable.Variable.Kind == "This")
             name = "this";
         else if (variable.Variable.Kind == "Return")
             name = "<returnvalue>";
         else
             name = variable.Variable.Name[0];
 
+        if (ignoreVars.has(name))
+            continue;
+
         if (isRootedType(variable.Type)) {
             if (!variableLiveAcrossGC(suppressed, variable.Variable)) {
                 // The earliest use of the variable should be its constructor.
                 var lineText;
                 for (var body of functionBodies) {
                     if (body.minimumUse) {
                         var text = findLocation(body, body.minimumUse);
                         if (!lineText || locationLine(lineText) > locationLine(text))
@@ -812,33 +1043,56 @@ function processBodies(functionName)
                 }
                 print("\nFunction '" + functionName + "'" +
                       " has unnecessary root '" + name + "' at " + lineText);
             }
         } else if (isUnrootedType(variable.Type)) {
             var result = variableLiveAcrossGC(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.gcInfo.body, result.gcInfo.ppoint);
-                print("\nFunction '" + functionName + "'" +
-                      " has unrooted '" + name + "'" +
-                      " of type '" + typeDesc(variable.Type) + "'" +
-                      " live across GC call " + result.gcInfo.name +
-                      " at " + lineText);
+                if (annotations.has('Expect Hazards')) {
+                    print("\nThis is expected, but '" + functionName + "'" +
+                          " has unrooted '" + name + "'" +
+                          " of type '" + typeDesc(variable.Type) + "'" +
+                          " live across GC call " + result.gcInfo.name +
+                          " at " + lineText);
+                    missingExpectedHazard = false;
+                } else {
+                    print("\nFunction '" + functionName + "'" +
+                          " has unrooted '" + name + "'" +
+                          " of type '" + typeDesc(variable.Type) + "'" +
+                          " live across GC call " + result.gcInfo.name +
+                          " at " + lineText);
+                }
                 printEntryTrace(functionName, result);
             }
             result = unsafeVariableAddressTaken(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.body, result.ppoint);
                 print("\nFunction '" + functionName + "'" +
                       " takes unsafe address of unrooted '" + name + "'" +
                       " at " + lineText);
                 printEntryTrace(functionName, {body:result.body, ppoint:result.ppoint});
             }
         }
     }
+
+    if (missingExpectedHazard) {
+        const {
+            Location: [
+                { CacheString: startfile, Line: startline },
+                { CacheString: endfile, Line: endline }
+            ]
+        } = functionBodies[0];
+
+        const loc = (startfile == endfile) ? `${startfile}:${startline}-${endline}`
+              : `${startfile}:${startline}`;
+
+        print("\nFunction '" + functionName + "' expected hazard(s) but none were found at " + loc);
+    }
 }
 
 if (batch == 1)
     print("Time: " + new Date);
 
 var xdb = xdbLibrary();
 xdb.open("src_body.xdb");
 
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -7,16 +7,33 @@ var ignoreIndirectCalls = {
     "mallocSizeOf" : true,
     "aMallocSizeOf" : true,
     "__conv" : true,
     "__convf" : true,
     "prerrortable.c:callback_newtable" : true,
     "mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true,
 };
 
+// Types that when constructed with no arguments, are "safe" values (they do
+// not contain GC pointers).
+var typesWithSafeConstructors = new Set([
+    "mozilla::Maybe",
+    "mozilla::dom::Nullable",
+    "mozilla::dom::Optional",
+    "mozilla::UniquePtr",
+    "js::UniquePtr"
+]);
+
+var resetterMethods = {
+    'mozilla::Maybe': new Set(["reset"]),
+    'mozilla::UniquePtr': new Set(["reset"]),
+    'js::UniquePtr': new Set(["reset"]),
+    'mozilla::dom::Nullable': new Set(["SetNull"]),
+};
+
 function indirectCallCannotGC(fullCaller, fullVariable)
 {
     var caller = readable(fullCaller);
 
     // This is usually a simple variable name, but sometimes a full name gets
     // passed through. And sometimes that name is truncated. Examples:
     //   _ZL13gAbortHandler|mozalloc_oom.cpp:void (* gAbortHandler)(size_t)
     //   _ZL14pMutexUnlockFn|umutex.cpp:void (* pMutexUnlockFn)(const void*
--- a/js/src/devtools/rootAnalysis/computeGCTypes.js
+++ b/js/src/devtools/rootAnalysis/computeGCTypes.js
@@ -195,19 +195,19 @@ for (const csu of inheritors) {
         param = param.replace(/\s+$/, '')
         const pieces = param.split("*");
         const core_type = pieces[0];
         const ptrdness = pieces.length - 1;
         if (ptrdness > 1)
             continue;
         const paramDesc = 'template-param-' + param;
         const why = '(inherited annotations from ' + param + ')';
-        if (typeInfo.GCThings.indexOf(core_type) != -1)
+        if (core_type in gcTypes)
             markGCType(csu, paramDesc, why, ptrdness, 0, "");
-        if (typeInfo.GCPointers.indexOf(core_type) != -1)
+        if (core_type in gcPointers)
             markGCType(csu, paramDesc, why, ptrdness + 1, 0, "");
     }
 }
 
 // Everything that inherits from a "Rooted Base" is considered to be rooted.
 // This is for things like CustomAutoRooter and its subclasses.
 var basework = Object.keys(typeInfo.RootedBases);
 while (basework.length) {
--- a/js/src/devtools/rootAnalysis/explain.py
+++ b/js/src/devtools/rootAnalysis/explain.py
@@ -12,31 +12,36 @@ parser.add_argument('rootingHazards', na
 parser.add_argument('gcFunctions', nargs='?', default='gcFunctions.txt')
 parser.add_argument('hazards', nargs='?', default='hazards.txt')
 parser.add_argument('extra', nargs='?', default='unnecessary.txt')
 parser.add_argument('refs', nargs='?', default='refs.txt')
 args = parser.parse_args()
 
 num_hazards = 0
 num_refs = 0
+num_missing = 0
+
 try:
     with open(args.rootingHazards) as rootingHazards, \
             open(args.hazards, 'w') as hazards, \
             open(args.extra, 'w') as extra, \
             open(args.refs, 'w') as refs:
         current_gcFunction = None
 
         # Map from a GC function name to the list of hazards resulting from
         # that GC function
         hazardousGCFunctions = defaultdict(list)
 
         # List of tuples (gcFunction, index of hazard) used to maintain the
         # ordering of the hazards
         hazardOrder = []
 
+        # Map from a hazardous GC function to the filename containing it.
+        fileOfFunction = {}
+
         for line in rootingHazards:
             m = re.match(r'^Time: (.*)', line)
             mm = re.match(r'^Run on:', line)
             if m or mm:
                 print(line, file=hazards)
                 print(line, file=extra)
                 print(line, file=refs)
                 continue
@@ -48,25 +53,32 @@ try:
 
             m = re.match(r'^Function.*takes unsafe address of unrooted', line)
             if m:
                 num_refs += 1
                 print(line, file=refs)
                 continue
 
             m = re.match(
-                r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at \S+:\d+$", line)  # NOQA: E501
+                r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at (\S+):\d+$", line)  # NOQA: E501
             if m:
                 # Function names are surrounded by single quotes. Field calls
                 # are unquoted.
                 current_gcFunction = m.group(2)
                 hazardousGCFunctions[current_gcFunction].append(line)
                 hazardOrder.append((current_gcFunction,
                                     len(hazardousGCFunctions[current_gcFunction]) - 1))
                 num_hazards += 1
+                fileOfFunction[current_gcFunction] = m.group(4)
+                continue
+
+            m = re.match(r'Function.*expected hazard.*but none were found', line)
+            if m:
+                num_missing += 1
+                print(line + "\n", file=hazards)
                 continue
 
             if current_gcFunction:
                 if not line.strip():
                     # Blank line => end of this hazard
                     current_gcFunction = None
                 else:
                     hazardousGCFunctions[current_gcFunction][-1] += line
@@ -99,9 +111,9 @@ try:
                 print(gcHazards[index], file=hazards)
 
 except IOError as e:
     print('Failed: %s' % str(e))
 
 print("Wrote %s" % args.hazards)
 print("Wrote %s" % args.extra)
 print("Wrote %s" % args.refs)
-print("Found %d hazards and %d unsafe references" % (num_hazards, num_refs))
+print("Found %d hazards %d unsafe references %d missing" % (num_hazards, num_refs, num_missing))
old mode 100644
new mode 100755
--- a/js/src/devtools/rootAnalysis/run-test.py
+++ b/js/src/devtools/rootAnalysis/run-test.py
@@ -104,13 +104,13 @@ for name in cfg.tests:
         print("Running test %s" % name)
     testpath = os.path.join(indir, "test.py")
     testscript = open(testpath).read()
     testcode = compile(testscript, testpath, 'exec')
     try:
         exec(testcode, {'test': test, 'equal': equal})
     except subprocess.CalledProcessError:
         print("TEST-FAILED: %s" % name)
-    except StandardError:
+    except AssertionError:
         print("TEST-FAILED: %s" % name)
         raise
     else:
         print("TEST-PASSED: %s" % name)
--- a/js/src/devtools/rootAnalysis/t/exceptions/source.cpp
+++ b/js/src/devtools/rootAnalysis/t/exceptions/source.cpp
@@ -27,20 +27,22 @@ class AutoSomething {
 
  public:
   AutoSomething() : gc() {
     asm("");  // Ooh, scary, this might throw an exception
   }
   ~AutoSomething() { asm(""); }
 };
 
+extern Cell* getcell();
+
 extern void usevar(Cell* cell);
 
 void f() {
-  Cell* thing = nullptr;  // Live range starts here
+  Cell* thing = getcell();  // Live range starts here
 
   // When compiling with -fexceptions, there should be a hazard below. With
   // -fno-exceptions, there should not be one. We will check both.
   {
     AutoSomething smth;  // Constructor can GC only if exceptions are enabled
     usevar(thing);       // Live range ends here
   }  // In particular, 'thing' is dead at the destructor, so no hazard
 }
--- a/js/src/devtools/rootAnalysis/t/hazards/source.cpp
+++ b/js/src/devtools/rootAnalysis/t/hazards/source.cpp
@@ -1,8 +1,10 @@
+#include <utility>
+
 #define ANNOTATE(property) __attribute__((annotate(property)))
 
 struct Cell {
   int f;
 } ANNOTATE("GC Thing");
 
 template <typename T, typename U>
 struct UntypedContainer {
@@ -202,8 +204,105 @@ void loopy() {
   // CellContainer;
   CellContainer haz8;
   for (int i6 = 0; i6 < 10; i6++) {
     GC();
     use(haz8.cell);
     haz8.cell = &cell;
   }
 }
+
+namespace mozilla {
+template<typename T>
+class UniquePtr
+{
+  T* val;
+ public:
+  UniquePtr() : val(nullptr) { asm(""); }
+  UniquePtr(T* p) : val(p) {}
+  UniquePtr(UniquePtr<T>&& u) : val(u.val) { u.val = nullptr; }
+  ~UniquePtr() { use(val); }
+  T* get() { return val; }
+  void reset() { val = nullptr; }
+} ANNOTATE("moz_inherit_type_annotations_from_template_args");
+} // namespace mozilla
+
+extern void consume(mozilla::UniquePtr<Cell> uptr);
+
+void safevals() {
+  Cell cell;
+
+  // Simple hazard.
+  Cell* unsafe1 = &cell;
+  GC();
+  use(unsafe1);
+
+  // Safe because it's known to be nullptr.
+  Cell* safe2 = &cell;
+  safe2 = nullptr;
+  GC();
+  use(safe2);
+
+  // Unsafe because it may not be nullptr.
+  Cell* unsafe3 = &cell;
+  if (reinterpret_cast<long>(&cell) & 0x100) {
+    unsafe3 = nullptr;
+  }
+  GC();
+  use(unsafe3);
+
+  // Unsafe because it's not nullptr anymore.
+  Cell* unsafe3b = &cell;
+  unsafe3b = nullptr;
+  unsafe3b = &cell;
+  GC();
+  use(unsafe3b);
+
+  // Hazard involving UniquePtr.
+  {
+    mozilla::UniquePtr<Cell> unsafe4(&cell);
+    GC();
+    // Destructor uses unsafe4.
+  }
+
+  // reset() to safe value before the GC.
+  {
+    mozilla::UniquePtr<Cell> safe5(&cell);
+    safe5.reset();
+    GC();
+  }
+
+  // reset() to safe value after the GC.
+  {
+    mozilla::UniquePtr<Cell> safe6(&cell);
+    GC();
+    safe6.reset();
+  }
+
+  // reset() to safe value after the GC -- but we've already used it, so it's
+  // too late.
+  {
+    mozilla::UniquePtr<Cell> unsafe7(&cell);
+    GC();
+    use(unsafe7.get());
+    unsafe7.reset();
+  }
+
+  // initialized to safe value.
+  {
+    mozilla::UniquePtr<Cell> safe8;
+    GC();
+  }
+
+  // passed to a function that takes ownership before GC.
+  {
+    mozilla::UniquePtr<Cell> safe9(&cell);
+    consume(std::move(safe9));
+    GC();
+  }
+
+  // passed to a function that takes ownership after GC.
+  {
+    mozilla::UniquePtr<Cell> unsafe10(&cell);
+    GC();
+    consume(std::move(unsafe10));
+  }
+}
--- a/js/src/devtools/rootAnalysis/t/hazards/test.py
+++ b/js/src/devtools/rootAnalysis/t/hazards/test.py
@@ -17,20 +17,20 @@ hazmap = {haz.variable: haz for haz in h
 assert('cell1' not in hazmap)
 assert('cell2' in hazmap)
 assert('cell3' in hazmap)
 assert('cell4' not in hazmap)
 assert('cell5' not in hazmap)
 assert('cell6' not in hazmap)
 assert('<returnvalue>' in hazmap)
 
-# All hazards should be in f() and loopy()
+# All hazards should be in f(), loopy(), and safevals()
 assert(hazmap['cell2'].function == 'Cell* f()')
 print(len(set(haz.function for haz in hazards)))
-assert(len(set(haz.function for haz in hazards)) == 2)
+assert(len(set(haz.function for haz in hazards)) == 3)
 
 # Check that the correct GC call is reported for each hazard. (cell3 has a
 # hazard from two different GC calls; it doesn't really matter which is
 # reported.)
 assert(hazmap['cell2'].GCFunction == 'void halfSuppressedFunction()')
 assert(hazmap['cell3'].GCFunction in (
     'void halfSuppressedFunction()', 'void unsuppressedFunction()'))
 assert(hazmap['<returnvalue>'].GCFunction == 'void GCInDestructor::~GCInDestructor()')
@@ -46,8 +46,21 @@ assert(hazmap['<returnvalue>'].type == '
 assert('haz1' not in hazmap)
 assert('haz2' not in hazmap)
 assert('haz3' in hazmap)
 assert('haz4' in hazmap)
 assert('haz5' in hazmap)
 assert('haz6' not in hazmap)
 assert('haz7' not in hazmap)
 assert('haz8' in hazmap)
+
+# safevals hazards. See comments in source.
+assert('unsafe1' in hazmap)
+assert('safe2' not in hazmap)
+assert('unsafe3' in hazmap)
+assert('unsafe3b' in hazmap)
+assert('unsafe4' in hazmap)
+assert('safe5' not in hazmap)
+assert('safe6' not in hazmap)
+assert('unsafe7' in hazmap)
+assert('safe8' not in hazmap)
+assert('safe9' not in hazmap)
+assert('safe10' not in hazmap)
--- a/js/src/jsapi-tests/testGCExactRooting.cpp
+++ b/js/src/jsapi-tests/testGCExactRooting.cpp
@@ -159,16 +159,55 @@ BEGIN_TEST(testGCRootedHashMap) {
     RootedObject obj(cx, r.front().value());
     CHECK(obj->as<NativeObject>().lastProperty() == r.front().key());
   }
 
   return true;
 }
 END_TEST(testGCRootedHashMap)
 
+// Repeat of the test above, but without rooting. This is a rooting hazard. The
+// JS_EXPECT_HAZARDS annotation will cause the hazard taskcluster job to fail
+// if the hazard below is *not* detected.
+BEGIN_TEST_WITH_ATTRIBUTES(testUnrootedGCHashMap,JS_EXPECT_HAZARDS) {
+  MyHashMap map(cx, 15);
+
+  for (size_t i = 0; i < 10; ++i) {
+    RootedObject obj(cx, JS_NewObject(cx, nullptr));
+    RootedValue val(cx, UndefinedValue());
+    // Construct a unique property name to ensure that the object creates a
+    // new shape.
+    char buffer[2];
+    buffer[0] = 'a' + i;
+    buffer[1] = '\0';
+    CHECK(JS_SetProperty(cx, obj, buffer, val));
+    CHECK(map.putNew(obj->as<NativeObject>().lastProperty(), obj));
+  }
+
+  JS_GC(cx);
+
+  // Access map to keep it live across the GC.
+  CHECK(map.count() == 10);
+
+  return true;
+}
+END_TEST(testUnrootedGCHashMap)
+
+BEGIN_TEST(testSafelyUnrootedGCHashMap) {
+  // This is not rooted, but it doesn't use GC pointers as keys or values so
+  // it's ok.
+  js::GCHashMap<uint64_t, uint64_t> map(cx, 15);
+
+  JS_GC(cx);
+  CHECK(map.putNew(12, 13));
+
+  return true;
+}
+END_TEST(testSafelyUnrootedGCHashMap)
+
 static bool FillMyHashMap(JSContext* cx, MutableHandle<MyHashMap> map) {
   for (size_t i = 0; i < 10; ++i) {
     RootedObject obj(cx, JS_NewObject(cx, nullptr));
     RootedValue val(cx, UndefinedValue());
     // Construct a unique property name to ensure that the object creates a
     // new shape.
     char buffer[2];
     buffer[0] = 'a' + i;
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -348,21 +348,23 @@ class JSAPITest {
             (unsigned int)report->lineno, report->message().c_str());
   }
 
   virtual const JSClass* getGlobalClass() { return basicGlobalClass(); }
 
   virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
 };
 
-#define BEGIN_TEST(testname)                                  \
+#define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs)           \
   class cls_##testname : public JSAPITest {                   \
    public:                                                    \
     virtual const char* name() override { return #testname; } \
-    virtual bool run(JS::HandleObject global) override
+    virtual bool run(JS::HandleObject global) override attrs
+
+#define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
 
 #define END_TEST(testname) \
   }                        \
   ;                        \
   static cls_##testname cls_##testname##_instance;
 
 /*
  * A "fixture" is a subclass of JSAPITest that holds common definitions for a
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -363,17 +363,17 @@ fuzzy-if(OSX,0-1,0-100) fuzzy-if(skiaCon
 == mfrac-C-3.html mfrac-C-3-ref.html
 == mfrac-C-4.html mfrac-C-4-ref.html
 fuzzy-if(OSX,0-1,0-100) fuzzy-if(skiaContent,0-1,0-14) == mfrac-D-1.html mfrac-D-1-ref.html
 == mfrac-D-2.html mfrac-D-2-ref.html
 == mfrac-D-3.html mfrac-D-3-ref.html
 == mfrac-D-4.html mfrac-D-4-ref.html
 == mfrac-E-1.html mfrac-E-1-ref.html
 == shadow-dom-1.html shadow-dom-1-ref.html
-pref(dom.meta-viewport.enabled,true) pref(font.size.inflation.emPerLine,25) fuzzy-if(webrender&&!gtkWidget,0-255,0-244) == font-inflation-1.html font-inflation-1-ref.html
+pref(dom.meta-viewport.enabled,true) pref(font.size.inflation.emPerLine,25) fuzzy-if(webrender&&!gtkWidget,0-255,0-260) == font-inflation-1.html font-inflation-1-ref.html
 test-pref(font.minimum-size.x-math,40) == default-font.html default-font-ref.html
 != radicalbar-1.html about:blank
 != radicalbar-1a.html about:blank
 != radicalbar-1b.html about:blank
 != radicalbar-1c.html about:blank
 != radicalbar-1d.html about:blank
 != radicalbar-2.html about:blank
 != radicalbar-2a.html about:blank
--- a/layout/reftests/scrolling/reftest.list
+++ b/layout/reftests/scrolling/reftest.list
@@ -30,17 +30,17 @@ fuzzy-if(Android,0-4,0-120) HTTP == text
 fuzzy-if(d2d,0-1,0-4) fuzzy-if(webrender,0-1,0-42) HTTP == transformed-1.html transformed-1.html?ref
 fuzzy-if(webrender,0-1,0-43) HTTP == transformed-1.html?up transformed-1.html?ref
 fuzzy-if(Android,0-5,0-20000) == uncovering-1.html uncovering-1-ref.html
 fuzzy-if(Android,0-5,0-20000) == uncovering-2.html uncovering-2-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,0-149,0-4520) == less-than-scrollbar-height.html less-than-scrollbar-height-ref.html
 == huge-horizontal-overflow.html huge-horizontal-overflow-ref.html
 == huge-vertical-overflow.html huge-vertical-overflow-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,0-102,0-6818) == iframe-scrolling-attr-1.html iframe-scrolling-attr-ref.html
-fuzzy-if(asyncPan&&!layersGPUAccelerated,0-140,0-6818) == iframe-scrolling-attr-2.html iframe-scrolling-attr-ref.html
+fuzzy-if(gtkWidget,0-1,0-1) fuzzy-if(asyncPan&&!layersGPUAccelerated,0-140,0-6818) == iframe-scrolling-attr-2.html iframe-scrolling-attr-ref.html
 fuzzy(0-1,0-2) == frame-scrolling-attr-1.html frame-scrolling-attr-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,0-102,0-2420) == frame-scrolling-attr-2.html frame-scrolling-attr-ref.html
 == move-item.html move-item-ref.html # bug 1125750
 == fractional-scroll-area.html?top=-0.4&outerBottom=100&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
 == fractional-scroll-area.html?top=0.4&outerBottom=100&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
 == fractional-scroll-area.html?top=0&outerBottom=99.6&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
 == fractional-scroll-area.html?top=0&outerBottom=100.4&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
 == fractional-scroll-area.html?top=-0.4&outerBottom=99.6&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
--- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc
+++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_receiver_impl.cc
@@ -24,17 +24,17 @@
 #include "modules/rtp_rtcp/include/rtp_payload_registry.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "modules/rtp_rtcp/source/rtp_receiver_strategy.h"
 #include "rtc_base/logging.h"
 
 namespace webrtc {
 
 namespace {
-bool InOrderPacket(rtc::Optional<uint16_t> latest_sequence_number,
+bool InOrderPacket(const rtc::Optional<uint16_t>& latest_sequence_number,
                    uint16_t current_sequence_number) {
   if (!latest_sequence_number)
     return true;
 
   // We need to distinguish between a late or retransmitted packet,
   // and a sequence number discontinuity.
   if (IsNewerSequenceNumber(current_sequence_number, *latest_sequence_number)) {
     return true;
--- a/taskcluster/scripts/builder/hazard-analysis.sh
+++ b/taskcluster/scripts/builder/hazard-analysis.sh
@@ -154,35 +154,42 @@ function grab_artifacts () {
 function check_hazards () {
     (
     set +e
     NUM_HAZARDS=$(grep -c 'Function.*has unrooted.*live across GC call' "$1"/rootingHazards.txt)
     NUM_UNSAFE=$(grep -c '^Function.*takes unsafe address of unrooted' "$1"/refs.txt)
     NUM_UNNECESSARY=$(grep -c '^Function.* has unnecessary root' "$1"/unnecessary.txt)
     NUM_DROPPED=$(grep -c '^Dropped CFG' "$1"/build_xgill.log)
     NUM_WRITE_HAZARDS=$(perl -lne 'print $1 if m!found (\d+)/\d+ allowed errors!' "$1"/heapWriteHazards.txt)
+    NUM_MISSING=$(grep -c '^Function.*expected hazard.*but none were found' "$1"/rootingHazards.txt)
 
     set +x
     echo "TinderboxPrint: rooting hazards<br/>$NUM_HAZARDS"
     echo "TinderboxPrint: (unsafe references to unrooted GC pointers)<br/>$NUM_UNSAFE"
     echo "TinderboxPrint: (unnecessary roots)<br/>$NUM_UNNECESSARY"
+    echo "TinderboxPrint: missing expected hazards<br/>$NUM_MISSING"
     echo "TinderboxPrint: heap write hazards<br/>$NUM_WRITE_HAZARDS"
 
     # Display errors in a way that will get picked up by the taskcluster scraper.
-    perl -le 'print "TEST-UNEXPECTED-FAIL | hazards | $ENV{NUM_HAZARDS} rooting hazards" if $ENV{NUM_HAZARDS}'
     perl -lne 'print "TEST-UNEXPECTED-FAIL | hazards | $1 $2" if /^Function.* has (unrooted .*live across GC call).* (at .*)$/' "$1"/hazards.txt
 
     exit_status=0
 
     if [ $NUM_HAZARDS -gt 0 ]; then
         echo "TEST-UNEXPECTED-FAIL | hazards | $NUM_HAZARDS rooting hazards detected" >&2
         echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_rooting_hazards_failure'>static rooting hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
         exit_status=1
     fi
 
+    if [ $NUM_MISSING -gt 0 ]; then
+        echo "TEST-UNEXPECTED-FAIL | hazards | $NUM_MISSING expected hazards went undetected" >&2
+        echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_rooting_hazards_failure'>static rooting hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
+        exit_status=1
+    fi
+
     NUM_ALLOWED_WRITE_HAZARDS=0
     if [ $NUM_WRITE_HAZARDS -gt $NUM_ALLOWED_WRITE_HAZARDS ]; then
         echo "TEST-UNEXPECTED-FAIL | heap-write-hazards | $NUM_WRITE_HAZARDS heap write hazards detected out of $NUM_ALLOWED_WRITE_HAZARDS allowed" >&2
         echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_heap_write_hazard_failure'>heap write hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
         exit_status = 1
     fi
 
     if [ $NUM_DROPPED -gt 0 ]; then
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -990,17 +990,21 @@
                      this._rlbPadding;
           }
 
           let currentHeight = this.richlistbox.getBoundingClientRect().height;
           if (height <= currentHeight) {
             this._collapseUnusedItems();
           }
           this.richlistbox.style.removeProperty("height");
-          this.richlistbox.height = height;
+          // We need to get the ceiling of the calculated value to ensure that the box fully contains
+          // all of its contents and doesn't cause a scrollbar since nsIBoxObject only expects a
+          // `long`. e.g. if `height` is 99.5 the richlistbox would render at height 99px with a
+          // scrollbar for the extra 0.5px.
+          this.richlistbox.height = Math.ceil(height);
           ]]>
         </body>
       </method>
 
       <method name="_appendCurrentResult">
         <parameter name="invalidateReason"/>
         <body>
           <![CDATA[
--- a/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
@@ -49,16 +49,20 @@ async function checkInstallConfirmation(
 
 add_task(async function test_install_from_file() {
   gManagerWindow = await open_manager("addons://list/extension");
 
   var filePaths = [
                    get_addon_file_url("browser_dragdrop1.xpi"),
                    get_addon_file_url("browser_dragdrop2.xpi"),
                   ];
+  for (let uri of filePaths) {
+    ok(uri.file != null, `Should have file for ${uri.spec}`);
+    ok(uri.file instanceof Ci.nsIFile, `Should have nsIFile for ${uri.spec}`);
+  }
   MockFilePicker.setFiles(filePaths.map(aPath => aPath.file));
 
   // Set handler that executes the core test after the window opens,
   // and resolves the promise when the window closes
   let pInstallURIClosed = checkInstallConfirmation("Drag Drop test 1", "Drag Drop test 2");
 
   gManagerWindow.gViewController.doCommand("cmd_installFromFile");
 
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -81,16 +81,18 @@ typedef struct _nsCocoaWindowList {
 
 - (void)disableSetNeedsDisplay;
 - (void)enableSetNeedsDisplay;
 
 - (NSRect)getAndResetNativeDirtyRect;
 
 - (void)setUseMenuStyle:(BOOL)aValue;
 
+- (void)releaseJSObjects;
+
 @end
 
 @interface NSWindow (Undocumented)
 
 // If a window has been explicitly removed from the "window cache" (to
 // deactivate it), it's sometimes necessary to "reset" it to reactivate it
 // (and put it back in the "window cache").  One way to do this, which Apple
 // often uses, is to set the "window number" to '-1' and then back to its
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -160,16 +160,17 @@ nsCocoaWindow::nsCocoaWindow()
   }
 }
 
 void nsCocoaWindow::DestroyNativeWindow() {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (!mWindow) return;
 
+  [mWindow releaseJSObjects];
   // We want to unhook the delegate here because we don't want events
   // sent to it after this object has been destroyed.
   [mWindow setDelegate:nil];
   [mWindow close];
   mWindow = nil;
   [mDelegate autorelease];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
@@ -3116,16 +3117,20 @@ static const NSString* kStateCollectionB
     retval = [NSArray arrayWithArray:holder];
   }
 
   return retval;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
+- (void)releaseJSObjects {
+  [mTouchBar releaseJSObjects];
+}
+
 @end
 
 // This class allows us to exercise control over the window's title bar. This
 // allows for a "unified toolbar" look without having to extend the content
 // area into the title bar.
 //
 // Drawing the unified gradient in the titlebar and the toolbar works like this:
 // 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
--- a/widget/cocoa/nsTouchBar.h
+++ b/widget/cocoa/nsTouchBar.h
@@ -168,11 +168,13 @@ typedef NSString* NSTouchBarItemIdentifi
                             sharingServicesForItems:(NSArray*)aItems
                             proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices;
 
 /**
  * Retrieves TouchBarInput icons.
  */
 + (NSImage*)getTouchBarIconNamed:(NSString*)aImageName;
 
+- (void)releaseJSObjects;
+
 @end  // nsTouchBar
 
 #endif  // nsTouchBar_h_
--- a/widget/cocoa/nsTouchBar.mm
+++ b/widget/cocoa/nsTouchBar.mm
@@ -187,16 +187,20 @@ static char sIdentifierAssociationKey;
   if (!input) {
     return;
   }
 
   nsCOMPtr<nsITouchBarInputCallback> callback = [input callback];
   callback->OnCommand();
 }
 
+- (void)releaseJSObjects {
+  mTouchBarHelper = nil;
+}
+
 #pragma mark - TouchBar Utilities
 
 + (NSImage*)getTouchBarIconNamed:(NSString*)aImageName {
   nsCOMPtr<nsIFile> resDir;
   nsAutoCString resPath;
   NSString* pathToImage;
 
   nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));