Merge mozilla-central to inbound. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Tue, 02 Oct 2018 01:05:49 +0300
changeset 494822 47279941c952461a85038d1345c8aad0931c4d3f
parent 494821 868e0b4673af81a6c17998ef269d76d6514b1ca3 (current diff)
parent 494752 856103837d4dda0e176706cc64927546459d510c (diff)
child 494823 6394f3198d88f4db82bf5333aaaad66a57a00899
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
--- a/browser/components/extensions/child/ext-menus.js
+++ b/browser/components/extensions/child/ext-menus.js
@@ -169,21 +169,16 @@ this.menusInternal = class extends Exten
 
         removeAll() {
           onClickedProp.deleteAllListenersFromExtension();
 
           return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
         },
 
         overrideContext(contextOptions) {
-          let {event} = context.contentWindow;
-          if (!event || event.type !== "contextmenu" || !event.isTrusted) {
-            throw new ExtensionError("overrideContext must be called during a \"contextmenu\" event");
-          }
-
           let checkValidArg = (contextType, propKey) => {
             if (contextOptions.context !== contextType) {
               if (contextOptions[propKey]) {
                 throw new ExtensionError(`Property "${propKey}" can only be used with context "${contextType}"`);
               }
               return false;
             }
             if (contextOptions.showDefaults) {
@@ -218,22 +213,26 @@ this.menusInternal = class extends Exten
             pendingMenuEvent.webExtContextData = webExtContextData;
             return;
           }
           pendingMenuEvent = {
             webExtContextData,
             observe(subject, topic, data) {
               pendingMenuEvent = null;
               Services.obs.removeObserver(this, "on-prepare-contextmenu");
-              subject.wrappedJSObject.webExtContextData = this.webExtContextData;
+              subject = subject.wrappedJSObject;
+              if (context.principal.subsumes(subject.context.principal)) {
+                subject.webExtContextData = this.webExtContextData;
+              }
             },
             run() {
               // "on-prepare-contextmenu" is expected to be observed before the
               // end of the "contextmenu" event dispatch. This task is queued
               // in case that does not happen, e.g. when the menu is not shown.
+              // ... or if the method was not called during a contextmenu event.
               if (pendingMenuEvent === this) {
                 pendingMenuEvent = null;
                 Services.obs.removeObserver(this, "on-prepare-contextmenu");
               }
             },
           };
           Services.obs.addObserver(pendingMenuEvent, "on-prepare-contextmenu");
           Services.tm.dispatchToMainThread(pendingMenuEvent);
--- a/browser/components/extensions/parent/ext-devtools.js
+++ b/browser/components/extensions/parent/ext-devtools.js
@@ -29,34 +29,33 @@ function getDevToolsPrefBranchName(exten
  *
  * @param {DevToolsExtensionPageContextParent} context
  *   A devtools extension proxy context.
  *
  * @returns {Promise<TabTarget>}
  *   The cloned devtools target associated to the context.
  */
 global.getDevToolsTargetForContext = async (context) => {
-  if (context.devToolsTarget) {
-    await context.devToolsTarget.attach();
-    return context.devToolsTarget;
-  }
+  if (!context.devToolsTargetPromise) {
+    if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
+      throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
+    }
 
-  if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
-    throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
+    if (!context.devToolsToolbox.target.isLocalTab) {
+      throw new Error("Unexpected target type: only local tabs are currently supported.");
+    }
+
+    const tab = context.devToolsToolbox.target.tab;
+    context.devToolsTargetPromise = DevToolsShim.createTargetForTab(tab);
   }
 
-  if (!context.devToolsToolbox.target.isLocalTab) {
-    throw new Error("Unexpected target type: only local tabs are currently supported.");
-  }
+  const target = await context.devToolsTargetPromise;
+  await target.attach();
 
-  const tab = context.devToolsToolbox.target.tab;
-  context.devToolsTarget = await DevToolsShim.createTargetForTab(tab);
-  await context.devToolsTarget.attach();
-
-  return context.devToolsTarget;
+  return target;
 };
 
 /**
  * Retrieve the devtools target for the devtools extension proxy context
  * (lazily cloned from the target of the toolbox associated to the context
  * the first time that it is accessed).
  *
  * @param {Toolbox} toolbox
--- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js
@@ -18,23 +18,35 @@ function checkIsDefaultMenuItemVisible(v
 // - Calling overrideContext({}) during oncontextmenu forces the menu to only
 //   show an extension's own items.
 // - These menu items all appear in the root menu.
 // - The usual extension filtering behavior (e.g. documentUrlPatterns and
 //   targetUrlPatterns) is still applied; some menu items are therefore hidden.
 // - Calling overrideContext({showDefaults:true}) causes the default menu items
 //   to be shown, but only after the extension's.
 // - overrideContext expires after the menu is opened once.
+// - overrideContext can be called from shadow DOM.
 add_task(async function overrideContext_in_extension_tab() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["dom.webcomponents.shadowdom.enabled", true]],
+  });
+
   function extensionTabScript() {
     document.addEventListener("contextmenu", () => {
       browser.menus.overrideContext({});
       browser.test.sendMessage("oncontextmenu_in_dom_part_1");
     }, {once: true});
 
+    let shadowRoot = document.getElementById("shadowHost").attachShadow({mode: "open"});
+    shadowRoot.innerHTML = `<a href="http://example.com/">Link</a>`;
+    shadowRoot.firstChild.addEventListener("contextmenu", () => {
+      browser.menus.overrideContext({});
+      browser.test.sendMessage("oncontextmenu_in_shadow_dom");
+    }, {once: true});
+
     browser.menus.create({
       id: "tab_1",
       title: "tab_1",
       documentUrlPatterns: [document.URL],
       onclick() {
         document.addEventListener("contextmenu", () => {
           // Verifies that last call takes precedence.
           browser.menus.overrideContext({showDefaults: false});
@@ -58,31 +70,33 @@ add_task(async function overrideContext_
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["menus", "menus.overrideContext"],
     },
     files: {
       "tab.html": `
         <!DOCTYPE html><meta charset="utf-8">
         <a href="http://example.com/">Link</a>
+        <div id="shadowHost"></div>
         <script src="tab.js"></script>
       `,
       "tab.js": extensionTabScript,
     },
     background() {
       // Expected to match and thus be visible.
       browser.menus.create({id: "bg_1", title: "bg_1"});
       browser.menus.create({id: "bg_2", title: "bg_2", targetUrlPatterns: ["*://example.com/*"]});
 
       // Expected to not match and be hidden.
       browser.menus.create({id: "bg_3", title: "bg_3", targetUrlPatterns: ["*://nomatch/*"]});
       browser.menus.create({id: "bg_4", title: "bg_4", documentUrlPatterns: [document.URL]});
 
       browser.menus.onShown.addListener(info => {
         browser.test.assertEq("bg_1,bg_2,tab_1,tab_2", info.menuIds.join(","), "Expected menu items.");
+        browser.test.assertEq("all,link", info.contexts.sort().join(","), "Expected menu contexts");
         browser.test.sendMessage("onShown");
       });
 
       browser.tabs.create({url: "tab.html"});
     },
   });
 
   let otherExtension = ExtensionTestUtils.loadExtension({
@@ -173,16 +187,30 @@ add_task(async function overrideContext_
     Assert.deepEqual(
       getVisibleChildrenIds(submenu),
       EXPECTED_EXTENSION_MENU_IDS,
       "Extension menu items should be in the submenu by default.");
 
     await closeContextMenu();
   }
 
+  {
+    // Tests that overrideContext({}) can be used from a listener inside shadow DOM.
+    let menu = await openContextMenu(() => content.document.getElementById("shadowHost").shadowRoot.firstChild);
+    await extension.awaitMessage("oncontextmenu_in_shadow_dom");
+    await extension.awaitMessage("onShown");
+
+    Assert.deepEqual(
+      getVisibleChildrenIds(menu),
+      EXPECTED_EXTENSION_MENU_IDS,
+      "Expected only extension menu items after overrideContext({}) in shadow DOM");
+
+    await closeContextMenu();
+  }
+
   // Unloading the extension will automatically close the extension's tab.html
   await extension.unload();
   await otherExtension.unload();
 });
 
 // Tests some edge cases:
 // - overrideContext() is called without any menu registrations,
 //   followed by a menu registration + menus.refresh..
@@ -263,20 +291,16 @@ add_task(async function overrideContext_
       `,
       "sidebar.js": sidebarJs,
     },
     background() {
       browser.test.assertThrows(
         () => { browser.menus.overrideContext({someInvalidParameter: true}); },
         /Unexpected property "someInvalidParameter"/,
         "overrideContext should be available and the parameters be validated.");
-      browser.test.assertThrows(
-        () => { browser.menus.overrideContext({}); },
-        /overrideContext must be called during a "contextmenu" event/,
-        "overrideContext should fail outside of a 'contextmenu' event.");
       browser.test.sendMessage("bg_test_done");
     },
   });
 
   await extension.startup();
   await extension.awaitMessage("bg_test_done");
   await extension.awaitMessage("sidebar_ready");
 
--- 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.0.866
+Current extension version is: 2.0.892
 
-Taken from upstream commit: 0e41eb16
+Taken from upstream commit: ec10cae5
--- 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.0.866';
-var pdfjsBuild = '0e41eb16';
+var pdfjsVersion = '2.0.892';
+var pdfjsBuild = 'ec10cae5';
 var pdfjsSharedUtil = __w_pdfjs_require__(1);
 var pdfjsDisplayAPI = __w_pdfjs_require__(7);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(19);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(20);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(8);
 var pdfjsDisplaySVG = __w_pdfjs_require__(21);
 let pdfjsDisplayWorkerOptions = __w_pdfjs_require__(13);
 let pdfjsDisplayAPICompatibility = __w_pdfjs_require__(10);
@@ -4221,17 +4221,17 @@ function _fetchDocument(worker, source, 
     return Promise.reject(new Error('Worker was destroyed'));
   }
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
   }
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.0.866',
+    apiVersion: '2.0.892',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -5548,18 +5548,18 @@ var InternalRenderTask = function Intern
         }
       });
     }
   };
   return InternalRenderTask;
 }();
 var version, build;
 {
-  exports.version = version = '2.0.866';
-  exports.build = build = '0e41eb16';
+  exports.version = version = '2.0.892';
+  exports.build = build = 'ec10cae5';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamFactory = setPDFNetworkStreamFactory;
--- 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";
 
 
-var pdfjsVersion = '2.0.866';
-var pdfjsBuild = '0e41eb16';
+var pdfjsVersion = '2.0.892';
+var pdfjsBuild = 'ec10cae5';
 var pdfjsCoreWorker = __w_pdfjs_require__(1);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -322,17 +322,17 @@ var WorkerMessageHandler = {
     });
   },
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.0.866';
+    let workerVersion = '2.0.892';
     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';
     var handler = new _message_handler.MessageHandler(workerHandlerName, docId, port);
     handler.postMessageTransfers = docParams.postMessageTransfers;
@@ -20440,28 +20440,28 @@ var PartialEvaluator = function PartialE
         glyphName = encoding[charcode];
         if (glyphName === '') {
           continue;
         } else if (glyphsUnicodeMap[glyphName] === undefined) {
           let code = 0;
           switch (glyphName[0]) {
             case 'G':
               if (glyphName.length === 3) {
-                code = parseInt(glyphName.substr(1), 16);
+                code = parseInt(glyphName.substring(1), 16);
               }
               break;
             case 'g':
               if (glyphName.length === 5) {
-                code = parseInt(glyphName.substr(1), 16);
+                code = parseInt(glyphName.substring(1), 16);
               }
               break;
             case 'C':
             case 'c':
               if (glyphName.length >= 3) {
-                code = +glyphName.substr(1);
+                code = +glyphName.substring(1);
               }
               break;
             default:
               let unicode = (0, _unicode.getUnicodeForGlyph)(glyphName, glyphsUnicodeMap);
               if (unicode !== -1) {
                 code = unicode;
               }
           }
@@ -21609,17 +21609,17 @@ class CMap {
     while (low <= high) {
       this._map[low++] = dstLow++;
     }
   }
   mapBfRange(low, high, dstLow) {
     var lastByte = dstLow.length - 1;
     while (low <= high) {
       this._map[low++] = dstLow;
-      dstLow = dstLow.substr(0, lastByte) + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1);
+      dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1);
     }
   }
   mapBfRangeToArray(low, high, array) {
     let i = 0,
         ii = array.length;
     while (low <= high && i < ii) {
       this._map[low] = array[i++];
       ++low;
@@ -26011,17 +26011,17 @@ var CFFCompiler = function CFFCompilerCl
           nibbles += 'e';
         } else {
           nibbles += a;
         }
       }
       nibbles += nibbles.length & 1 ? 'f' : 'ff';
       var out = [30];
       for (i = 0, ii = nibbles.length; i < ii; i += 2) {
-        out.push(parseInt(nibbles.substr(i, 2), 16));
+        out.push(parseInt(nibbles.substring(i, i + 2), 16));
       }
       return out;
     },
     encodeInteger: function CFFCompiler_encodeInteger(value) {
       var code;
       if (value >= -107 && value <= 107) {
         code = [value + 139];
       } else if (value >= 108 && value <= 1131) {
@@ -31678,19 +31678,19 @@ function getUnicodeForGlyph(name, glyphs
   return unicode;
  }
  if (!name) {
   return -1;
  }
  if (name[0] === 'u') {
   var nameLen = name.length, hexStr;
   if (nameLen === 7 && name[1] === 'n' && name[2] === 'i') {
-   hexStr = name.substr(3);
+   hexStr = name.substring(3);
   } else if (nameLen >= 5 && nameLen <= 7) {
-   hexStr = name.substr(1);
+   hexStr = name.substring(1);
   } else {
    return -1;
   }
   if (hexStr === hexStr.toUpperCase()) {
    unicode = parseInt(hexStr, 16);
    if (unicode >= 0) {
     return unicode;
    }
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -257,17 +257,17 @@ var Stepper = (function StepperClosure()
     }
     return d;
   }
 
   function simplifyArgs(args) {
     if (typeof args === 'string') {
       var MAX_STRING_LENGTH = 75;
       return args.length <= MAX_STRING_LENGTH ? args :
-        args.substr(0, MAX_STRING_LENGTH) + '...';
+        args.substring(0, MAX_STRING_LENGTH) + '...';
     }
     if (typeof args !== 'object' || args === null) {
       return args;
     }
     if ('length' in args) { // array
       var simpleArgs = [], i, ii;
       var MAX_ITEMS = 10;
       for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -486,25 +486,31 @@ let PDFViewerApplication = {
     let pdfLinkService = new _pdf_link_service.PDFLinkService({
       eventBus,
       externalLinkTarget: _app_options.AppOptions.get('externalLinkTarget'),
       externalLinkRel: _app_options.AppOptions.get('externalLinkRel')
     });
     this.pdfLinkService = pdfLinkService;
     let downloadManager = this.externalServices.createDownloadManager({ disableCreateObjectURL: _app_options.AppOptions.get('disableCreateObjectURL') });
     this.downloadManager = downloadManager;
+    const findController = new _pdf_find_controller.PDFFindController({
+      linkService: pdfLinkService,
+      eventBus
+    });
+    this.findController = findController;
     let container = appConfig.mainContainer;
     let viewer = appConfig.viewerContainer;
     this.pdfViewer = new _pdf_viewer.PDFViewer({
       container,
       viewer,
       eventBus,
       renderingQueue: pdfRenderingQueue,
       linkService: pdfLinkService,
       downloadManager,
+      findController,
       renderer: _app_options.AppOptions.get('renderer'),
       enableWebGL: _app_options.AppOptions.get('enableWebGL'),
       l10n: this.l10n,
       textLayerMode: _app_options.AppOptions.get('textLayerMode'),
       imageResourcesPath: _app_options.AppOptions.get('imageResourcesPath'),
       renderInteractiveForms: _app_options.AppOptions.get('renderInteractiveForms'),
       enablePrintAutoRotate: _app_options.AppOptions.get('enablePrintAutoRotate'),
       useOnlyCssZoom: _app_options.AppOptions.get('useOnlyCssZoom'),
@@ -520,41 +526,17 @@ let PDFViewerApplication = {
       l10n: this.l10n
     });
     pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
     this.pdfHistory = new _pdf_history.PDFHistory({
       linkService: pdfLinkService,
       eventBus
     });
     pdfLinkService.setHistory(this.pdfHistory);
-    this.findController = new _pdf_find_controller.PDFFindController({
-      pdfViewer: this.pdfViewer,
-      eventBus
-    });
-    this.findController.onUpdateResultsCount = matchesCount => {
-      if (this.supportsIntegratedFind) {
-        this.externalServices.updateFindMatchesCount(matchesCount);
-      } else {
-        this.findBar.updateResultsCount(matchesCount);
-      }
-    };
-    this.findController.onUpdateState = (state, previous, matchesCount) => {
-      if (this.supportsIntegratedFind) {
-        this.externalServices.updateFindControlState({
-          result: state,
-          findPrevious: previous,
-          matchesCount
-        });
-      } else {
-        this.findBar.updateUIState(state, previous, matchesCount);
-      }
-    };
-    this.pdfViewer.setFindController(this.findController);
     let findBarConfig = Object.create(appConfig.findBar);
-    findBarConfig.findController = this.findController;
     findBarConfig.eventBus = eventBus;
     this.findBar = new _pdf_find_bar.PDFFindBar(findBarConfig, this.l10n);
     this.pdfDocumentProperties = new _pdf_document_properties.PDFDocumentProperties(appConfig.documentProperties, this.overlayManager, eventBus, this.l10n);
     this.pdfCursorTools = new _pdf_cursor_tools.PDFCursorTools({
       container,
       eventBus,
       cursorToolOnLoad: _app_options.AppOptions.get('cursorToolOnLoad')
     });
@@ -705,31 +687,31 @@ let PDFViewerApplication = {
     errorWrapper.setAttribute('hidden', 'true');
     if (!this.pdfLoadingTask) {
       return;
     }
     let promise = this.pdfLoadingTask.destroy();
     this.pdfLoadingTask = null;
     if (this.pdfDocument) {
       this.pdfDocument = null;
+      this.findController.setDocument(null);
       this.pdfThumbnailViewer.setDocument(null);
       this.pdfViewer.setDocument(null);
-      this.pdfLinkService.setDocument(null, null);
-      this.pdfDocumentProperties.setDocument(null, null);
+      this.pdfLinkService.setDocument(null);
+      this.pdfDocumentProperties.setDocument(null);
     }
     this.store = null;
     this.isInitialViewSet = false;
     this.downloadComplete = false;
     this.url = '';
     this.baseUrl = '';
     this.contentDispositionFilename = null;
     this.pdfSidebar.reset();
     this.pdfOutlineViewer.reset();
     this.pdfAttachmentViewer.reset();
-    this.findController.reset();
     this.findBar.reset();
     this.toolbar.reset();
     this.secondaryToolbar.reset();
     if (typeof PDFBug !== 'undefined') {
       PDFBug.cleanup();
     }
     return promise;
   },
@@ -886,16 +868,17 @@ let PDFViewerApplication = {
       });
     });
     let pageModePromise = pdfDocument.getPageMode().catch(function () {});
     this.toolbar.setPagesCount(pdfDocument.numPages, false);
     this.secondaryToolbar.setPagesCount(pdfDocument.numPages);
     const store = this.store = new _view_history.ViewHistory(pdfDocument.fingerprint);
     let baseDocumentUrl;
     baseDocumentUrl = this.baseUrl;
+    this.findController.setDocument(pdfDocument);
     this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
     this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
     let pdfViewer = this.pdfViewer;
     pdfViewer.setDocument(pdfDocument);
     let firstPagePromise = pdfViewer.firstPagePromise;
     let pagesPromise = pdfViewer.pagesPromise;
     let onePageRendered = pdfViewer.onePageRendered;
     let pdfThumbnailViewer = this.pdfThumbnailViewer;
@@ -1191,16 +1174,18 @@ let PDFViewerApplication = {
     eventBus.on('rotateccw', webViewerRotateCcw);
     eventBus.on('switchscrollmode', webViewerSwitchScrollMode);
     eventBus.on('scrollmodechanged', webViewerScrollModeChanged);
     eventBus.on('switchspreadmode', webViewerSwitchSpreadMode);
     eventBus.on('spreadmodechanged', webViewerSpreadModeChanged);
     eventBus.on('documentproperties', webViewerDocumentProperties);
     eventBus.on('find', webViewerFind);
     eventBus.on('findfromurlhash', webViewerFindFromUrlHash);
+    eventBus.on('updatefindmatchescount', webViewerUpdateFindMatchesCount);
+    eventBus.on('updatefindcontrolstate', webViewerUpdateFindControlState);
   },
   bindWindowEvents() {
     let { eventBus, _boundEvents } = this;
     _boundEvents.windowResize = () => {
       eventBus.dispatch('resize', { source: window });
     };
     _boundEvents.windowHashChange = () => {
       eventBus.dispatch('hashchange', {
@@ -1254,16 +1239,18 @@ let PDFViewerApplication = {
     eventBus.off('rotateccw', webViewerRotateCcw);
     eventBus.off('switchscrollmode', webViewerSwitchScrollMode);
     eventBus.off('scrollmodechanged', webViewerScrollModeChanged);
     eventBus.off('switchspreadmode', webViewerSwitchSpreadMode);
     eventBus.off('spreadmodechanged', webViewerSpreadModeChanged);
     eventBus.off('documentproperties', webViewerDocumentProperties);
     eventBus.off('find', webViewerFind);
     eventBus.off('findfromurlhash', webViewerFindFromUrlHash);
+    eventBus.off('updatefindmatchescount', webViewerUpdateFindMatchesCount);
+    eventBus.off('updatefindcontrolstate', webViewerUpdateFindControlState);
     _boundEvents.beforePrint = null;
     _boundEvents.afterPrint = null;
   },
   unbindWindowEvents() {
     let { _boundEvents } = this;
     window.removeEventListener('wheel', webViewerWheel);
     window.removeEventListener('click', webViewerClick);
     window.removeEventListener('keydown', webViewerKeyDown);
@@ -1553,16 +1540,34 @@ function webViewerFindFromUrlHash(evt) {
     query: evt.query,
     phraseSearch: evt.phraseSearch,
     caseSensitive: false,
     entireWord: false,
     highlightAll: true,
     findPrevious: false
   });
 }
+function webViewerUpdateFindMatchesCount({ matchesCount }) {
+  if (PDFViewerApplication.supportsIntegratedFind) {
+    PDFViewerApplication.externalServices.updateFindMatchesCount(matchesCount);
+  } else {
+    PDFViewerApplication.findBar.updateResultsCount(matchesCount);
+  }
+}
+function webViewerUpdateFindControlState({ state, previous, matchesCount }) {
+  if (PDFViewerApplication.supportsIntegratedFind) {
+    PDFViewerApplication.externalServices.updateFindControlState({
+      result: state,
+      findPrevious: previous,
+      matchesCount
+    });
+  } else {
+    PDFViewerApplication.findBar.updateUIState(state, previous, matchesCount);
+  }
+}
 function webViewerScaleChanging(evt) {
   PDFViewerApplication.toolbar.setPageScale(evt.presetValue, evt.scale);
   PDFViewerApplication.pdfViewer.update();
 }
 function webViewerRotationChanging(evt) {
   PDFViewerApplication.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
   PDFViewerApplication.forceRendering();
   PDFViewerApplication.pdfViewer.currentPageNumber = evt.pageNumber;
@@ -2189,19 +2194,22 @@ function noContextMenuHandler(evt) {
   evt.preventDefault();
 }
 function isDataSchema(url) {
   let i = 0,
       ii = url.length;
   while (i < ii && url[i].trim() === '') {
     i++;
   }
-  return url.substr(i, 5).toLowerCase() === 'data:';
+  return url.substring(i, i + 5).toLowerCase() === 'data:';
 }
 function getPDFFileNameFromURL(url, defaultFilename = 'document.pdf') {
+  if (typeof url !== 'string') {
+    return defaultFilename;
+  }
   if (isDataSchema(url)) {
     console.warn('getPDFFileNameFromURL: ' + 'ignoring "data:" URL for performance reasons.');
     return defaultFilename;
   }
   const reURI = /^(?:(?:[^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
   const reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
   let splitURI = reURI.exec(url);
   let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
@@ -2293,21 +2301,22 @@ class EventBus {
       return;
     }
     eventListeners.splice(i, 1);
   }
   dispatch(eventName) {
     let eventListeners = this._listeners[eventName];
     if (!eventListeners || eventListeners.length === 0) {
       if (this._dispatchToDOM) {
-        this._dispatchDOMEvent(eventName);
-      }
-      return;
-    }
-    let args = Array.prototype.slice.call(arguments, 1);
+        const args = Array.prototype.slice.call(arguments, 1);
+        this._dispatchDOMEvent(eventName, args);
+      }
+      return;
+    }
+    const args = Array.prototype.slice.call(arguments, 1);
     eventListeners.slice(0).forEach(function (listener) {
       listener.apply(null, args);
     });
     if (this._dispatchToDOM) {
       this._dispatchDOMEvent(eventName, args);
     }
   }
   _dispatchDOMEvent(eventName, args = null) {
@@ -3782,17 +3791,17 @@ class PDFDocumentProperties {
     Promise.all([this.overlayManager.open(this.overlayName), this._dataAvailableCapability.promise]).then(() => {
       const currentPageNumber = this._currentPageNumber;
       const pagesRotation = this._pagesRotation;
       if (this.fieldData && currentPageNumber === this.fieldData['_currentPageNumber'] && pagesRotation === this.fieldData['_pagesRotation']) {
         this._updateUI();
         return;
       }
       this.pdfDocument.getMetadata().then(({ info, metadata, contentDispositionFilename }) => {
-        return Promise.all([info, metadata, contentDispositionFilename || (0, _ui_utils.getPDFFileNameFromURL)(this.url), this._parseFileSize(this.maybeFileSize), this._parseDate(info.CreationDate), this._parseDate(info.ModDate), this.pdfDocument.getPage(currentPageNumber).then(pdfPage => {
+        return Promise.all([info, metadata, contentDispositionFilename || (0, _ui_utils.getPDFFileNameFromURL)(this.url || ''), this._parseFileSize(this.maybeFileSize), this._parseDate(info.CreationDate), this._parseDate(info.ModDate), this.pdfDocument.getPage(currentPageNumber).then(pdfPage => {
           return this._parsePageSize((0, _ui_utils.getPageSizeInches)(pdfPage), pagesRotation);
         }), this._parseLinearization(info.IsLinearized)]);
       }).then(([info, metadata, fileName, fileSize, creationDate, modDate, pageSize, isLinearized]) => {
         freezeFieldData({
           'fileName': fileName,
           'fileSize': fileSize,
           'title': info.Title,
           'author': info.Author,
@@ -3823,17 +3832,17 @@ class PDFDocumentProperties {
         freezeFieldData(data);
         this._updateUI();
       });
     });
   }
   close() {
     this.overlayManager.close(this.overlayName);
   }
-  setDocument(pdfDocument, url) {
+  setDocument(pdfDocument, url = null) {
     if (this.pdfDocument) {
       this._reset();
       this._updateUI(true);
     }
     if (!pdfDocument) {
       return;
     }
     this.pdfDocument = pdfDocument;
@@ -4001,22 +4010,18 @@ class PDFFindBar {
     this.findField = options.findField || null;
     this.highlightAll = options.highlightAllCheckbox || null;
     this.caseSensitive = options.caseSensitiveCheckbox || null;
     this.entireWord = options.entireWordCheckbox || null;
     this.findMsg = options.findMsg || null;
     this.findResultsCount = options.findResultsCount || null;
     this.findPreviousButton = options.findPreviousButton || null;
     this.findNextButton = options.findNextButton || null;
-    this.findController = options.findController || null;
     this.eventBus = options.eventBus;
     this.l10n = l10n;
-    if (this.findController === null) {
-      throw new Error('PDFFindBar cannot be used without a ' + 'PDFFindController instance.');
-    }
     this.toggleButton.addEventListener('click', () => {
       this.toggle();
     });
     this.findField.addEventListener('input', () => {
       this.dispatchEvent('');
     });
     this.bar.addEventListener('keydown', e => {
       switch (e.keyCode) {
@@ -4130,17 +4135,17 @@ class PDFFindBar {
   }
   close() {
     if (!this.opened) {
       return;
     }
     this.opened = false;
     this.toggleButton.classList.remove('toggled');
     this.bar.classList.add('hidden');
-    this.findController.active = false;
+    this.eventBus.dispatch('findbarclose', { source: this });
   }
   toggle() {
     if (this.opened) {
       this.close();
     } else {
       this.open();
     }
   }
@@ -4192,39 +4197,76 @@ const CHARACTERS_TO_NORMALIZE = {
   '\u201D': '"',
   '\u201E': '"',
   '\u201F': '"',
   '\u00BC': '1/4',
   '\u00BD': '1/2',
   '\u00BE': '3/4'
 };
 class PDFFindController {
-  constructor({ pdfViewer, eventBus = (0, _dom_events.getGlobalEventBus)() }) {
-    this._pdfViewer = pdfViewer;
+  constructor({ linkService, eventBus = (0, _dom_events.getGlobalEventBus)() }) {
+    this._linkService = linkService;
     this._eventBus = eventBus;
-    this.onUpdateResultsCount = null;
-    this.onUpdateState = null;
-    this.reset();
+    this._reset();
+    eventBus.on('findbarclose', () => {
+      this._highlightMatches = false;
+      eventBus.dispatch('updatetextlayermatches', {
+        source: this,
+        pageIndex: -1
+      });
+    });
     const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
     this._normalizationRegex = new RegExp(`[${replace}]`, 'g');
   }
+  get highlightMatches() {
+    return this._highlightMatches;
+  }
   get pageMatches() {
     return this._pageMatches;
   }
   get pageMatchesLength() {
     return this._pageMatchesLength;
   }
   get selected() {
     return this._selected;
   }
   get state() {
     return this._state;
   }
-  reset() {
-    this.active = false;
+  setDocument(pdfDocument) {
+    if (this._pdfDocument) {
+      this._reset();
+    }
+    if (!pdfDocument) {
+      return;
+    }
+    this._pdfDocument = pdfDocument;
+  }
+  executeCommand(cmd, state) {
+    if (!this._pdfDocument) {
+      return;
+    }
+    if (this._state === null || cmd !== 'findagain') {
+      this._dirtyMatch = true;
+    }
+    this._state = state;
+    this._updateUIState(FindState.PENDING);
+    this._firstPagePromise.then(() => {
+      this._extractText();
+      clearTimeout(this._findTimeout);
+      if (cmd === 'find') {
+        this._findTimeout = setTimeout(this._nextMatch.bind(this), FIND_TIMEOUT);
+      } else {
+        this._nextMatch();
+      }
+    });
+  }
+  _reset() {
+    this._highlightMatches = false;
+    this._pdfDocument = null;
     this._pageMatches = [];
     this._pageMatchesLength = null;
     this._state = null;
     this._selected = {
       pageIdx: -1,
       matchIdx: -1
     };
     this._offset = {
@@ -4242,32 +4284,16 @@ class PDFFindController {
     this._firstPagePromise = new Promise(resolve => {
       const eventBus = this._eventBus;
       eventBus.on('pagesinit', function onPagesInit() {
         eventBus.off('pagesinit', onPagesInit);
         resolve();
       });
     });
   }
-  executeCommand(cmd, state) {
-    if (this._state === null || cmd !== 'findagain') {
-      this._dirtyMatch = true;
-    }
-    this._state = state;
-    this._updateUIState(FindState.PENDING);
-    this._firstPagePromise.then(() => {
-      this._extractText();
-      clearTimeout(this._findTimeout);
-      if (cmd === 'find') {
-        this._findTimeout = setTimeout(this._nextMatch.bind(this), FIND_TIMEOUT);
-      } else {
-        this._nextMatch();
-      }
-    });
-  }
   _normalize(text) {
     return text.replace(this._normalizationRegex, function (ch) {
       return CHARACTERS_TO_NORMALIZE[ch];
     });
   }
   _prepareMatches(matchesWithLength, matches, matchesLength) {
     function isSubTerm(matchesWithLength, currentIndex) {
       const currentElem = matchesWithLength[currentIndex];
@@ -4392,21 +4418,23 @@ class PDFFindController {
       this._updateUIResultsCount();
     }
   }
   _extractText() {
     if (this._extractTextPromises.length > 0) {
       return;
     }
     let promise = Promise.resolve();
-    for (let i = 0, ii = this._pdfViewer.pagesCount; i < ii; i++) {
+    for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
       const extractTextCapability = (0, _pdfjsLib.createPromiseCapability)();
       this._extractTextPromises[i] = extractTextCapability.promise;
       promise = promise.then(() => {
-        return this._pdfViewer.getPageTextContent(i).then(textContent => {
+        return this._pdfDocument.getPage(i + 1).then(pdfPage => {
+          return pdfPage.getTextContent({ normalizeWhitespace: true });
+        }).then(textContent => {
           const textItems = textContent.items;
           const strBuf = [];
           for (let j = 0, jj = textItems.length; j < jj; j++) {
             strBuf.push(textItems[j].str);
           }
           this._pageContents[i] = strBuf.join('');
           extractTextCapability.resolve(i);
         }, reason => {
@@ -4414,28 +4442,28 @@ class PDFFindController {
           this._pageContents[i] = '';
           extractTextCapability.resolve(i);
         });
       });
     }
   }
   _updatePage(index) {
     if (this._selected.pageIdx === index) {
-      this._pdfViewer.currentPageNumber = index + 1;
-    }
-    const page = this._pdfViewer.getPageView(index);
-    if (page.textLayer) {
-      page.textLayer.updateMatches();
-    }
+      this._linkService.page = index + 1;
+    }
+    this._eventBus.dispatch('updatetextlayermatches', {
+      source: this,
+      pageIndex: index
+    });
   }
   _nextMatch() {
     const previous = this._state.findPrevious;
-    const currentPageIndex = this._pdfViewer.currentPageNumber - 1;
-    const numPages = this._pdfViewer.pagesCount;
-    this.active = true;
+    const currentPageIndex = this._linkService.page - 1;
+    const numPages = this._linkService.pagesCount;
+    this._highlightMatches = true;
     if (this._dirtyMatch) {
       this._dirtyMatch = false;
       this._selected.pageIdx = this._selected.matchIdx = -1;
       this._offset.pageIdx = currentPageIndex;
       this._offset.matchIdx = null;
       this._resumePageIdx = null;
       this._pageMatches.length = 0;
       this._pageMatchesLength = null;
@@ -4501,33 +4529,33 @@ class PDFFindController {
       if (!matches) {
         this._resumePageIdx = pageIdx;
         break;
       }
     } while (!this._matchesReady(matches));
   }
   _advanceOffsetPage(previous) {
     const offset = this._offset;
-    const numPages = this._extractTextPromises.length;
+    const numPages = this._linkService.pagesCount;
     offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
     offset.matchIdx = null;
     this._pagesToSearch--;
     if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
       offset.pageIdx = previous ? numPages - 1 : 0;
       offset.wrapped = true;
     }
   }
   _updateMatch(found = false) {
     let state = FindState.NOT_FOUND;
     const wrapped = this._offset.wrapped;
     this._offset.wrapped = false;
     if (found) {
       const previousPage = this._selected.pageIdx;
-      this.selected.pageIdx = this._offset.pageIdx;
-      this.selected.matchIdx = this._offset.matchIdx;
+      this._selected.pageIdx = this._offset.pageIdx;
+      this._selected.matchIdx = this._offset.matchIdx;
       state = wrapped ? FindState.WRAPPED : FindState.FOUND;
       if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
         this._updatePage(previousPage);
       }
     }
     this._updateUIState(state, this._state.findPrevious);
     if (this._selected.pageIdx !== -1) {
       this._updatePage(this._selected.pageIdx);
@@ -4547,28 +4575,28 @@ class PDFFindController {
       current = total = 0;
     }
     return {
       current,
       total
     };
   }
   _updateUIResultsCount() {
-    if (!this.onUpdateResultsCount) {
-      return;
-    }
-    const matchesCount = this._requestMatchesCount();
-    this.onUpdateResultsCount(matchesCount);
+    this._eventBus.dispatch('updatefindmatchescount', {
+      source: this,
+      matchesCount: this._requestMatchesCount()
+    });
   }
   _updateUIState(state, previous) {
-    if (!this.onUpdateState) {
-      return;
-    }
-    const matchesCount = this._requestMatchesCount();
-    this.onUpdateState(state, previous, matchesCount);
+    this._eventBus.dispatch('updatefindcontrolstate', {
+      source: this,
+      state,
+      previous,
+      matchesCount: this._requestMatchesCount()
+    });
   }
 }
 exports.FindState = FindState;
 exports.PDFFindController = PDFFindController;
 
 /***/ }),
 /* 17 */
 /***/ (function(module, exports, __webpack_require__) {
@@ -5048,17 +5076,17 @@ class PDFLinkService {
     this.externalLinkTarget = externalLinkTarget;
     this.externalLinkRel = externalLinkRel;
     this.baseUrl = null;
     this.pdfDocument = null;
     this.pdfViewer = null;
     this.pdfHistory = null;
     this._pagesRefCache = null;
   }
-  setDocument(pdfDocument, baseUrl) {
+  setDocument(pdfDocument, baseUrl = null) {
     this.baseUrl = baseUrl;
     this.pdfDocument = pdfDocument;
     this._pagesRefCache = Object.create(null);
   }
   setViewer(pdfViewer) {
     this.pdfViewer = pdfViewer;
   }
   setHistory(pdfHistory) {
@@ -6607,16 +6635,17 @@ class BaseViewer {
       throw new Error('Cannot initialize BaseViewer.');
     }
     this._name = this.constructor.name;
     this.container = options.container;
     this.viewer = options.viewer || options.container.firstElementChild;
     this.eventBus = options.eventBus || (0, _dom_events.getGlobalEventBus)();
     this.linkService = options.linkService || new _pdf_link_service.SimpleLinkService();
     this.downloadManager = options.downloadManager || null;
+    this.findController = options.findController || null;
     this.removePageBorders = options.removePageBorders || false;
     this.textLayerMode = Number.isInteger(options.textLayerMode) ? options.textLayerMode : _ui_utils.TextLayerMode.ENABLE;
     this.imageResourcesPath = options.imageResourcesPath || '';
     this.renderInteractiveForms = options.renderInteractiveForms || false;
     this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
     this.renderer = options.renderer || _ui_utils.RendererType.CANVAS;
     this.enableWebGL = options.enableWebGL || false;
     this.useOnlyCssZoom = options.useOnlyCssZoom || false;
@@ -7172,21 +7201,16 @@ class BaseViewer {
     if (pageView) {
       this._ensurePdfPageLoaded(pageView).then(() => {
         this.renderingQueue.renderView(pageView);
       });
       return true;
     }
     return false;
   }
-  getPageTextContent(pageIndex) {
-    return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
-      return page.getTextContent({ normalizeWhitespace: true });
-    });
-  }
   createTextLayerBuilder(textLayerDiv, pageIndex, viewport, enhanceTextSelection = false) {
     return new _text_layer_builder.TextLayerBuilder({
       textLayerDiv,
       eventBus: this.eventBus,
       pageIndex,
       viewport,
       findController: this.isInPresentationMode ? null : this.findController,
       enhanceTextSelection: this.isInPresentationMode ? false : enhanceTextSelection
@@ -7198,19 +7222,16 @@ class BaseViewer {
       pdfPage,
       imageResourcesPath,
       renderInteractiveForms,
       linkService: this.linkService,
       downloadManager: this.downloadManager,
       l10n
     });
   }
-  setFindController(findController) {
-    this.findController = findController;
-  }
   get hasEqualPageSizes() {
     let firstPageView = this._pages[0];
     for (let i = 1, ii = this._pages.length; i < ii; ++i) {
       let pageView = this._pages[i];
       if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
         return false;
       }
     }
@@ -7592,30 +7613,38 @@ class PDFPageView {
       }
     }
     if (this.zoomLayer) {
       this.cssTransform(this.zoomLayer.firstChild);
     }
     this.reset(true, true);
   }
   cancelRendering(keepAnnotations = false) {
+    const renderingState = this.renderingState;
     if (this.paintTask) {
       this.paintTask.cancel();
       this.paintTask = null;
     }
     this.renderingState = _pdf_rendering_queue.RenderingStates.INITIAL;
     this.resume = null;
     if (this.textLayer) {
       this.textLayer.cancel();
       this.textLayer = null;
     }
     if (!keepAnnotations && this.annotationLayer) {
       this.annotationLayer.cancel();
       this.annotationLayer = null;
     }
+    if (renderingState !== _pdf_rendering_queue.RenderingStates.INITIAL) {
+      this.eventBus.dispatch('pagecancelled', {
+        source: this,
+        pageNumber: this.id,
+        renderingState
+      });
+    }
   }
   cssTransform(target, redrawAnnotations = false) {
     let width = this.viewport.width;
     let height = this.viewport.height;
     let div = this.div;
     target.style.width = target.parentNode.style.width = div.style.width = Math.floor(width) + 'px';
     target.style.height = target.parentNode.style.height = div.style.height = Math.floor(height) + 'px';
     let relativeRotation = this.viewport.rotation - this.paintedViewportMap.get(target).rotation;
@@ -7907,16 +7936,18 @@ class TextLayerBuilder {
     this.pageIdx = pageIndex;
     this.pageNumber = this.pageIdx + 1;
     this.matches = [];
     this.viewport = viewport;
     this.textDivs = [];
     this.findController = findController;
     this.textLayerRenderTask = null;
     this.enhanceTextSelection = enhanceTextSelection;
+    this._boundEvents = Object.create(null);
+    this._bindEvents();
     this._bindMouse();
   }
   _finishRendering() {
     this.renderingDone = true;
     if (!this.enhanceTextSelection) {
       let endOfContent = document.createElement('div');
       endOfContent.className = 'endOfContent';
       this.textLayerDiv.appendChild(endOfContent);
@@ -8098,27 +8129,51 @@ class TextLayerBuilder {
       let begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
       for (let n = begin, end = match.end.divIdx; n <= end; n++) {
         let div = textDivs[n];
         div.textContent = textContentItemsStr[n];
         div.className = '';
       }
       clearedUntilDivIdx = match.end.divIdx + 1;
     }
-    if (this.findController === null || !this.findController.active) {
+    if (!this.findController || !this.findController.highlightMatches) {
       return;
     }
     let pageMatches, pageMatchesLength;
     if (this.findController !== null) {
       pageMatches = this.findController.pageMatches[this.pageIdx] || null;
       pageMatchesLength = this.findController.pageMatchesLength ? this.findController.pageMatchesLength[this.pageIdx] || null : null;
     }
     this.matches = this.convertMatches(pageMatches, pageMatchesLength);
     this.renderMatches(this.matches);
   }
+  _bindEvents() {
+    const { eventBus, _boundEvents } = this;
+    _boundEvents.pageCancelled = evt => {
+      if (evt.pageNumber !== this.pageNumber) {
+        return;
+      }
+      if (this.textLayerRenderTask) {
+        console.error('TextLayerBuilder._bindEvents: `this.cancel()` should ' + 'have been called when the page was reset, or rendering cancelled.');
+        return;
+      }
+      for (const name in _boundEvents) {
+        eventBus.off(name.toLowerCase(), _boundEvents[name]);
+        delete _boundEvents[name];
+      }
+    };
+    _boundEvents.updateTextLayerMatches = evt => {
+      if (evt.pageIndex !== this.pageIdx && evt.pageIndex !== -1) {
+        return;
+      }
+      this.updateMatches();
+    };
+    eventBus.on('pagecancelled', _boundEvents.pageCancelled);
+    eventBus.on('updatetextlayermatches', _boundEvents.updateTextLayerMatches);
+  }
   _bindMouse() {
     let div = this.textLayerDiv;
     let expandDivsTimer = null;
     div.addEventListener('mousedown', evt => {
       if (this.enhanceTextSelection && this.textLayerRenderTask) {
         this.textLayerRenderTask.expandTextDivs(true);
         return;
       }
@@ -8939,30 +8994,34 @@ class MozL10n {
   async get(property, args, fallback) {
     return this.mozL10n.get(property, args, fallback);
   }
   async translate(element) {
     this.mozL10n.translate(element);
   }
 }
 (function listenFindEvents() {
-  const events = ['find', 'findagain', 'findhighlightallchange', 'findcasesensitivitychange', 'findentirewordchange'];
-  let handleEvent = function (evt) {
+  const events = ['find', 'findagain', 'findhighlightallchange', 'findcasesensitivitychange', 'findentirewordchange', 'findbarclose'];
+  let handleEvent = function ({ type, detail }) {
     if (!_app.PDFViewerApplication.initialized) {
       return;
     }
+    if (type === 'findbarclose') {
+      _app.PDFViewerApplication.eventBus.dispatch('findbarclose', { source: window });
+      return;
+    }
     _app.PDFViewerApplication.eventBus.dispatch('find', {
       source: window,
-      type: evt.type.substring('find'.length),
-      query: evt.detail.query,
+      type: type.substring('find'.length),
+      query: detail.query,
       phraseSearch: true,
-      caseSensitive: !!evt.detail.caseSensitive,
-      entireWord: !!evt.detail.entireWord,
-      highlightAll: !!evt.detail.highlightAll,
-      findPrevious: !!evt.detail.findPrevious
+      caseSensitive: !!detail.caseSensitive,
+      entireWord: !!detail.entireWord,
+      highlightAll: !!detail.highlightAll,
+      findPrevious: !!detail.findPrevious
     });
   };
   for (let event of events) {
     window.addEventListener(event, handleEvent);
   }
 })();
 function FirefoxComDataRangeTransport(length, initialData) {
   _pdfjsLib.PDFDataRangeTransport.call(this, length, initialData);
--- 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.0.866
+  release: version 2.0.892
 
   # 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/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -792,43 +792,80 @@ MediaEngineRemoteVideoSource::GetBestFit
   if (!candidateSet.Length()) {
     return UINT32_MAX;
   }
   TrimLessFitCandidates(candidateSet);
   return candidateSet[0].mDistance;
 }
 
 static void
+LogConstraintStringRange(const NormalizedConstraintSet::StringRange& aRange)
+{
+  if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) {
+    LOG(("  %s: { exact: [%s], ideal: [%s] }",
+         aRange.mName,
+         (aRange.mExact.size()? NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get() : ""),
+         (aRange.mIdeal.size()? NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get() : "")));
+  } else {
+    LOG(("  %s: { exact: [", aRange.mName));
+    for (auto& entry : aRange.mExact) {
+      LOG(("      %s,", NS_ConvertUTF16toUTF8(entry).get()));
+    }
+    LOG(("    ], ideal: ["));
+    for (auto& entry : aRange.mIdeal) {
+      LOG(("      %s,", NS_ConvertUTF16toUTF8(entry).get()));
+    }
+    LOG(("    ]}"));
+  }
+}
+
+template<typename T>
+static void
+LogConstraintRange(const NormalizedConstraintSet::Range<T>& aRange)
+{
+  if (aRange.mIdeal.isSome()) {
+    LOG(("  %s: { min: %d, max: %d, ideal: %d }",
+         aRange.mName, aRange.mMin, aRange.mMax, aRange.mIdeal.valueOr(0)));
+  } else {
+    LOG(("  %s: { min: %d, max: %d }",
+         aRange.mName, aRange.mMin, aRange.mMax));
+  }
+}
+
+template<>
+void
+LogConstraintRange(const NormalizedConstraintSet::Range<double>& aRange)
+{
+  if (aRange.mIdeal.isSome()) {
+    LOG(("  %s: { min: %f, max: %f, ideal: %f }",
+         aRange.mName, aRange.mMin, aRange.mMax, aRange.mIdeal.valueOr(0)));
+  } else {
+    LOG(("  %s: { min: %f, max: %f }",
+         aRange.mName, aRange.mMin, aRange.mMax));
+  }
+}
+
+static void
 LogConstraints(const NormalizedConstraintSet& aConstraints)
 {
   auto& c = aConstraints;
-  if (c.mWidth.mIdeal.isSome()) {
-    LOG(("Constraints: width: { min: %d, max: %d, ideal: %d }",
-         c.mWidth.mMin, c.mWidth.mMax,
-         c.mWidth.mIdeal.valueOr(0)));
-  } else {
-    LOG(("Constraints: width: { min: %d, max: %d }",
-         c.mWidth.mMin, c.mWidth.mMax));
-  }
-  if (c.mHeight.mIdeal.isSome()) {
-    LOG(("             height: { min: %d, max: %d, ideal: %d }",
-         c.mHeight.mMin, c.mHeight.mMax,
-         c.mHeight.mIdeal.valueOr(0)));
-  } else {
-    LOG(("             height: { min: %d, max: %d }",
-         c.mHeight.mMin, c.mHeight.mMax));
-  }
-  if (c.mFrameRate.mIdeal.isSome()) {
-    LOG(("             frameRate: { min: %f, max: %f, ideal: %f }",
-         c.mFrameRate.mMin, c.mFrameRate.mMax,
-         c.mFrameRate.mIdeal.valueOr(0)));
-  } else {
-    LOG(("             frameRate: { min: %f, max: %f }",
-         c.mFrameRate.mMin, c.mFrameRate.mMax));
-  }
+  LOG(("Constraints: {"));
+  LOG(("%s", [&]() {
+    LogConstraintRange(c.mWidth);
+    LogConstraintRange(c.mHeight);
+    LogConstraintRange(c.mFrameRate);
+    LogConstraintStringRange(c.mMediaSource);
+    LogConstraintStringRange(c.mFacingMode);
+    LogConstraintStringRange(c.mDeviceId);
+    LogConstraintRange(c.mEchoCancellation);
+    LogConstraintRange(c.mAutoGainControl);
+    LogConstraintRange(c.mNoiseSuppression);
+    LogConstraintRange(c.mChannelCount);
+    return "}";
+  }()));
 }
 
 static void
 LogCapability(const char* aHeader,
               const webrtc::CaptureCapability &aCapability,
               uint32_t aDistance)
 {
   // RawVideoType and VideoCodecType media/webrtc/trunk/webrtc/common_types.h
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -476,16 +476,17 @@ MediaConstraintsHelper::FitnessDistance(
 
 /* static */ const char*
 MediaConstraintsHelper::SelectSettings(
     const NormalizedConstraints& aConstraints,
     nsTArray<RefPtr<MediaDevice>>& aDevices,
     bool aIsChrome)
 {
   auto& c = aConstraints;
+  LogConstraints(c);
 
   // First apply top-level constraints.
 
   // Stack constraintSets that pass, starting with the required one, because the
   // whole stack must be re-satisfied each time a capability-set is ruled out
   // (this avoids storing state or pushing algorithm into the lower-level code).
   nsTArray<RefPtr<MediaDevice>> unsatisfactory;
   nsTArray<const NormalizedConstraintSet*> aggregateConstraints;
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -35,19 +35,21 @@ struct ComputedFlexItemInfo
   /**
    * mMainBaseSize is a measure of the size of the item in the main
    * axis before the flex sizing algorithm is applied. In the spec,
    * this is called "flex base size", but we use this name to connect
    * the value to the other main axis sizes.
    */
   nscoord mMainBaseSize;
   /**
-   * mMainDeltaSize is the value that the flex sizing algorithm
-   * "wants" to use to stretch or shrink the item, before clamping to
-   * the item's main min and max sizes. Since the flex sizing
+   * mMainDeltaSize is the amount that the flex sizing algorithm
+   * adds to the mMainBaseSize, before clamping to mMainMinSize and
+   * mMainMaxSize. This can be thought of as the amount by which the
+   * flex layout algorithm "wants" to shrink or grow the item, and
+   * would do, if it was unconstrained. Since the flex sizing
    * algorithm proceeds linearly, the mMainDeltaSize for an item only
    * respects the resolved size of items already frozen.
    */
   nscoord mMainDeltaSize;
   nscoord mMainMinSize;
   nscoord mMainMaxSize;
   nscoord mCrossMinSize;
   nscoord mCrossMaxSize;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1574,16 +1574,23 @@ var BrowserApp = {
 
       key = key.replace("private.data.", "");
 
       switch (key) {
         case "cookies_sessions":
           promises.push(Sanitizer.clearItem("cookies"));
           promises.push(Sanitizer.clearItem("sessions"));
           break;
+        case "downloadFiles":
+          // If the user is quiting the app and the downloads are to be sanitized
+          // means he chose to "Clear private data -> Downloads" upon exit so
+          // all downloads will be purged, irrespective of their current state (in progress/error/completed)
+          let clearUnfinishedDownloads = aShutdown === true;
+          promises.push(Sanitizer.clearItem(key, undefined, clearUnfinishedDownloads));
+          break;
         case "openTabs":
           if (aShutdown === true) {
             Services.obs.notifyObservers(null, "browser:purge-session-tabs");
             break;
           }
           // fall-through if aShutdown is false
         default:
           promises.push(Sanitizer.clearItem(key));
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -24,30 +24,32 @@ XPCOMUtils.defineLazyServiceGetters(this
   quotaManagerService: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
 });
 
 
 var EXPORTED_SYMBOLS = ["Sanitizer"];
 
 function Sanitizer() {}
 Sanitizer.prototype = {
-  clearItem: function(aItemName, startTime) {
+  clearItem: function(aItemName, startTime, clearUnfinishedDownloads) {
     // Only a subset of items support deletion with startTime.
     // Those who do not will be rejected with error message.
     if (typeof startTime != "undefined") {
       switch (aItemName) {
         // Normal call to DownloadFiles remove actual data from storage, but our web-extension consumer
         // deletes only download history. So, for this reason we are passing a flag 'deleteFiles'.
         case "downloadHistory":
           return this._clear("downloadFiles", { startTime, deleteFiles: false });
         case "formdata":
           return this._clear(aItemName, { startTime });
         default:
           return Promise.reject({message: `Invalid argument: ${aItemName} does not support startTime argument.`});
       }
+    } else if (aItemName === "downloadFiles" && typeof clearUnfinishedDownloads != "undefined") {
+      return this._clear(aItemName, { clearUnfinishedDownloads });
     } else {
       return this._clear(aItemName);
     }
   },
 
  _clear: function(aItemName, options) {
     let item = this.items[aItemName];
     let canClear = item.canClear;
@@ -311,34 +313,34 @@ Sanitizer.prototype = {
           handleCompletion: function(aReason) { aCallback(aReason == 0 && count > 0); }
         };
         FormHistory.count({}, countDone);
       }
     },
 
     // Adapted from desktop, but heavily modified - see comments below.
     downloadFiles: {
-      clear: Task.async(function* ({ startTime = 0, deleteFiles = true} = {}) {
+      clear: Task.async(function* ({ startTime = 0,
+                                     deleteFiles = true,
+                                     clearUnfinishedDownloads = false } = {}) {
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
 
         let list = yield Downloads.getList(Downloads.ALL);
         let downloads = yield list.getAll();
         var finalizePromises = [];
 
         // Logic copied from DownloadList.removeFinished. Ideally, we would
         // just use that method directly, but we want to be able to remove the
         // downloaded files as well.
         for (let download of downloads) {
-          // Remove downloads that have been canceled, even if the cancellation
-          // operation hasn't completed yet so we don't check "stopped" here.
-          // Failed downloads with partial data are also removed. The startTime
-          // check is provided for addons that may want to delete only recent downloads.
-          if (download.stopped && (!download.hasPartialData || download.error) &&
-              download.startTime.getTime() >= startTime) {
+          let downloadFinished = download.stopped &&
+                                 (!download.hasPartialData || download.error);
+          if ((downloadFinished || clearUnfinishedDownloads) &&
+               download.startTime.getTime() >= startTime) {
             // Remove the download first, so that the views don't get the change
             // notifications that may occur during finalization.
             yield list.remove(download);
             // Ensure that the download is stopped and no partial data is kept.
             // This works even if the download state has changed meanwhile.  We
             // don't need to wait for the procedure to be complete before
             // processing the other downloads in the list.
             finalizePromises.push(download.finalize(true).then(() => null, Cu.reportError));
--- a/python/mozbuild/mozbuild/action/check_binary.py
+++ b/python/mozbuild/mozbuild/action/check_binary.py
@@ -297,20 +297,25 @@ def checks(target, binary):
     # Cheat and pretend we were passed the right argument.
     if 'clang-plugin' in binary:
         target = HOST
     checks = []
     if target['MOZ_LIBSTDCXX_VERSION']:
         checks.append(check_stdcxx)
         checks.append(check_libgcc)
         checks.append(check_glibc)
-    checks.append(check_textrel)
+
+    # Disabled for local builds because of readelf performance: See bug 1472496
+    if not buildconfig.substs.get('DEVELOPER_OPTIONS'):
+        checks.append(check_textrel)
+        checks.append(check_pt_load)
+        checks.append(check_mozglue_order)
+
     checks.append(check_nsmodules)
-    checks.append(check_pt_load)
-    checks.append(check_mozglue_order)
+
     retcode = 0
     basename = os.path.basename(binary)
     for c in checks:
         try:
             name = c.__name__
             c(target, binary)
             if buildconfig.substs.get('MOZ_AUTOMATION'):
                 print('TEST-PASS | {} | {}'.format(name, basename))
--- a/security/manager/tools/PreloadedHPKPins.json
+++ b/security/manager/tools/PreloadedHPKPins.json
@@ -170,17 +170,22 @@
     { "name": "addons.mozilla.net", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 2 },
     // AUS servers MUST remain in test mode
     // see: https://bugzilla.mozilla.org/show_bug.cgi?id=1301956#c23
     { "name": "aus4.mozilla.org", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": true, "id": 3 },
     { "name": "aus5.mozilla.org", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": true, "id": 7 },
+    // Catchall for applications hosted under firefox.com
+    // see https://bugzilla.mozilla.org/show_bug.cgi?id=1494431
+    { "name": "firefox.com", "include_subdomains": true,
+      "pins": "mozilla_services", "test_mode": true, "id": 15 },
     // Firefox Accounts & sync
+    // superseded by catchall for firefox.com, but leaving for tracking
     { "name": "accounts.firefox.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 4 },
     { "name": "api.accounts.firefox.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 5 },
     { "name": "sync.services.mozilla.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 13 },
     // Catch-all for all CDN resources, including product delivery
     { "name": "cdn.mozilla.net", "include_subdomains": true,
@@ -192,16 +197,17 @@
     // Catch-all for everything hosted under services.mozilla.com
     { "name": "services.mozilla.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 6 },
     // Catch-all for everything hosted under telemetry.mozilla.org
     // MUST remain in test mode in order to receive telemetry on broken pins
     { "name": "telemetry.mozilla.org", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": true, "id": 8 },
     // Test Pilot
+    // superseded by catchall for firefox.com, but leaving for tracking
     { "name": "testpilot.firefox.com", "include_subdomains": false,
       "pins": "mozilla_services", "test_mode": false, "id": 9 },
     // Crash report sites
     { "name": "crash-reports.mozilla.com", "include_subdomains": false,
       "pins": "mozilla_services", "test_mode": false, "id": 10 },
     { "name": "crash-reports-xpsp2.mozilla.com", "include_subdomains": false,
       "pins": "mozilla_services", "test_mode": false, "id": 11 },
     { "name": "crash-stats.mozilla.com", "include_subdomains": false,
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -739,34 +739,34 @@ class DevToolsExtensionPageContextParent
 
     return toolbox;
   }
 
   get devToolsToolbox() {
     return this._devToolsToolbox;
   }
 
-  set devToolsTarget(contextDevToolsTarget) {
-    if (this._devToolsTarget) {
+  set devToolsTargetPromise(promise) {
+    if (this._devToolsTargetPromise) {
       throw new Error("Cannot set the context DevTools target twice");
     }
 
-    this._devToolsTarget = contextDevToolsTarget;
+    this._devToolsTargetPromise = promise;
 
-    return contextDevToolsTarget;
+    return promise;
   }
 
-  get devToolsTarget() {
-    return this._devToolsTarget;
+  get devToolsTargetPromise() {
+    return this._devToolsTargetPromise;
   }
 
   shutdown() {
-    if (this._devToolsTarget) {
-      this._devToolsTarget.destroy();
-      this._devToolsTarget = null;
+    if (this._devToolsTargetPromise) {
+      this._devToolsTargetPromise.then(target => target.destroy());
+      this._devToolsTargetPromise = null;
     }
 
     this._devToolsToolbox = null;
 
     super.shutdown();
   }
 }
 
--- a/toolkit/components/extensions/child/ext-identity.js
+++ b/toolkit/components/extensions/child/ext-identity.js
@@ -26,12 +26,28 @@ this.identity = class extends ExtensionA
     return {
       identity: {
         getRedirectURL: function(path = "") {
           let hash = computeHash(extension.id);
           let url = new URL(`https://${hash}.${redirectDomain}/`);
           url.pathname = path;
           return url.href;
         },
+        launchWebAuthFlow: function(details) {
+          // Validate the url and retreive redirect_uri if it was provided.
+          let url, redirectURI;
+          try {
+            url = new URL(details.url);
+          } catch (e) {
+            return Promise.reject({message: "details.url is invalid"});
+          }
+          try {
+            redirectURI = new URL(url.searchParams.get("redirect_uri") || this.getRedirectURL());
+          } catch (e) {
+            return Promise.reject({message: "redirect_uri is invalid"});
+          }
+
+          return context.childManager.callParentAsyncFunction("identity.launchWebAuthFlowInParent", [details, redirectURI.href]);
+        },
       },
     };
   }
 };
--- a/toolkit/components/extensions/parent/ext-identity.js
+++ b/toolkit/components/extensions/parent/ext-identity.js
@@ -1,16 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "XMLHttpRequest"]);
+XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]);
 
 var {
   promiseDocumentLoaded,
 } = ExtensionUtils;
 
 const checkRedirected = (url, redirectURI) => {
   return new Promise((resolve, reject) => {
     let xhr = new XMLHttpRequest();
@@ -89,33 +89,17 @@ const openOAuthWindow = (details, redire
     });
   });
 };
 
 this.identity = class extends ExtensionAPI {
   getAPI(context) {
     return {
       identity: {
-        launchWebAuthFlow: function(details) {
-          // In OAuth2 the url should have a redirect_uri param, parse the url and grab it
-          let url, redirectURI;
-          try {
-            url = new URL(details.url);
-          } catch (e) {
-            return Promise.reject({message: "details.url is invalid"});
-          }
-          try {
-            redirectURI = new URL(url.searchParams.get("redirect_uri"));
-            if (!redirectURI) {
-              return Promise.reject({message: "redirect_uri is missing"});
-            }
-          } catch (e) {
-            return Promise.reject({message: "redirect_uri is invalid"});
-          }
-
+        launchWebAuthFlowInParent: function(details, redirectURI) {
           // If the request is automatically redirected the user has already
           // authorized and we do not want to show the window.
           return checkRedirected(details.url, redirectURI).catch((requestError) => {
             // requestError is zero or xhr.status
             if (requestError !== 0) {
               Cu.reportError(`browser.identity auth check failed with ${requestError}`);
               return Promise.reject({message: "Invalid request"});
             }
--- a/toolkit/components/extensions/test/mochitest/redirect_auto.sjs
+++ b/toolkit/components/extensions/test/mochitest/redirect_auto.sjs
@@ -5,13 +5,13 @@ Components.utils.importGlobalProperties(
 
 function handleRequest(request, response) {
   let params = new URLSearchParams(request.queryString);
   if (params.has("no_redirect")) {
     response.setStatusLine(request.httpVersion, 200, "OK");
     response.write("ok");
   } else {
     response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
-    let url = new URL(params.get("redirect_uri"));
+    let url = new URL(params.get("redirect_uri") || params.get("default_redirect"));
     url.searchParams.set("access_token", "here ya go");
     response.setHeader("Location", url.href);
   }
 }
--- a/toolkit/components/extensions/test/mochitest/test_ext_identity.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_identity.html
@@ -133,22 +133,31 @@ add_task(async function test_otherRedire
     },
   });
 
   await extension.startup();
   await extension.awaitMessage("done");
   await extension.unload();
 });
 
-function background_launchWebAuthFlow(interactive, path, redirect = true) {
-  let expected_redirect = "https://35b64b676900f491c00e7f618d43f7040e88422e.example.com/identity_cb";
+function background_launchWebAuthFlow(interactive, path, redirect = true, useRedirectUri = true) {
+  let uri_path = useRedirectUri ? "identity_cb" : "";
+  let expected_redirect = `https://35b64b676900f491c00e7f618d43f7040e88422e.example.com/${uri_path}`;
   let base_uri = "https://example.com/tests/toolkit/components/extensions/test/mochitest/";
-  let redirect_uri = browser.identity.getRedirectURL("/identity_cb");
+  let redirect_uri = browser.identity.getRedirectURL(useRedirectUri ? uri_path : undefined);
   browser.test.assertEq(expected_redirect, redirect_uri, "expected redirect uri matches hash");
-  let url = `${base_uri}${path}?redirect_uri=${encodeURIComponent(redirect_uri)}`;
+  let url = `${base_uri}${path}`;
+  if (useRedirectUri) {
+    url = `${url}?redirect_uri=${encodeURIComponent(redirect_uri)}`;
+  } else {
+    // We kind of fake it with the redirect url that would normally be configured
+    // in the oauth service.  This does still test that the identity service falls back
+    // to the extensions redirect url.
+    url = `${url}?default_redirect=${encodeURIComponent(expected_redirect)}`;
+  }
   if (!redirect) {
     url = `${url}&no_redirect=1`;
   }
 
   // Ensure we do not start the actual request for the redirect url.
   browser.webRequest.onBeforeRequest.addListener(details => {
     if (details.url.startsWith(expected_redirect)) {
       browser.test.fail("onBeforeRequest called for redirect url");
@@ -191,16 +200,38 @@ add_task(async function test_autoRedirec
     background: `(${background_launchWebAuthFlow})(false, "redirect_auto.sjs")`,
   });
 
   await extension.startup();
   await extension.awaitMessage("done");
   await extension.unload();
 });
 
+add_task(async function test_autoRedirect_noRedirectURI() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "applications": {
+        "gecko": {
+          "id": "identity@mozilla.org",
+        },
+      },
+      "permissions": [
+        "webRequest",
+        "identity",
+        "https://*.example.com/*",
+      ],
+    },
+    background: `(${background_launchWebAuthFlow})(false, "redirect_auto.sjs", true, false)`,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+});
+
 // Tests the situation where the oauth provider has not granted access and interactive=false
 add_task(async function test_noRedirect() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "applications": {
         "gecko": {
           "id": "identity@mozilla.org",
         },
--- a/widget/gtk/WakeLockListener.cpp
+++ b/widget/gtk/WakeLockListener.cpp
@@ -392,23 +392,18 @@ WakeLockTopic::ReceiveInhibitReply(DBusP
     }
   } else {
     self->InhibitFailed();
   }
 }
 
 
 WakeLockListener::WakeLockListener()
-  : mConnection(already_AddRefed<DBusConnection>(
-    dbus_bus_get(DBUS_BUS_SESSION, nullptr)))
+  : mConnection(nullptr)
 {
-  if (mConnection) {
-    dbus_connection_set_exit_on_disconnect(mConnection, false);
-    dbus_connection_setup_with_g_main(mConnection, nullptr);
-  }
 }
 
 /* static */ WakeLockListener*
 WakeLockListener::GetSingleton(bool aCreate)
 {
   if (!sSingleton && aCreate) {
     sSingleton = new WakeLockListener();
   }
@@ -417,20 +412,36 @@ WakeLockListener::GetSingleton(bool aCre
 }
 
 /* static */ void
 WakeLockListener::Shutdown()
 {
   sSingleton = nullptr;
 }
 
+bool
+WakeLockListener::EnsureDBusConnection()
+{
+  if (!mConnection) {
+    mConnection =
+      already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+
+    if (mConnection) {
+      dbus_connection_set_exit_on_disconnect(mConnection, false);
+      dbus_connection_setup_with_g_main(mConnection, nullptr);
+    }
+  }
+
+  return mConnection != nullptr;
+}
+
 nsresult
 WakeLockListener::Callback(const nsAString& topic, const nsAString& state)
 {
-  if (!mConnection) {
+  if (!EnsureDBusConnection()) {
     return NS_ERROR_FAILURE;
   }
 
   if(!topic.Equals(NS_LITERAL_STRING("screen")) &&
      !topic.Equals(NS_LITERAL_STRING("audio-playing")) &&
      !topic.Equals(NS_LITERAL_STRING("video-playing")))
     return NS_OK;
 
--- a/widget/gtk/WakeLockListener.h
+++ b/widget/gtk/WakeLockListener.h
@@ -36,16 +36,18 @@ public:
 
   virtual nsresult Callback(const nsAString& topic,
                             const nsAString& state) override;
 
 private:
   WakeLockListener();
   ~WakeLockListener() = default;
 
+  bool EnsureDBusConnection();
+
   static mozilla::StaticRefPtr<WakeLockListener> sSingleton;
 
 #ifdef MOZ_ENABLE_DBUS
   RefPtr<DBusConnection> mConnection;
 #endif
   // Map of topic names to |WakeLockTopic|s.
   // We assume a small, finite-sized set of topics.
   nsClassHashtable<nsStringHashKey, WakeLockTopic> mTopics;