merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 02 Aug 2016 17:04:48 +0200
changeset 333345 8c1c9706029249b5a94e64a39d0abe0b6fee0408
parent 333336 ea6e87bbd03e976ddb00f625afe40ee5167a8a35 (current diff)
parent 333344 46045ec8a4aa09a341b7209170089ade3c27c1a5 (diff)
child 333501 f299890191b297427f73dce94e3094d7e24a4552
push id10033
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:50:26 +0000
treeherdermozilla-aurora@5dddbefdf759 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
merge fx-team to mozilla-central a=merge
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-const { getSourceNames, parseURL, isScratchpadScheme } = require("devtools/client/shared/source-utils");
+const { getSourceNames, parseURL,
+        isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
 const { LocalizationHelper } = require("devtools/client/shared/l10n");
 
 const l10n = new LocalizationHelper("chrome://devtools/locale/components.properties");
 const webl10n = new LocalizationHelper("chrome://devtools/locale/webconsole.properties");
 
 module.exports = createClass({
   displayName: "Frame",
 
@@ -176,25 +177,18 @@ module.exports = createClass({
         elements.push(
           dom.span({ className: "frame-link-function-display-name" },
             functionDisplayName)
         );
       }
     }
 
     let displaySource = showFullSourceUrl ? long : short;
-    // SourceMapped locations might not be parsed properly by parseURL.
-    // Eg: sourcemapped location could be /folder/file.coffee instead of a url
-    // and so the url parser would not parse non-url locations properly
-    // Check for "/" in displaySource. If "/" is in displaySource,
-    // take everything after last "/".
     if (isSourceMapped) {
-      displaySource = displaySource.lastIndexOf("/") < 0 ?
-        displaySource :
-        displaySource.slice(displaySource.lastIndexOf("/") + 1);
+      displaySource = getSourceMappedFile(displaySource);
     } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
       displaySource = host;
     }
 
     sourceElements.push(dom.span({
       className: "frame-link-filename",
     }, displaySource));
 
--- a/devtools/client/shared/source-utils.js
+++ b/devtools/client/shared/source-utils.js
@@ -294,14 +294,35 @@ function isChromeScheme(location, i = 0)
       }
       return false;
 
     default:
       return false;
   }
 }
 
+/**
+ * A utility method to get the file name from a sourcemapped location
+ * The sourcemap location can be in any form. This method returns a
+ * formatted file name for different cases like Windows or OSX.
+ * @param source
+ * @returns String
+ */
+function getSourceMappedFile(source) {
+  // If sourcemapped source is a OSX path, return
+  // the characters after last "/".
+  // If sourcemapped source is a Windowss path, return
+  // the characters after last "\\".
+  if (source.lastIndexOf("/") >= 0) {
+    source = source.slice(source.lastIndexOf("/") + 1);
+  } else if (source.lastIndexOf("\\") >= 0) {
+    source = source.slice(source.lastIndexOf("\\") + 1);
+  }
+  return source;
+}
+
 exports.parseURL = parseURL;
 exports.getSourceNames = getSourceNames;
 exports.isScratchpadScheme = isScratchpadScheme;
 exports.isChromeScheme = isChromeScheme;
 exports.isContentScheme = isContentScheme;
 exports.isDataScheme = isDataScheme;
+exports.getSourceMappedFile = getSourceMappedFile;
--- a/devtools/client/shared/test/unit/test_source-utils.js
+++ b/devtools/client/shared/test/unit/test_source-utils.js
@@ -147,14 +147,30 @@ add_task(function* () {
 
   // Check query with trailing slash
   testAbbreviation("http://example.com/foo/?bar=1&baz=2",
                    "foo",
                    "http://example.com/foo/",
                    "example.com");
 });
 
+// Test for source mapped file name
+add_task(function* () {
+  const { getSourceMappedFile } = sourceUtils;
+  const source = "baz.js";
+  const output = getSourceMappedFile(source);
+  equal(output, "baz.js", "correctly formats file name");
+  // Test for OSX file path
+  const source1 = "/foo/bar/baz.js";
+  const output1 = getSourceMappedFile(source1);
+  equal(output1, "baz.js", "correctly formats Linux file path");
+  // Test for Windows file path
+  const source2 = "Z:\\foo\\bar\\baz.js";
+  const output2 = getSourceMappedFile(source2);
+  equal(output2, "baz.js", "correctly formats Windows file path");
+});
+
 function testAbbreviation(source, short, long, host) {
   let results = sourceUtils.getSourceNames(source);
   equal(results.short, short, `${source} has correct "short" name`);
   equal(results.long, long, `${source} has correct "long" name`);
   equal(results.host, host, `${source} has correct "host" name`);
 }
--- a/toolkit/components/addoncompat/tests/addon/bootstrap.js
+++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js
@@ -10,22 +10,20 @@ Cu.import("resource://gre/modules/XPCOMU
 const baseURL = "http://mochi.test:8888/browser/" +
   "toolkit/components/addoncompat/tests/browser/";
 
 var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
                           .getService(Ci.nsIContentSecurityManager);
 
 function forEachWindow(f)
 {
-  let wins = Services.ww.getWindowEnumerator("navigator:browser");
+  let wins = Services.wm.getEnumerator("navigator:browser");
   while (wins.hasMoreElements()) {
     let win = wins.getNext();
-    if (win.gBrowser) {
-      f(win);
-    }
+    f(win);
   }
 }
 
 function addLoadListener(target, listener)
 {
   target.addEventListener("load", function handler(event) {
     target.removeEventListener("load", handler, true);
     return listener(event);
--- a/toolkit/components/addoncompat/tests/compat-addon/bootstrap.js
+++ b/toolkit/components/addoncompat/tests/compat-addon/bootstrap.js
@@ -7,22 +7,20 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/BrowserUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const baseURL = "http://mochi.test:8888/browser/" +
   "toolkit/components/addoncompat/tests/browser/";
 
 function forEachWindow(f)
 {
-  let wins = Services.ww.getWindowEnumerator("navigator:browser");
+  let wins = Services.wm.getEnumerator("navigator:browser");
   while (wins.hasMoreElements()) {
     let win = wins.getNext();
-    if (win.gBrowser) {
-      f(win);
-    }
+    f(win);
   }
 }
 
 function addLoadListener(target, listener)
 {
   function frameScript() {
     addEventListener("load", function handler(event) {
       removeEventListener("load", handler, true);
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -582,16 +582,33 @@
             this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
           }
           this._highlightAll = aHighlight;
           let checkbox = this.getElement("highlight");
           checkbox.checked = this._highlightAll;
         ]]></body>
       </method>
 
+      <method name="_setHighlightTimeout">
+        <body><![CDATA[
+          if (this._highlightTimeout)
+            clearTimeout(this._highlightTimeout);
+
+          let word = this._findField.value;
+          // Bug 429723. Don't attempt to highlight ""
+          if (!this._highlightAll || !word)
+            return;
+
+          this._highlightTimeout = setTimeout(() => {
+            this.browser.finder.highlight(true, word,
+              this._findMode == this.FIND_LINKS);
+          }, 500);
+        ]]></body>
+      </method>
+
       <!--
         - Updates the case-sensitivity mode of the findbar and its UI.
         - @param [optional] aString
         -        The string for which case sensitivity might be turned on.
         -        This only used when case-sensitivity is in auto mode,
         -        @see _shouldBeCaseSensitive. The default value for this
         -        parameter is the find-field value.
         -->
@@ -676,18 +693,17 @@
         <body><![CDATA[
           let prefsvc =
             Components.classes["@mozilla.org/preferences-service;1"]
                       .getService(Components.interfaces.nsIPrefBranch);
 
           // Just set the pref; our observer will change the find bar behavior.
           prefsvc.setBoolPref("findbar.entireword", aEntireWord);
 
-          if (this.getElement("highlight").checked)
-            this._setHighlightTimeout();
+          this._setHighlightTimeout();
         ]]></body>
       </method>
 
       <field name="_strBundle">null</field>
       <property name="strBundle">
         <getter><![CDATA[
           if (!this._strBundle) {
             this._strBundle =
@@ -1021,18 +1037,17 @@
             // Getting here means the user commanded a find op. Make sure any
             // initial prefilling is ignored if it hasn't happened yet.
             if (this._startFindDeferred) {
               this._startFindDeferred.resolve();
               this._startFindDeferred = null;
             }
 
             this._enableFindButtons(val);
-            if (this.getElement("highlight").checked)
-              this._setHighlightTimeout();
+            this._setHighlightTimeout();
 
             this._updateCaseSensitivity(val);
             this._updateEntireWord();
 
             this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
                                          this._findMode != this.FIND_NORMAL);
           }
 
@@ -1065,28 +1080,16 @@
           }
 
           this.setAttribute("flash",
                             (this._flashFindBarCount % 2 == 0) ?
                             "false" : "true");
         ]]></body>
       </method>
 
-      <method name="_setHighlightTimeout">
-        <body><![CDATA[
-          if (this._highlightTimeout)
-            clearTimeout(this._highlightTimeout);
-          this._highlightTimeout =
-            setTimeout(function(aSelf) {
-                         aSelf.toggleHighlight(false);
-                         aSelf.toggleHighlight(true);
-                       }, 500, this);
-        ]]></body>
-      </method>
-
       <method name="_findAgain">
         <parameter name="aFindPrevious"/>
         <body><![CDATA[
           this.browser.finder.findAgain(aFindPrevious,
                                         this._findMode == this.FIND_LINKS,
                                         this._findMode != this.FIND_NORMAL);
         ]]></body>
       </method>
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -129,20 +129,22 @@ Finder.prototype = {
       return;
 
     ClipboardHelper.copyStringToClipboard(aSearchString,
                                           Ci.nsIClipboard.kFindClipboard);
   },
 
   set caseSensitive(aSensitive) {
     this._fastFind.caseSensitive = aSensitive;
+    this.iterator.reset();
   },
 
   set entireWord(aEntireWord) {
     this._fastFind.entireWord = aEntireWord;
+    this.iterator.reset();
   },
 
   get highlighter() {
     if (this._highlighter)
       return this._highlighter;
 
     const {FinderHighlighter} = Cu.import("resource://gre/modules/FinderHighlighter.jsm", {});
     return this._highlighter = new FinderHighlighter(this);
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -14,16 +14,17 @@ Cu.import("resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
 XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
   const kDebugPref = "findbar.modalHighlight.debug";
   return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
 });
 
 const kModalHighlightRepaintFreqMs = 10;
+const kHighlightAllPref = "findbar.highlightAll";
 const kModalHighlightPref = "findbar.modalHighlight";
 const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
   "font-size-adjust", "font-stretch", "font-variant", "font-weight", "letter-spacing",
   "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
 const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
   let parts = prop.split("-");
   return parts.shift() + parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("");
 });
@@ -95,17 +96,16 @@ const kModalStyle = `
   background: #fff;
   border: 1px solid #666;
   position: absolute;
 }
 
 .findbar-modalHighlight-outlineMask[brighttext] > .findbar-modalHighlight-rect {
   background: #000;
 }`;
-const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function mockAnonymousContentNode(domNode) {
   return {
     setTextContentForElement(id, text) {
       (domNode.querySelector("#" + id) || domNode).textContent = text;
     },
     getAttributeForElement(id, attrName) {
       let node = domNode.querySelector("#" + id) || domNode;
@@ -132,18 +132,21 @@ function mockAnonymousContentNode(domNod
 
 /**
  * FinderHighlighter class that is used by Finder.jsm to take care of the
  * 'Highlight All' feature, which can highlight all find occurrences in a page.
  *
  * @param {Finder} finder Finder.jsm instance
  */
 function FinderHighlighter(finder) {
+  this._currentFoundRange = null;
+  this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
+  this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
   this.finder = finder;
-  this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
+  this.visible = false;
 }
 
 FinderHighlighter.prototype = {
   get iterator() {
     if (this._iterator)
       return this._iterator;
     this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
     return this._iterator;
@@ -254,68 +257,67 @@ FinderHighlighter.prototype = {
       this._addEditorListeners(editableNode.editor);
     }
   },
 
   /**
    * If modal highlighting is enabled, show the dimmed background that will overlay
    * the page.
    *
-   * @param  {nsIDOMWindow} window The dimmed background will overlay this window.
-   *                               Optional, defaults to the finder window.
-   * @return {AnonymousContent}    Reference to the node inserted into the
-   *                               CanvasFrame. It'll also be stored in the
-   *                               `_modalHighlightOutline` member variable.
+   * @param {nsIDOMWindow} window The dimmed background will overlay this window.
+   *                              Optional, defaults to the finder window.
    */
   show(window = null) {
-    if (!this._modal)
-      return null;
+    if (!this._modal || this.visible)
+      return;
 
+    this.visible = true;
     window = window || this.finder._getWindow();
-    let anonNode = this._maybeCreateModalHighlightNodes(window);
+    this._maybeCreateModalHighlightNodes(window);
     this._addModalHighlightListeners(window);
-
-    return anonNode;
   },
 
   /**
    * Clear all highlighted matches. If modal highlighting is enabled and
    * the outline + dimmed background is currently visible, both will be hidden.
+   *
+   * @param {nsIDOMWindow} window    The dimmed background will overlay this window.
+   *                                 Optional, defaults to the finder window.
+   * @param {nsIDOMRange}  skipRange A range that should not be removed from the
+   *                                 find selection.
    */
-  hide(window = null) {
+  hide(window = null, skipRange = null) {
     window = window || this.finder._getWindow();
 
     let doc = window.document;
-    let controller = this.finder._getSelectionController(window);
-    let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
-    sel.removeAllRanges();
+    this._clearSelection(this.finder._getSelectionController(window), skipRange);
 
     // Next, check our editor cache, for editors belonging to this
     // document
     if (this._editors) {
       for (let x = this._editors.length - 1; x >= 0; --x) {
         if (this._editors[x].document == doc) {
-          sel = this._editors[x].selectionController
-                                .getSelection(Ci.nsISelectionController.SELECTION_FIND);
-          sel.removeAllRanges();
+          this._clearSelection(this._editors[x].selectionController, skipRange);
           // We don't need to listen to this editor any more
           this._unhookListenersAtIndex(x);
         }
       }
     }
 
-    if (!this._modal)
+    if (!this._modal || !this.visible)
       return;
 
     if (this._modalHighlightOutline)
       this._modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
 
     this._removeHighlightAllMask(window);
     this._removeModalHighlightListeners(window);
     delete this._brightText;
+
+    this.visible = false;
   },
 
   /**
    * Called by the Finder after a find result comes in; update the position and
    * content of the outline to the newly found occurrence.
    * To make sure that the outline covers the found range completely, all the
    * CSS styles that influence the text are copied and applied to the outline.
    *
@@ -330,62 +332,79 @@ FinderHighlighter.prototype = {
    *   {String}  linkURL       If a link was hit, this will contain a URL string.
    *   {Rect}    rect          An object with top, left, width and height
    *                           coordinates of the current selection.
    *   {String}  searchString  The string the search was performed with.
    *   {Boolean} storeResult   Indicator if the search string should be stored
    *                           by the consumer of the Finder.
    */
   update(data) {
-    if (!this._modal)
+    let window = this.finder._getWindow();
+    let foundRange = this.finder._fastFind.getFoundRange();
+    if (!this._modal) {
+      if (this._highlightAll) {
+        this.hide(window, foundRange);
+        let params = this.iterator.params;
+        if (params.word)
+          this.highlight(true, params.word, params.linksOnly);
+      }
       return;
+    }
 
     // Place the match placeholder on top of the current found range.
-    let foundRange = this.finder._fastFind.getFoundRange();
     if (data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND || !foundRange) {
       this.hide();
       return;
     }
 
-    let window = this.finder._getWindow();
-    let textContent = this._getRangeContentArray(foundRange);
-    if (!textContent.length) {
-      this.hide(window);
-      return;
-    }
+    let outlineNode;
+    if (foundRange !== this._currentFoundRange || data.findAgain) {
+      this._currentFoundRange = foundRange;
+
+      let textContent = this._getRangeContentArray(foundRange);
+      if (!textContent.length) {
+        this.hide(window);
+        return;
+      }
+
+      let rect = foundRange.getBoundingClientRect();
+      let fontStyle = this._getRangeFontStyle(foundRange);
+      if (typeof this._brightText == "undefined") {
+        this._brightText = this._isColorBright(fontStyle.color);
+      }
 
-    let rect = foundRange.getBoundingClientRect();
-    let fontStyle = this._getRangeFontStyle(foundRange);
-    if (typeof this._brightText == "undefined") {
-      this._brightText = this._isColorBright(fontStyle.color);
+      // Text color in the outline is determined by our stylesheet.
+      delete fontStyle.color;
+
+      if (!this.visible)
+        this.show(window);
+      else
+        this._maybeCreateModalHighlightNodes(window);
+
+      outlineNode = this._modalHighlightOutline;
+      outlineNode.setTextContentForElement(kModalOutlineId + "-text", textContent.join(" "));
+      outlineNode.setAttributeForElement(kModalOutlineId + "-text", "style",
+        this._getHTMLFontStyle(fontStyle));
+
+      if (typeof outlineNode.getAttributeForElement(kModalOutlineId, "hidden") == "string")
+        outlineNode.removeAttributeForElement(kModalOutlineId, "hidden");
+      let { scrollX, scrollY } = this._getScrollPosition(window);
+      outlineNode.setAttributeForElement(kModalOutlineId, "style",
+        `top: ${scrollY + rect.top}px; left: ${scrollX + rect.left}px`);
     }
 
-    // Text color in the outline is determined by our stylesheet.
-    delete fontStyle.color;
-
-    let anonNode = this.show(window);
-
-    anonNode.setTextContentForElement(kModalOutlineId + "-text", textContent.join(" "));
-    anonNode.setAttributeForElement(kModalOutlineId + "-text", "style",
-      this._getHTMLFontStyle(fontStyle));
-
-    if (typeof anonNode.getAttributeForElement(kModalOutlineId, "hidden") == "string")
-      anonNode.removeAttributeForElement(kModalOutlineId, "hidden");
-    let { scrollX, scrollY } = this._getScrollPosition(window);
-    anonNode.setAttributeForElement(kModalOutlineId, "style",
-      `top: ${scrollY + rect.top}px; left: ${scrollX + rect.left}px`);
-
-    if (typeof anonNode.getAttributeForElement(kModalOutlineId, "grow") == "string")
+    outlineNode = this._modalHighlightOutline;
+    if (typeof outlineNode.getAttributeForElement(kModalOutlineId, "grow") == "string")
       return;
 
     window.requestAnimationFrame(() => {
-      anonNode.setAttributeForElement(kModalOutlineId, "grow", true);
+      outlineNode.setAttributeForElement(kModalOutlineId, "grow", true);
       this._listenForOutlineEvent(kModalOutlineId, "transitionend", () => {
         try {
-          anonNode.removeAttributeForElement(kModalOutlineId, "grow");
+          outlineNode.removeAttributeForElement(kModalOutlineId, "grow");
         } catch (ex) {}
       });
     });
   },
 
   /**
    * Invalidates the list by clearing the map of highglighted ranges that we
    * keep to build the mask for.
@@ -436,23 +455,44 @@ FinderHighlighter.prototype = {
 
   /**
    * When 'Highlight All' is toggled during a session, this callback is invoked
    * and when it's turned off, the found occurrences will be removed from the mask.
    *
    * @param {Boolean} highlightAll
    */
   onHighlightAllChange(highlightAll) {
+    this._highlightAll = highlightAll;
     if (this._modal && !highlightAll) {
       this.clear();
       this._scheduleRepaintOfMask(this.finder._getWindow());
     }
   },
 
   /**
+   * Utility; removes all ranges from the find selection that belongs to a
+   * controller. Optionally skips a specific range.
+   *
+   * @param  {nsISelectionController} controller
+   * @param  {nsIDOMRange}            skipRange
+   */
+  _clearSelection(controller, skipRange = null) {
+    let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
+    if (!skipRange) {
+      sel.removeAllRanges();
+    } else {
+      for (let i = sel.rangeCount - 1; i >= 0; --i) {
+        let range = sel.getRangeAt(i);
+        if (range !== skipRange)
+          sel.removeRange(range);
+      }
+    }
+  },
+
+  /**
    * Utility; get the nsIDOMWindowUtils for a window.
    *
    * @param  {nsIDOMWindow} window Optional, defaults to the finder window.
    * @return {nsIDOMWindowUtils}
    */
   _getDWU(window = null) {
     return (window || this.finder._getWindow())
       .QueryInterface(Ci.nsIInterfaceRequestor)
@@ -600,35 +640,37 @@ FinderHighlighter.prototype = {
     // sorts.
     this._scheduleRepaintOfMask(window);
   },
 
   /**
    * Lazily insert the nodes we need as anonymous content into the CanvasFrame
    * of a window.
    *
-   * @param  {nsIDOMWindow} window Window to draw in.
-   * @return {AnonymousContent} The reference to the outline node, NOT the mask.
+   * @param {nsIDOMWindow} window Window to draw in.
    */
   _maybeCreateModalHighlightNodes(window) {
     if (this._modalHighlightOutline) {
-      if (!this._modalHighlightAllMask)
-        this._repaintHighlightAllMask(window);
-      return this._modalHighlightOutline;
+      if (!this._modalHighlightAllMask) {
+        // Make sure to at least show the dimmed background.
+        this._repaintHighlightAllMask(window, false);
+        this._scheduleRepaintOfMask(window);
+      }
+      return;
     }
 
     let document = window.document;
     // A hidden document doesn't accept insertAnonymousContent calls yet.
     if (document.hidden) {
       let onVisibilityChange = () => {
         document.removeEventListener("visibilitychange", onVisibilityChange);
         this._maybeCreateModalHighlightNodes(window);
       };
       document.addEventListener("visibilitychange", onVisibilityChange);
-      return null;
+      return;
     }
 
     this._maybeInstallStyleSheet(window);
 
     // The outline needs to be sitting inside a container, otherwise the anonymous
     // content API won't find it by its ID later...
     let container = document.createElement("div");
 
@@ -636,58 +678,60 @@ FinderHighlighter.prototype = {
     let outlineBox = document.createElement("div");
     outlineBox.setAttribute("id", kModalOutlineId);
     outlineBox.className = kModalOutlineId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : "");
     let outlineBoxText = document.createElement("span");
     outlineBoxText.setAttribute("id", kModalOutlineId + "-text");
     outlineBox.appendChild(outlineBoxText);
 
     container.appendChild(outlineBox);
-
-    this._repaintHighlightAllMask(window);
-
     this._modalHighlightOutline = kDebug ?
       mockAnonymousContentNode(document.body.appendChild(container.firstChild)) :
       document.insertAnonymousContent(container);
-    return this._modalHighlightOutline;
+
+    // Make sure to at least show the dimmed background.
+    this._repaintHighlightAllMask(window, false);
   },
 
   /**
    * Build and draw the mask that takes care of the dimmed background that
    * overlays the current page and the mask that cuts out all the rectangles of
    * the ranges that were found.
    *
    * @param {nsIDOMWindow} window Window to draw in.
+   * @param {Boolean} [paintContent]
    */
-  _repaintHighlightAllMask(window) {
+  _repaintHighlightAllMask(window, paintContent = true) {
     let document = window.document;
 
     const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
     let maskNode = document.createElement("div");
 
     // Make sure the dimmed mask node takes the full width and height that's available.
     let {width, height} = this._getWindowDimensions(window);
     maskNode.setAttribute("id", kMaskId);
     maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
     maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
     if (this._brightText)
       maskNode.setAttribute("brighttext", "true");
 
-    // Create a DOM node for each rectangle representing the ranges we found.
-    let maskContent = [];
-    const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
-    if (this._modalHighlightRectsMap) {
-      for (let rects of this._modalHighlightRectsMap.values()) {
-        for (let rect of rects) {
-          maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
-            left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
+    if (paintContent) {
+      // Create a DOM node for each rectangle representing the ranges we found.
+      let maskContent = [];
+      const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
+      if (this._modalHighlightRectsMap) {
+        for (let rects of this._modalHighlightRectsMap.values()) {
+          for (let rect of rects) {
+            maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
+              left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
+          }
         }
       }
+      maskNode.innerHTML = maskContent.join("");
     }
-    maskNode.innerHTML = maskContent.join("");
 
     // Always remove the current mask and insert it a-fresh, because we're not
     // free to alter DOM nodes inside the CanvasFrame.
     this._removeHighlightAllMask(window);
 
     this._modalHighlightAllMask = kDebug ?
       mockAnonymousContentNode(document.body.appendChild(maskNode)) :
       document.insertAnonymousContent(maskNode);
--- a/toolkit/modules/FinderIterator.jsm
+++ b/toolkit/modules/FinderIterator.jsm
@@ -24,16 +24,20 @@ this.FinderIterator = {
   _previousRanges: [],
   _spawnId: 0,
   ranges: [],
   running: false,
 
   // Expose `kIterationSizeMax` to the outside world for unit tests to use.
   get kIterationSizeMax() { return kIterationSizeMax },
 
+  get params() {
+    return Object.assign({}, this._currentParams || this._previousParams);
+  },
+
   /**
    * Start iterating the active Finder docShell, using the options below. When
    * it already started at the request of another consumer, we first yield the
    * results we already collected before continuing onward to yield fresh results.
    * We make sure to pause every `kIterationSizeMax` iterations to make sure we
    * don't block the host process too long. In the case of a break like this, we
    * yield `undefined`, instead of a range.
    * Upon re-entrance after a break, we check if `stop()` was called during the
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -204,17 +204,17 @@ var dataProviders = {
                        getService(Ci.nsIURLFormatter);
     try {
       data.supportURL = urlFormatter.formatURLPref("app.support.baseURL");
     }
     catch (e) {}
 
     data.numTotalWindows = 0;
     data.numRemoteWindows = 0;
-    let winEnumer = Services.ww.getWindowEnumerator("navigator:browser");
+    let winEnumer = Services.wm.getEnumerator("navigator:browser");
     while (winEnumer.hasMoreElements()) {
       data.numTotalWindows++;
       let remote = winEnumer.getNext().
                    QueryInterface(Ci.nsIInterfaceRequestor).
                    getInterface(Ci.nsIWebNavigation).
                    QueryInterface(Ci.nsILoadContext).
                    useRemoteTabs;
       if (remote) {
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -60,16 +60,17 @@ function* testInstall(browser, url, step
       "onDownloadFailed",
       "onInstallStarted",
       "onInstallEnded",
       "onInstallCancelled",
       "onInstallFailed",
     ];
     let eventWaiter = null;
     let receivedEvents = [];
+    let prevEvent = null;
     events.forEach(event => {
       install.addEventListener(event, e => {
         receivedEvents.push({
           event,
           state: install.state,
           error: install.error,
           progress: install.progress,
           maxProgress: install.maxProgress,
@@ -82,37 +83,45 @@ function* testInstall(browser, url, step
 
     // Returns a promise that is resolved when the given event occurs
     // or rejects if a different event comes first or if props is supplied
     // and properties on the AddonInstall don't match those in props.
     function expectEvent(event, props) {
       return new Promise((resolve, reject) => {
         function check() {
           let received = receivedEvents.shift();
+          // Skip any repeated onDownloadProgress events.
+          while (received &&
+                 received.event == prevEvent &&
+                 prevEvent == "onDownloadProgress") {
+            received = receivedEvents.shift();
+          }
+          // Wait for more events if we skipped all there were.
+          if (!received) {
+            eventWaiter = () => {
+              eventWaiter = null;
+              check();
+            }
+            return;
+          }
+          prevEvent = received.event;
           if (received.event != event) {
             let err = new Error(`expected ${event} but got ${received.event}`);
             reject(err);
           }
           if (props) {
             for (let key of Object.keys(props)) {
               if (received[key] != props[key]) {
                 throw new Error(`AddonInstall property ${key} was ${received[key]} but expected ${props[key]}`);
               }
             }
           }
           resolve();
         }
-        if (receivedEvents.length > 0) {
-          check();
-        } else {
-          eventWaiter = () => {
-            eventWaiter = null;
-            check();
-          }
-        }
+        check();
       });
     }
 
     while (steps.length > 0) {
       let nextStep = steps.shift();
       if (nextStep.action) {
         if (nextStep.action == "install") {
           yield install.install();