--- 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/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