зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1279695 - update the position of ranges that are inside fixed, sticky positioned container nodes or frameset or iframe at each repaint or scroll. r=jaws
MozReview-Commit-ID: J8YK1s2kArq
This commit is contained in:
Родитель
56661a22a9
Коммит
915c0ea08b
|
@ -484,7 +484,7 @@ skip-if = (os == "win" && !debug)
|
|||
[browser_windowopen_reflows.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_zbug569342.js]
|
||||
skip-if = e10s # Bug 1094240 - has findbar-related failures
|
||||
skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
|
||||
[browser_registerProtocolHandler_notification.js]
|
||||
[browser_no_mcb_on_http_site.js]
|
||||
tags = mcb
|
||||
|
|
|
@ -13,13 +13,14 @@ Cu.import("resource://gre/modules/Task.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
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 kModalHighlightRepaintFreqMs = 100;
|
||||
const kModalHighlightRepaintFreqMs = 200;
|
||||
const kHighlightAllPref = "findbar.highlightAll";
|
||||
const kModalHighlightPref = "findbar.modalHighlight";
|
||||
const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
|
||||
|
@ -144,6 +145,8 @@ function mockAnonymousContentNode(domNode) {
|
|||
};
|
||||
}
|
||||
|
||||
let gWindows = new Map();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -151,12 +154,9 @@ function mockAnonymousContentNode(domNode) {
|
|||
* @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._lastIteratorParams = null;
|
||||
this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
|
||||
this.finder = finder;
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
FinderHighlighter.prototype = {
|
||||
|
@ -183,6 +183,34 @@ FinderHighlighter.prototype = {
|
|||
return this._modalStyleSheetURI;
|
||||
},
|
||||
|
||||
/**
|
||||
* Each window is unique, globally, and the relation between an active
|
||||
* highlighting session and a window is 1:1.
|
||||
* For each window we track a number of properties which _at least_ consist of
|
||||
* - {Set} dynamicRangesSet Set of ranges that may move around, depending
|
||||
* on page layout changes and user input
|
||||
* - {Map} frames Collection of frames that were encountered
|
||||
* when inspecting the found ranges
|
||||
* - {Boolean} installedSheet Whether the modal stylesheet was loaded
|
||||
* already
|
||||
* - {Map} modalHighlightRectsMap Collection of ranges and their corresponding
|
||||
* Rects
|
||||
*
|
||||
* @param {nsIDOMWindow} window
|
||||
* @return {Object}
|
||||
*/
|
||||
getForWindow(window, propName = null) {
|
||||
if (!gWindows.has(window)) {
|
||||
gWindows.set(window, {
|
||||
dynamicRangesSet: new Set(),
|
||||
frames: new Map(),
|
||||
installedSheet: false,
|
||||
modalHighlightRectsMap: new Map()
|
||||
});
|
||||
}
|
||||
return gWindows.get(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify all registered listeners that the 'Highlight All' operation finished.
|
||||
*
|
||||
|
@ -207,6 +235,7 @@ FinderHighlighter.prototype = {
|
|||
*/
|
||||
highlight: Task.async(function* (highlight, word, linksOnly) {
|
||||
let window = this.finder._getWindow();
|
||||
let dict = this.getForWindow(window);
|
||||
let controller = this.finder._getSelectionController(window);
|
||||
let doc = window.document;
|
||||
this._found = false;
|
||||
|
@ -227,7 +256,7 @@ FinderHighlighter.prototype = {
|
|||
listener: this,
|
||||
useCache: true
|
||||
};
|
||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
||||
if (this.iterator._areParamsEqual(params, dict.lastIteratorParams))
|
||||
return this._found;
|
||||
if (params) {
|
||||
yield this.iterator.start(params);
|
||||
|
@ -252,17 +281,19 @@ FinderHighlighter.prototype = {
|
|||
},
|
||||
|
||||
onIteratorReset() {
|
||||
this.clear();
|
||||
this.clear(this.finder._getWindow());
|
||||
},
|
||||
|
||||
onIteratorRestart() {},
|
||||
|
||||
onIteratorStart(params) {
|
||||
let window = this.finder._getWindow();
|
||||
let dict = this.getForWindow(window);
|
||||
// Save a clean params set for use later in the `update()` method.
|
||||
this._lastIteratorParams = params;
|
||||
this.clear();
|
||||
dict.lastIteratorParams = params;
|
||||
this.clear(window);
|
||||
if (!this._modal)
|
||||
this.hide(this.finder._getWindow(), this.finder._fastFind.getFoundRange());
|
||||
this.hide(window, this.finder._fastFind.getFoundRange());
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -302,11 +333,13 @@ FinderHighlighter.prototype = {
|
|||
* Optional, defaults to the finder window.
|
||||
*/
|
||||
show(window = null) {
|
||||
if (!this._modal || this.visible)
|
||||
window = (window || this.finder._getWindow()).top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (!this._modal || dict.visible)
|
||||
return;
|
||||
|
||||
this.visible = true;
|
||||
window = window || this.finder._getWindow();
|
||||
dict.visible = true;
|
||||
|
||||
this._maybeCreateModalHighlightNodes(window);
|
||||
this._addModalHighlightListeners(window);
|
||||
},
|
||||
|
@ -327,14 +360,17 @@ FinderHighlighter.prototype = {
|
|||
if (event && event.type == "click" && event.button !== 0)
|
||||
return;
|
||||
|
||||
window = window || this.finder._getWindow();
|
||||
window = (window || this.finder._getWindow()).top;
|
||||
let dict = this.getForWindow(window);
|
||||
|
||||
let doc = window.document;
|
||||
this._clearSelection(this.finder._getSelectionController(window), skipRange);
|
||||
for (let frame of dict.frames)
|
||||
this._clearSelection(this.finder._getSelectionController(frame), skipRange);
|
||||
|
||||
// Next, check our editor cache, for editors belonging to this
|
||||
// document
|
||||
if (this._editors) {
|
||||
let doc = window.document;
|
||||
for (let x = this._editors.length - 1; x >= 0; --x) {
|
||||
if (this._editors[x].document == doc) {
|
||||
this._clearSelection(this._editors[x].selectionController, skipRange);
|
||||
|
@ -344,20 +380,20 @@ FinderHighlighter.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
if (this._modalRepaintScheduler) {
|
||||
window.clearTimeout(this._modalRepaintScheduler);
|
||||
this._modalRepaintScheduler = null;
|
||||
if (dict.modalRepaintScheduler) {
|
||||
window.clearTimeout(dict.modalRepaintScheduler);
|
||||
dict.modalRepaintScheduler = null;
|
||||
}
|
||||
this._lastWindowDimensions = null;
|
||||
dict.lastWindowDimensions = null;
|
||||
|
||||
if (this._modalHighlightOutline)
|
||||
this._modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
|
||||
if (dict.modalHighlightOutline)
|
||||
dict.modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
|
||||
|
||||
this._removeHighlightAllMask(window);
|
||||
this._removeModalHighlightListeners(window);
|
||||
delete this._brightText;
|
||||
delete dict.brightText;
|
||||
|
||||
this.visible = false;
|
||||
dict.visible = false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -383,12 +419,13 @@ FinderHighlighter.prototype = {
|
|||
*/
|
||||
update(data) {
|
||||
let window = this.finder._getWindow();
|
||||
let dict = this.getForWindow(window);
|
||||
let foundRange = this.finder._fastFind.getFoundRange();
|
||||
if (!this._modal) {
|
||||
if (this._highlightAll) {
|
||||
this._currentFoundRange = foundRange;
|
||||
dict.currentFoundRange = foundRange;
|
||||
let params = this.iterator.params;
|
||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
||||
if (this.iterator._areParamsEqual(params, dict.lastIteratorParams))
|
||||
return;
|
||||
if (params)
|
||||
this.highlight(true, params.word, params.linksOnly);
|
||||
|
@ -403,8 +440,8 @@ FinderHighlighter.prototype = {
|
|||
}
|
||||
|
||||
let outlineNode;
|
||||
if (foundRange !== this._currentFoundRange || data.findAgain) {
|
||||
this._currentFoundRange = foundRange;
|
||||
if (foundRange !== dict.currentFoundRange || data.findAgain) {
|
||||
dict.currentFoundRange = foundRange;
|
||||
|
||||
let textContent = this._getRangeContentArray(foundRange);
|
||||
if (!textContent.length) {
|
||||
|
@ -412,36 +449,20 @@ FinderHighlighter.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let rect = foundRange.getClientRects()[0];
|
||||
let fontStyle = this._getRangeFontStyle(foundRange);
|
||||
if (typeof this._brightText == "undefined") {
|
||||
this._brightText = this._isColorBright(fontStyle.color);
|
||||
if (typeof dict.brightText == "undefined") {
|
||||
dict.brightText = this._isColorBright(fontStyle.color);
|
||||
}
|
||||
|
||||
// Text color in the outline is determined by our stylesheet.
|
||||
delete fontStyle.color;
|
||||
|
||||
if (!this.visible)
|
||||
if (!dict.visible)
|
||||
this.show(window);
|
||||
else
|
||||
this._maybeCreateModalHighlightNodes(window);
|
||||
|
||||
outlineNode = this._modalHighlightOutline;
|
||||
outlineNode.setTextContentForElement(kModalOutlineId + "-text", textContent.join(" "));
|
||||
// Correct the line-height to align the text in the middle of the box.
|
||||
fontStyle.lineHeight = rect.height + "px";
|
||||
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;
|
||||
height: ${rect.height}px; width: ${rect.width}px;`);
|
||||
this._updateRangeOutline(dict, textContent, fontStyle);
|
||||
}
|
||||
|
||||
outlineNode = this._modalHighlightOutline;
|
||||
outlineNode = dict.modalHighlightOutline;
|
||||
try {
|
||||
outlineNode.removeAttributeForElement(kModalOutlineId, "grow");
|
||||
} catch (ex) {}
|
||||
|
@ -454,12 +475,18 @@ FinderHighlighter.prototype = {
|
|||
* Invalidates the list by clearing the map of highglighted ranges that we
|
||||
* keep to build the mask for.
|
||||
*/
|
||||
clear() {
|
||||
this._currentFoundRange = null;
|
||||
clear(window = null) {
|
||||
if (!window) {
|
||||
// Reset the Map, because no range references a node anymore.
|
||||
gWindows.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the Map, because no range references a node anymore.
|
||||
if (this._modalHighlightRectsMap)
|
||||
this._modalHighlightRectsMap.clear();
|
||||
let dict = this.getForWindow(window.top);
|
||||
dict.currentFoundRange = null;
|
||||
dict.dynamicRangesSet.clear();
|
||||
dict.frames.clear();
|
||||
dict.modalHighlightRectsMap.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -469,21 +496,22 @@ FinderHighlighter.prototype = {
|
|||
* everything when the user starts to find in page again.
|
||||
*/
|
||||
onLocationChange() {
|
||||
this.clear();
|
||||
let window = this.finder._getWindow();
|
||||
let dict = this.getForWindow(window);
|
||||
this.clear(window);
|
||||
|
||||
if (!this._modalHighlightOutline)
|
||||
if (!dict.modalHighlightOutline)
|
||||
return;
|
||||
|
||||
if (kDebug) {
|
||||
this._modalHighlightOutline.remove();
|
||||
dict.modalHighlightOutline.remove();
|
||||
} else {
|
||||
try {
|
||||
this.finder._getWindow().document
|
||||
.removeAnonymousContent(this._modalHighlightOutline);
|
||||
window.document.removeAnonymousContent(dict.modalHighlightOutline);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
this._modalHighlightOutline = null;
|
||||
dict.modalHighlightOutline = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -523,6 +551,8 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMRange} restoreRange
|
||||
*/
|
||||
_clearSelection(controller, restoreRange = null) {
|
||||
if (!controller)
|
||||
return;
|
||||
let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
sel.removeAllRanges();
|
||||
if (restoreRange) {
|
||||
|
@ -546,19 +576,41 @@ FinderHighlighter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Utility; wrapper around nsIDOMWindowUtils#getScrollXY.
|
||||
* Utility; returns the bounds of the page relative to the viewport.
|
||||
* If the pages is part of a frameset or inside an iframe of any kind, its
|
||||
* offset is accounted for.
|
||||
* Geometry.jsm takes care of the DOMRect calculations.
|
||||
*
|
||||
* @param {nsDOMWindow} window Optional, defaults to the finder window.
|
||||
* @return {Object} The current scroll position.
|
||||
* @param {nsIDOMWindow} window
|
||||
* @return {Rect}
|
||||
*/
|
||||
_getScrollPosition(window = null) {
|
||||
_getRootBounds(window) {
|
||||
let dwu = this._getDWU(window);
|
||||
let cssPageRect = Rect.fromRect(dwu.getRootBounds());
|
||||
|
||||
let scrollX = {};
|
||||
let scrollY = {};
|
||||
this._getDWU(window).getScrollXY(false, scrollX, scrollY);
|
||||
return {
|
||||
scrollX: scrollX.value,
|
||||
scrollY: scrollY.value
|
||||
};
|
||||
dwu.getScrollXY(false, scrollX, scrollY);
|
||||
cssPageRect.translate(scrollX.value, scrollY.value);
|
||||
|
||||
// If we're in a frame, update the position of the rect (top/ left).
|
||||
let currWin = window;
|
||||
while (currWin != window.top) {
|
||||
// Since the frame is an element inside a parent window, we'd like to
|
||||
// learn its position relative to it.
|
||||
let el = this._getDWU(currWin).containerElement;
|
||||
currWin = window.parent;
|
||||
dwu = this._getDWU(currWin);
|
||||
let parentRect = Rect.fromRect(dwu.getBoundsWithoutFlushing(el));
|
||||
|
||||
// Always take the scroll position into account.
|
||||
dwu.getScrollXY(false, scrollX, scrollY);
|
||||
parentRect.translate(scrollX.value, scrollY.value);
|
||||
|
||||
cssPageRect.translate(parentRect.left, parentRect.top);
|
||||
}
|
||||
|
||||
return cssPageRect;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -572,7 +624,7 @@ FinderHighlighter.prototype = {
|
|||
_getWindowDimensions(window) {
|
||||
// First we'll try without flushing layout, because it's way faster.
|
||||
let dwu = this._getDWU(window);
|
||||
let {width, height} = dwu.getBoundsWithoutFlushing(window.document.body);
|
||||
let { width, height } = dwu.getRootBounds();
|
||||
|
||||
if (!width || !height) {
|
||||
// We need a flush after all :'(
|
||||
|
@ -658,6 +710,141 @@ FinderHighlighter.prototype = {
|
|||
return new Color(...cssColor).isBright;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 fixed container
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isInDynamicContainer(range) {
|
||||
const kFixed = new Set(["fixed", "sticky"]);
|
||||
let node = range.startContainer;
|
||||
while (node.nodeType != 1)
|
||||
node = node.parentNode;
|
||||
let document = node.ownerDocument;
|
||||
let window = document.defaultView;
|
||||
let dict = this.getForWindow(window.top);
|
||||
|
||||
// Check if we're in a frameset (including iframes).
|
||||
if (window != window.top) {
|
||||
if (!dict.frames.has(window))
|
||||
dict.frames.set(window, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
do {
|
||||
if (kFixed.has(window.getComputedStyle(node, null).position))
|
||||
return true;
|
||||
node = node.parentNode;
|
||||
} while (node && node != document.documentElement)
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Read and store the rectangles that encompass the entire region of a range
|
||||
* for use by the drawing function of the highlighter.
|
||||
*
|
||||
* @param {nsIDOMRange} range Range to fetch the rectangles from
|
||||
* @param {Boolean} [checkIfDynamic] Whether we should check if the range
|
||||
* is dynamic as per the rules in
|
||||
* `_isInDynamicContainer()`. Optional,
|
||||
* defaults to `true`
|
||||
* @param {Object} [dict] Dictionary of properties belonging to
|
||||
* the currently active window
|
||||
*/
|
||||
_updateRangeRects(range, checkIfDynamic = true, dict = null) {
|
||||
let window = range.startContainer.ownerDocument.defaultView;
|
||||
let bounds;
|
||||
// If the window is part of a frameset, try to cache the bounds query.
|
||||
if (dict && dict.frames.has(window)) {
|
||||
bounds = dict.frames.get(window);
|
||||
if (!bounds) {
|
||||
bounds = this._getRootBounds(window);
|
||||
dict.frames.set(window, bounds);
|
||||
}
|
||||
} else
|
||||
bounds = this._getRootBounds(window);
|
||||
|
||||
let rects = new Set();
|
||||
// A range may consist of multiple rectangles, we can also do these kind of
|
||||
// precise cut-outs. range.getBoundingClientRect() returns the fully
|
||||
// encompassing rectangle, which is too much for our purpose here.
|
||||
for (let dims of range.getClientRects()) {
|
||||
rects.add({
|
||||
height: dims.bottom - dims.top,
|
||||
width: dims.right - dims.left,
|
||||
y: dims.top + bounds.top,
|
||||
x: dims.left + bounds.left
|
||||
});
|
||||
}
|
||||
|
||||
dict = dict || this.getForWindow(window.top);
|
||||
dict.modalHighlightRectsMap.set(range, rects);
|
||||
if (checkIfDynamic && this._isInDynamicContainer(range))
|
||||
dict.dynamicRangesSet.add(range);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-read the rectangles of the ranges that we keep track of separately,
|
||||
* because they're enclosed by a position: fixed container DOM node.
|
||||
*
|
||||
* @param {Object} dict Dictionary of properties belonging to the currently
|
||||
* active window
|
||||
*/
|
||||
_updateFixedRangesRects(dict) {
|
||||
for (let range of dict.dynamicRangesSet)
|
||||
this._updateRangeRects(range, false, dict);
|
||||
// Reset the frame bounds cache.
|
||||
for (let frame of dict.frames.keys())
|
||||
dict.frames.set(frame, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the content, position and style of the yellow current found range
|
||||
* outline the floats atop the mask with the dimmed background.
|
||||
*
|
||||
* @param {Object} dict Dictionary of properties belonging to the
|
||||
* currently active window
|
||||
* @param {Array} [textContent] Array of text that's inside the range. Optional,
|
||||
* defaults to an empty array
|
||||
* @param {Object} [fontStyle] Dictionary of CSS styles in camelCase as
|
||||
* returned by `_getRangeFontStyle()`. Optional
|
||||
*/
|
||||
_updateRangeOutline(dict, textContent = [], fontStyle = null) {
|
||||
let outlineNode = dict.modalHighlightOutline;
|
||||
let range = dict.currentFoundRange;
|
||||
if (!outlineNode || !range)
|
||||
return;
|
||||
let rect = range.getClientRects()[0];
|
||||
if (!rect)
|
||||
return;
|
||||
|
||||
if (!fontStyle)
|
||||
fontStyle = this._getRangeFontStyle(range);
|
||||
// Text color in the outline is determined by our stylesheet.
|
||||
delete fontStyle.color;
|
||||
|
||||
if (textContent.length)
|
||||
outlineNode.setTextContentForElement(kModalOutlineId + "-text", textContent.join(" "));
|
||||
// Correct the line-height to align the text in the middle of the box.
|
||||
fontStyle.lineHeight = rect.height + "px";
|
||||
outlineNode.setAttributeForElement(kModalOutlineId + "-text", "style",
|
||||
this._getHTMLFontStyle(fontStyle));
|
||||
|
||||
if (typeof outlineNode.getAttributeForElement(kModalOutlineId, "hidden") == "string")
|
||||
outlineNode.removeAttributeForElement(kModalOutlineId, "hidden");
|
||||
|
||||
let window = range.startContainer.ownerDocument.defaultView;
|
||||
let { left, top } = this._getRootBounds(window);
|
||||
outlineNode.setAttributeForElement(kModalOutlineId, "style",
|
||||
`top: ${top + rect.top}px; left: ${left + rect.left}px;
|
||||
height: ${rect.height}px; width: ${rect.width}px;`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a range to the list of ranges to highlight on, or cut out of, the dimmed
|
||||
* background.
|
||||
|
@ -669,24 +856,7 @@ FinderHighlighter.prototype = {
|
|||
if (!this._getRangeContentArray(range).length)
|
||||
return;
|
||||
|
||||
let rects = new Set();
|
||||
// Absolute positions should include the viewport scroll offset.
|
||||
let { scrollX, scrollY } = this._getScrollPosition(window);
|
||||
// A range may consist of multiple rectangles, we can also do these kind of
|
||||
// precise cut-outs. range.getBoundingClientRect() returns the fully
|
||||
// encompassing rectangle, which is too much for our purpose here.
|
||||
for (let dims of range.getClientRects()) {
|
||||
rects.add({
|
||||
height: dims.bottom - dims.top,
|
||||
width: dims.right - dims.left,
|
||||
y: dims.top + scrollY,
|
||||
x: dims.left + scrollX
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._modalHighlightRectsMap)
|
||||
this._modalHighlightRectsMap = new Map();
|
||||
this._modalHighlightRectsMap.set(range, rects);
|
||||
this._updateRangeRects(range);
|
||||
|
||||
this.show(window);
|
||||
// We don't repaint the mask right away, but pass it off to a render loop of
|
||||
|
@ -701,8 +871,10 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMWindow} window Window to draw in.
|
||||
*/
|
||||
_maybeCreateModalHighlightNodes(window) {
|
||||
if (this._modalHighlightOutline) {
|
||||
if (!this._modalHighlightAllMask) {
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (dict.modalHighlightOutline) {
|
||||
if (!dict.modalHighlightAllMask) {
|
||||
// Make sure to at least show the dimmed background.
|
||||
this._repaintHighlightAllMask(window, false);
|
||||
this._scheduleRepaintOfMask(window);
|
||||
|
@ -738,7 +910,7 @@ FinderHighlighter.prototype = {
|
|||
outlineBox.appendChild(outlineBoxText);
|
||||
|
||||
container.appendChild(outlineBox);
|
||||
this._modalHighlightOutline = kDebug ?
|
||||
dict.modalHighlightOutline = kDebug ?
|
||||
mockAnonymousContentNode(document.body.appendChild(container.firstChild)) :
|
||||
document.insertAnonymousContent(container);
|
||||
|
||||
|
@ -755,6 +927,8 @@ FinderHighlighter.prototype = {
|
|||
* @param {Boolean} [paintContent]
|
||||
*/
|
||||
_repaintHighlightAllMask(window, paintContent = true) {
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
let document = window.document;
|
||||
|
||||
const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
|
||||
|
@ -762,23 +936,23 @@ FinderHighlighter.prototype = {
|
|||
|
||||
// Make sure the dimmed mask node takes the full width and height that's available.
|
||||
let {width, height} = this._getWindowDimensions(window);
|
||||
this._lastWindowDimensions = { width, height };
|
||||
dict.lastWindowDimensions = { width, height };
|
||||
maskNode.setAttribute("id", kMaskId);
|
||||
maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
|
||||
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
|
||||
if (this._brightText)
|
||||
if (dict.brightText)
|
||||
maskNode.setAttribute("brighttext", "true");
|
||||
|
||||
if (paintContent || this._modalHighlightAllMask) {
|
||||
if (paintContent || dict.modalHighlightAllMask) {
|
||||
this._updateRangeOutline(dict);
|
||||
this._updateFixedRangesRects(dict);
|
||||
// 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 [range, rects] of this._modalHighlightRectsMap) {
|
||||
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>`);
|
||||
}
|
||||
for (let [range, rects] of dict.modalHighlightRectsMap) {
|
||||
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("");
|
||||
|
@ -788,7 +962,7 @@ FinderHighlighter.prototype = {
|
|||
// free to alter DOM nodes inside the CanvasFrame.
|
||||
this._removeHighlightAllMask(window);
|
||||
|
||||
this._modalHighlightAllMask = kDebug ?
|
||||
dict.modalHighlightAllMask = kDebug ?
|
||||
mockAnonymousContentNode(document.body.appendChild(maskNode)) :
|
||||
document.insertAnonymousContent(maskNode);
|
||||
},
|
||||
|
@ -799,19 +973,21 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMWindow} window
|
||||
*/
|
||||
_removeHighlightAllMask(window) {
|
||||
if (!this._modalHighlightAllMask)
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (!dict.modalHighlightAllMask)
|
||||
return;
|
||||
|
||||
// If the current window isn't the one the content was inserted into, this
|
||||
// will fail, but that's fine.
|
||||
if (kDebug) {
|
||||
this._modalHighlightAllMask.remove();
|
||||
dict.modalHighlightAllMask.remove();
|
||||
} else {
|
||||
try {
|
||||
window.document.removeAnonymousContent(this._modalHighlightAllMask);
|
||||
window.document.removeAnonymousContent(dict.modalHighlightAllMask);
|
||||
} catch (ex) {}
|
||||
}
|
||||
this._modalHighlightAllMask = null;
|
||||
dict.modalHighlightAllMask = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -820,37 +996,46 @@ FinderHighlighter.prototype = {
|
|||
* `kModalHighlightRepaintFreqMs` milliseconds.
|
||||
*
|
||||
* @param {nsIDOMWindow} window
|
||||
* @param {Boolean} contentChanged Whether the documents' content changed
|
||||
* in the meantime. This happens when the
|
||||
* DOM is updated whilst the page is loaded.
|
||||
* @param {Object} options Dictionary of painter hints that contains the
|
||||
* following properties:
|
||||
* {Boolean} contentChanged Whether the documents' content changed in the
|
||||
* meantime. This happens when the DOM is updated
|
||||
* whilst the page is loaded.
|
||||
* {Boolean} scrollOnly TRUE when the page has scrolled in the meantime,
|
||||
* which means that the fixed positioned elements
|
||||
* need to be repainted.
|
||||
*/
|
||||
_scheduleRepaintOfMask(window, contentChanged = false) {
|
||||
if (this._modalRepaintScheduler) {
|
||||
window.clearTimeout(this._modalRepaintScheduler);
|
||||
this._modalRepaintScheduler = null;
|
||||
}
|
||||
_scheduleRepaintOfMask(window, { contentChanged, scrollOnly } = { contentChanged: false, scrollOnly: false }) {
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
let repaintFixedNodes = (scrollOnly && !!dict.dynamicRangesSet.size);
|
||||
|
||||
// When we request to repaint unconditionally, we mean to call
|
||||
// `_repaintHighlightAllMask()` right after the timeout.
|
||||
if (!this._unconditionalRepaintRequested)
|
||||
this._unconditionalRepaintRequested = !contentChanged;
|
||||
if (!dict.unconditionalRepaintRequested)
|
||||
dict.unconditionalRepaintRequested = !contentChanged || repaintFixedNodes;
|
||||
|
||||
this._modalRepaintScheduler = window.setTimeout(() => {
|
||||
if (this._unconditionalRepaintRequested) {
|
||||
this._unconditionalRepaintRequested = false;
|
||||
if (dict.modalRepaintScheduler)
|
||||
return;
|
||||
|
||||
dict.modalRepaintScheduler = window.setTimeout(() => {
|
||||
dict.modalRepaintScheduler = null;
|
||||
|
||||
if (dict.unconditionalRepaintRequested) {
|
||||
dict.unconditionalRepaintRequested = false;
|
||||
this._repaintHighlightAllMask(window);
|
||||
return;
|
||||
}
|
||||
|
||||
let { width, height } = this._getWindowDimensions(window);
|
||||
if (!this._modalHighlightRectsMap ||
|
||||
(Math.abs(this._lastWindowDimensions.width - width) < kContentChangeThresholdPx &&
|
||||
Math.abs(this._lastWindowDimensions.height - height) < kContentChangeThresholdPx)) {
|
||||
if (!dict.modalHighlightRectsMap.size ||
|
||||
(Math.abs(dict.lastWindowDimensions.width - width) < kContentChangeThresholdPx &&
|
||||
Math.abs(dict.lastWindowDimensions.height - height) < kContentChangeThresholdPx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.iterator.restart(this.finder);
|
||||
this._lastWindowDimensions = { width, height };
|
||||
dict.lastWindowDimensions = { width, height };
|
||||
this._repaintHighlightAllMask(window);
|
||||
}, kModalHighlightRepaintFreqMs);
|
||||
},
|
||||
|
@ -864,12 +1049,9 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMWindow} window
|
||||
*/
|
||||
_maybeInstallStyleSheet(window) {
|
||||
let document = window.document;
|
||||
// The WeakMap is a cheap method to make sure we don't needlessly insert the
|
||||
// same sheet twice.
|
||||
if (!this._modalInstalledSheets)
|
||||
this._modalInstalledSheets = new WeakMap();
|
||||
if (this._modalInstalledSheets.has(document))
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (dict.installedSheet)
|
||||
return;
|
||||
|
||||
let dwu = this._getDWU(window);
|
||||
|
@ -877,7 +1059,7 @@ FinderHighlighter.prototype = {
|
|||
try {
|
||||
dwu.loadSheetUsingURIString(uri, dwu.AGENT_SHEET);
|
||||
} catch (e) {}
|
||||
this._modalInstalledSheets.set(document, uri);
|
||||
dict.installedSheet = true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -887,16 +1069,22 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMWindow} window
|
||||
*/
|
||||
_addModalHighlightListeners(window) {
|
||||
if (this._highlightListeners)
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (dict.highlightListeners)
|
||||
return;
|
||||
|
||||
this._highlightListeners = [
|
||||
this._scheduleRepaintOfMask.bind(this, window, true),
|
||||
window = window.top;
|
||||
dict.highlightListeners = [
|
||||
this._scheduleRepaintOfMask.bind(this, window, { contentChanged: true }),
|
||||
this._scheduleRepaintOfMask.bind(this, window, { scrollOnly: true }),
|
||||
this.hide.bind(this, window, null)
|
||||
];
|
||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||
target.addEventListener("MozAfterPaint", this._highlightListeners[0]);
|
||||
window.addEventListener("click", this._highlightListeners[1]);
|
||||
target.addEventListener("MozAfterPaint", dict.highlightListeners[0]);
|
||||
target.addEventListener("DOMMouseScroll", dict.highlightListeners[1]);
|
||||
target.addEventListener("mousewheel", dict.highlightListeners[1]);
|
||||
target.addEventListener("click", dict.highlightListeners[2]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -905,14 +1093,18 @@ FinderHighlighter.prototype = {
|
|||
* @param {nsIDOMWindow} window
|
||||
*/
|
||||
_removeModalHighlightListeners(window) {
|
||||
if (!this._highlightListeners)
|
||||
window = window.top;
|
||||
let dict = this.getForWindow(window);
|
||||
if (!dict.highlightListeners)
|
||||
return;
|
||||
|
||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||
target.removeEventListener("MozAfterPaint", this._highlightListeners[0]);
|
||||
window.removeEventListener("click", this._highlightListeners[1]);
|
||||
target.removeEventListener("MozAfterPaint", dict.highlightListeners[0]);
|
||||
target.removeEventListener("DOMMouseScroll", dict.highlightListeners[1]);
|
||||
target.removeEventListener("mousewheel", dict.highlightListeners[1]);
|
||||
target.removeEventListener("click", dict.highlightListeners[2]);
|
||||
|
||||
this._highlightListeners = null;
|
||||
dict.highlightListeners = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -155,8 +155,8 @@ add_task(function* testModalResults() {
|
|||
}],
|
||||
["o", {
|
||||
rectCount: 492,
|
||||
insertCalls: [1, 3],
|
||||
removeCalls: [1, 2]
|
||||
insertCalls: [1, 4],
|
||||
removeCalls: [1, 3]
|
||||
}]
|
||||
]);
|
||||
let url = kFixtureBaseURL + "file_FinderSample.html";
|
||||
|
@ -294,7 +294,7 @@ add_task(function* testHighlightAllToggle() {
|
|||
expectedResult = {
|
||||
rectCount: 0,
|
||||
insertCalls: [0, 1],
|
||||
removeCalls: [1, 1]
|
||||
removeCalls: [0, 1]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
|
||||
|
|
Загрузка…
Ссылка в новой задаче