Bug 1280978 - don't show the modal highlighting ui on very large pages, because it degrades performance too much. r?jaws draft
authorMike de Boer <mdeboer@mozilla.com>
Mon, 09 Jan 2017 16:33:54 +0100
changeset 457666 c6c23985140f88c48dad0ee7f0ae4f07e13c07ad
parent 457629 701868bfddcba5bdec516be33a86dcd525dc74cf
child 541557 def74846ec0eb6ed0953299eb8fdea5ab5836110
push id40858
push usermdeboer@mozilla.com
push dateMon, 09 Jan 2017 15:38:24 +0000
reviewersjaws
bugs1280978
milestone53.0a1
Bug 1280978 - don't show the modal highlighting ui on very large pages, because it degrades performance too much. r?jaws MozReview-Commit-ID: IQOsxKtBW09
toolkit/content/widgets/findbar.xml
toolkit/modules/FinderHighlighter.jsm
toolkit/modules/RemoteFinder.jsm
widget/nsXPLookAndFeel.cpp
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -1311,16 +1311,23 @@
 
       <method name="onHighlightFinished">
         <parameter name="result"/>
         <body><![CDATA[
           // Noop.
         ]]></body>
       </method>
 
+      <method name="onModalHighlightShowing">
+        <parameter name="data"/>
+        <body><![CDATA[
+          this._prefsvc.setBoolPref("findbar.modalHighlight.showing", data.showing);
+        ]]></body>
+      </method>
+
       <method name="onCurrentSelection">
         <parameter name="aSelectionString" />
         <parameter name="aIsInitialSelection" />
         <body><![CDATA[
           // Ignore the prefill if the user has already typed in the findbar,
           // it would have been overwritten anyway. See bug 1198465.
           if (aIsInitialSelection && !this._startFindDeferred)
             return;
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -179,29 +179,62 @@ FinderHighlighter.prototype = {
         modalHighlightRectsMap: new Map(),
         previousRangeRectsAndTexts: { rectList: [], textList: [] }
       });
     }
     return gWindows.get(window);
   },
 
   /**
-   * Notify all registered listeners that the 'Highlight All' operation finished.
+   * Notify all registered listeners of a specific event.
    *
-   * @param {Boolean} highlight Whether highlighting was turned on
+   * @param  {String} messageName Name of the message handler
+   * @param  {Object} [message]   Arguments for the message handler. Optional.
    */
-  notifyFinished(highlight) {
+  _notify(messageName, message) {
     for (let l of this.finder._listeners) {
       try {
-        l.onHighlightFinished(highlight);
+        l[messageName](message);
       } catch (ex) {}
     }
   },
 
   /**
+   * Check if we should show the modal highlighting UI. Currently, this depends
+   * on two factors:
+   * 1. Yes, when the 'findbar.modalHighlight' pref is set to TRUE.
+   * 2. Yes, when the page is not too large to be efficiently highlighted by the
+   *    modal UI.
+   * Since drawing semi-transparent overlays on top of very large document doesn't
+   * scale, we have to turn the feature off based on the `kPageIsTooBigPx`
+   * threshold as hardcoded above.
+   *
+   * @param  {Object}       dict     Dictionary of properties belonging to the
+   *                                 currently active window
+   * @param  {nsIDOMWindow} [window] The dimmed background is overlaying this window.
+   *                                 Optional.
+   * @return {Boolean}
+   */
+  shouldShowModalUI(dict, window = null) {
+    if (!dict || !this._modal)
+      return this._modal;
+
+    if (this._isPageTooBig(dict)) {
+      if (window && dict.modalHighlightAllMask) {
+        this._removeRangeOutline(window);
+        this._removeHighlightAllMask(window);
+        this._removeModalHighlightListeners(window);
+      }
+      return false;
+    }
+
+    return true;
+  },
+
+  /**
    * Toggle highlighting all occurrences of a word in a page. This method will
    * be called recursively for each (i)frame inside a page.
    *
    * @param {Booolean} highlight Whether highlighting should be turned on
    * @param {String}   word      Needle to search for and highlight when found
    * @param {Boolean}  linksOnly Only consider nodes that are links for the search
    * @yield {Promise}  that resolves once the operation has finished
    */
@@ -224,32 +257,33 @@ FinderHighlighter.prototype = {
         caseSensitive: this.finder._fastFind.caseSensitive,
         entireWord: this.finder._fastFind.entireWord,
         linksOnly, word,
         finder: this.finder,
         listener: this,
         useCache: true,
         window
       };
-      if (this._modal && this.iterator._areParamsEqual(params, dict.lastIteratorParams))
+      if (this.shouldShowModalUI(dict) && this.iterator._areParamsEqual(params, dict.lastIteratorParams))
         return this._found;
 
-      if (!this._modal)
+      if (!this.shouldShowModalUI(dict, window))
         dict.visible = true;
       yield this.iterator.start(params);
       if (this._found)
         this.finder._outlineLink(true);
     } else {
       this.hide(window);
 
       // Removing the highlighting always succeeds, so return true.
       this._found = true;
     }
 
-    this.notifyFinished({ highlight, found: this._found });
+    // Notify all registered listeners that the 'Highlight All' operation finished.
+    this._notify("onHighlightFinished", { highlight, found: this._found });
 
     return this._found;
   }),
 
   // FinderIterator listener implementation
 
   onIteratorRangeFound(range) {
     this.highlightRange(range);
@@ -262,17 +296,17 @@ FinderHighlighter.prototype = {
     this.clear(this.finder._getWindow());
   },
 
   onIteratorStart(params) {
     let window = this.finder._getWindow();
     let dict = this.getForWindow(window);
     // Save a clean params set for use later in the `update()` method.
     dict.lastIteratorParams = params;
-    if (!this._modal)
+    if (!this.shouldShowModalUI(dict))
       this.hide(window, this.finder._fastFind.getFoundRange());
     this.clear(window);
   },
 
   /**
    * Add a range to the find selection, i.e. highlight it, and if it's inside an
    * editable node, track it.
    *
@@ -281,25 +315,25 @@ FinderHighlighter.prototype = {
   highlightRange(range) {
     let node = range.startContainer;
     let editableNode = this._getEditableNode(node);
     let window = node.ownerDocument.defaultView;
     let controller = this.finder._getSelectionController(window);
     if (editableNode) {
       controller = editableNode.editor.selectionController;
     }
+    let dict = this.getForWindow(window.top);
 
-    if (this._modal) {
+    if (this.shouldShowModalUI(dict, window)) {
       this._modalHighlight(range, controller, window);
     } else {
       let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
       findSelection.addRange(range);
       // Check if the range is inside an iframe.
       if (window != window.top) {
-        let dict = this.getForWindow(window.top);
         if (!dict.frames.has(window))
           dict.frames.set(window, null);
       }
     }
 
     if (editableNode) {
       // Highlighting added, so cache this editor, and hook up listeners
       // to ensure we deal properly with edits within the highlighting
@@ -312,20 +346,21 @@ FinderHighlighter.prototype = {
    * the page.
    *
    * @param {nsIDOMWindow} window The dimmed background will overlay this window.
    *                              Optional, defaults to the finder window.
    */
   show(window = null) {
     window = (window || this.finder._getWindow()).top;
     let dict = this.getForWindow(window);
-    if (!this._modal || dict.visible)
+    if (!this.shouldShowModalUI(dict, window) || dict.visible)
       return;
 
     dict.visible = true;
+    this._notify("onModalHighlightShowing", { showing: true });
 
     this._maybeCreateModalHighlightNodes(window);
     this._addModalHighlightListeners(window);
   },
 
   /**
    * Clear all highlighted matches. If modal highlighting is enabled and
    * the outline + dimmed background is currently visible, both will be hidden.
@@ -378,16 +413,17 @@ FinderHighlighter.prototype = {
     }
     dict.lastWindowDimensions = { width: 0, height: 0 };
 
     this._removeRangeOutline(window);
     this._removeHighlightAllMask(window);
     this._removeModalHighlightListeners(window);
 
     dict.visible = false;
+    this._notify("onModalHighlightShowing", { showing: 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.
    *
@@ -411,17 +447,17 @@ FinderHighlighter.prototype = {
     let dict = this.getForWindow(window);
     let foundRange = this.finder._fastFind.getFoundRange();
 
     if (data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND || !data.searchString || !foundRange) {
       this.hide();
       return;
     }
 
-    if (!this._modal) {
+    if (!this.shouldShowModalUI(dict, window)) {
       if (this._highlightAll) {
         dict.previousFoundRange = dict.currentFoundRange;
         dict.currentFoundRange = foundRange;
         let params = this.iterator.params;
         if (dict.visible && this.iterator._areParamsEqual(params, dict.lastIteratorParams))
           return;
         if (!dict.visible && !params)
           params = {word: data.searchString, linksOnly: data.linksOnly};
@@ -437,17 +473,17 @@ FinderHighlighter.prototype = {
 
       if (!dict.visible)
         this.show(window);
       else
         this._maybeCreateModalHighlightNodes(window);
     }
 
     let outlineNode = dict.modalHighlightOutline;
-    if (outlineNode && !this._isPageTooBig(dict)) {
+    if (outlineNode) {
       let animation;
       if (dict.animations) {
         for (animation of dict.animations)
           animation.finish();
       }
       dict.animations = [];
       for (let i = dict.previousRangeRectsAndTexts.rectList.length - 1; i >= 0; --i) {
         animation = outlineNode.setAnimationForElement(kModalOutlineId + i,
@@ -523,17 +559,18 @@ FinderHighlighter.prototype = {
    * and when it's turned off, the found occurrences will be removed from the mask.
    *
    * @param {Boolean} highlightAll
    */
   onHighlightAllChange(highlightAll) {
     this._highlightAll = highlightAll;
     if (!highlightAll) {
       let window = this.finder._getWindow();
-      if (!this._modal)
+      let dict = this.getForWindow(window.top);
+      if (!this.shouldShowModalUI(dict))
         this.hide(window);
       this.clear(window);
       this._scheduleRepaintOfMask(window);
     }
   },
 
   /**
    * Utility; removes all ranges from the find selection that belongs to a
@@ -1195,21 +1232,21 @@ FinderHighlighter.prototype = {
    *   {Boolean} scrollOnly      TRUE when the page has scrolled in the meantime,
    *                             which means that the dynamically positioned
    *                             elements need to be repainted.
    *   {Boolean} updateAllRanges Whether to recalculate the rects of all ranges
    *                             that were found up until now.
    */
   _scheduleRepaintOfMask(window, { contentChanged, scrollOnly, updateAllRanges } =
                                  { contentChanged: false, scrollOnly: false, updateAllRanges: false }) {
-    if (!this._modal)
+    window = window.top;
+    let dict = this.getForWindow(window);
+    if (!this.shouldShowModalUI(dict, window))
       return;
 
-    window = window.top;
-    let dict = this.getForWindow(window);
     let hasDynamicRanges = !!dict.dynamicRangesSet.size;
     let pageIsTooBig = this._isPageTooBig(dict);
     let repaintDynamicRanges = ((scrollOnly || contentChanged) && hasDynamicRanges
       && !pageIsTooBig);
 
     // When we request to repaint unconditionally, we mean to call
     // `_repaintHighlightAllMask()` right after the timeout.
     if (!dict.unconditionalRepaintRequested)
--- a/toolkit/modules/RemoteFinder.jsm
+++ b/toolkit/modules/RemoteFinder.jsm
@@ -79,16 +79,20 @@ RemoteFinder.prototype = {
       case "Finder:CurrentSelectionResult":
         callback = "onCurrentSelection";
         params = [ aMessage.data.selection, aMessage.data.initial ];
         break;
       case "Finder:HighlightFinished":
         callback = "onHighlightFinished";
         params = [ aMessage.data ];
         break;
+      case "Finder:ModalHighlightShowing":
+        callback = "onModalHighlightShowing";
+        params = [ aMessage.data ];
+        break;
     }
 
     for (let l of this._listeners) {
       // Don't let one callback throwing stop us calling the rest
       try {
         l[callback].apply(l, params);
       } catch (e) {
         if (!l[callback]) {
@@ -246,16 +250,20 @@ RemoteFinderListener.prototype = {
   onMatchesCountResult(aData) {
     this._global.sendAsyncMessage("Finder:MatchesResult", aData);
   },
 
   onHighlightFinished(aData) {
     this._global.sendAsyncMessage("Finder:HighlightFinished", aData);
   },
 
+  onModalHighlightShowing(aData) {
+    this._global.sendAsyncMessage("Finder:ModalHighlightShowing", aData);
+  },
+
   receiveMessage(aMessage) {
     let data = aMessage.data;
 
     switch (aMessage.name) {
       case "Finder:CaseSensitive":
         this._finder.caseSensitive = data.caseSensitive;
         break;
 
--- a/widget/nsXPLookAndFeel.cpp
+++ b/widget/nsXPLookAndFeel.cpp
@@ -462,17 +462,17 @@ nsXPLookAndFeel::Init()
 
   Preferences::AddBoolVarCache(&sUseNativeColors,
                                "ui.use_native_colors",
                                sUseNativeColors);
   Preferences::AddBoolVarCache(&sUseStandinsForNativeColors,
                                "ui.use_standins_for_native_colors",
                                sUseStandinsForNativeColors);
   Preferences::AddBoolVarCache(&sFindbarModalHighlight,
-                               "findbar.modalHighlight",
+                               "findbar.modalHighlight.showing",
                                sFindbarModalHighlight);
 
   if (XRE_IsContentProcess()) {
     mozilla::dom::ContentChild* cc =
       mozilla::dom::ContentChild::GetSingleton();
 
     LookAndFeel::SetIntCache(cc->LookAndFeelCache());
     // This is only ever used once during initialization, and can be cleared now.