Bug 1302035 - implement an algorithm to better detect whether a page is dark or bright and use that to toggle the dimming mode of the find toolbar. r?jaws draft
authorMike de Boer <mdeboer@mozilla.com>
Fri, 23 Sep 2016 15:35:14 +0200
changeset 417018 f8df4a6945b4def8e47a75d0f92e70353630bd9b
parent 416979 583713a71198250a2e080b786e9fa597ce6db023
child 417019 85c1a5b271999915fcbfa0441f94a2351c2b1770
push id30311
push usermdeboer@mozilla.com
push dateFri, 23 Sep 2016 13:35:47 +0000
reviewersjaws
bugs1302035
milestone52.0a1
Bug 1302035 - implement an algorithm to better detect whether a page is dark or bright and use that to toggle the dimming mode of the find toolbar. r?jaws MozReview-Commit-ID: 2yJsHfivEBO
toolkit/modules/FinderHighlighter.jsm
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -15,16 +15,17 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
 XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
   const kDebugPref = "findbar.modalHighlight.debug";
   return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
 });
 
 const kContentChangeThresholdPx = 5;
+const kBrightTextSampleSize = 5;
 const kModalHighlightRepaintFreqMs = 100;
 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", "line-height",
   "letter-spacing", "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
 const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
   let parts = prop.split("-");
@@ -352,17 +353,16 @@ FinderHighlighter.prototype = {
     if (dict.modalHighlightOutline) {
       dict.modalHighlightOutline.setAttributeForElement(kModalOutlineId, "style",
         dict.modalHighlightOutline.getAttributeForElement(kModalOutlineId, "style") +
         "; opacity: 0");
     }
 
     this._removeHighlightAllMask(window);
     this._removeModalHighlightListeners(window);
-    delete dict.brightText;
 
     dict.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
@@ -408,30 +408,25 @@ FinderHighlighter.prototype = {
       dict.currentFoundRange = foundRange;
 
       let textContent = this._getRangeContentArray(foundRange);
       if (!textContent.length) {
         this.hide(window);
         return;
       }
 
-      let fontStyle = this._getRangeFontStyle(foundRange);
-      if (typeof dict.brightText == "undefined") {
-        dict.brightText = this._isColorBright(fontStyle.color);
-      }
-
       if (data.findAgain)
         dict.updateAllRanges = true;
 
       if (!dict.visible)
         this.show(window);
       else
         this._maybeCreateModalHighlightNodes(window);
 
-      this._updateRangeOutline(dict, textContent, fontStyle);
+      this._updateRangeOutline(dict, textContent);
     }
 
     let outlineNode = dict.modalHighlightOutline;
     if (outlineNode) {
       if (dict.animation)
         dict.animation.finish();
       dict.animation = outlineNode.setAnimationForElement(kModalOutlineId,
         Cu.cloneInto(kModalOutlineAnim.keyframes, window), kModalOutlineAnim.duration);
@@ -457,16 +452,17 @@ FinderHighlighter.prototype = {
     }
 
     let dict = this.getForWindow(window.top);
     if (dict.animation)
       dict.animation.finish();
     dict.dynamicRangesSet.clear();
     dict.frames.clear();
     dict.modalHighlightRectsMap.clear();
+    dict.brightText = null;
   },
 
   /**
    * When the current page is refreshed or navigated away from, the CanvasFrame
    * contents is not valid anymore, i.e. all anonymous content is destroyed.
    * We need to clear the references we keep, which'll make sure we redraw
    * everything when the user starts to find in page again.
    */
@@ -707,16 +703,58 @@ FinderHighlighter.prototype = {
     cssColor = cssColor.match(kRGBRE);
     if (!cssColor || !cssColor.length)
       return false;
     cssColor.shift();
     return new Color(...cssColor).isBright;
   },
 
   /**
+   * Detects if the overall text color in the page can be described as bright.
+   * This is done according to the following algorithm:
+   *  1. With the entire set of ranges that we have found thusfar;
+   *  2. Get an odd-numbered `sampleSize`, with a maximum of `kBrightTextSampleSize`
+   *     ranges,
+   *  3. Slice the set of ranges into `sampleSize` number of equal parts,
+   *  4. Grab the first range for each slice and inspect the brightness of the
+   *     color of its text content.
+   *  5. When the majority of ranges are counted as contain bright colored text,
+   *     the page is considered to contain bright text overall.
+   *
+   * @param {Object} dict Dictionary of properties belonging to the
+   *                      currently active window. The page text color property
+   *                      will be recorded in `dict.brightText` as `true` or `false`.
+   */
+  _detectBrightText(dict) {
+    let sampleSize = Math.min(dict.modalHighlightRectsMap.size, kBrightTextSampleSize);
+    let ranges = [...dict.modalHighlightRectsMap.keys()];
+    let rangesCount = ranges.length;
+    // Make sure the sample size is an odd number.
+    if (sampleSize % 2 == 0) {
+      // Make the currently found range weigh heavier.
+      if (dict.currentFoundRange) {
+        ranges.push(dict.currentFoundRange);
+        ++sampleSize;
+        ++rangesCount;
+      } else {
+        --sampleSize;
+      }
+    }
+    let brightCount = 0;
+    for (let i = 0; i < sampleSize; ++i) {
+      let range = ranges[Math.floor((rangesCount / sampleSize) * i)];
+      let fontStyle = this._getRangeFontStyle(range);
+      if (this._isColorBright(fontStyle.color))
+        ++brightCount;
+    }
+
+    dict.brightText = (brightCount >= Math.ceil(sampleSize / 2));
+  },
+
+  /**
    * Checks if a range is inside a DOM node that's positioned in a way that it
    * doesn't scroll along when the document is scrolled and/ or zoomed. This
    * is the case for 'fixed' and 'sticky' positioned elements and elements inside
    * (i)frames.
    *
    * @param  {nsIDOMRange} range Range that be enclosed in a dynamic container
    * @return {Boolean}
    */
@@ -1007,16 +1045,18 @@ FinderHighlighter.prototype = {
       maskNode.setAttribute("id", kMaskId);
       dict.modalHighlightAllMask = kDebug ?
         mockAnonymousContentNode((document.body || document.documentElement).appendChild(maskNode)) :
         document.insertAnonymousContent(maskNode);
     }
 
     // Make sure the dimmed mask node takes the full width and height that's available.
     let {width, height} = dict.lastWindowDimensions = this._getWindowDimensions(window);
+    if (typeof dict.brightText != "boolean" || dict.updateAllRanges)
+      this._detectBrightText(dict);
     let maskStyle = this._getStyleString(kModalStyles.maskNode,
       [ ["width", width + "px"], ["height", height + "px"] ],
       dict.brightText ? kModalStyles.maskNodeBrightText : [],
       kDebug ? kModalStyles.maskNodeDebug : []);
     dict.modalHighlightAllMask.setAttributeForElement(kMaskId, "style", maskStyle);
     if (dict.brightText)
       dict.modalHighlightAllMask.setAttributeForElement(kMaskId, "brighttext", "true");