Bug 1282752 - use the awesome new Range.getRectsAndTexts() API to fetch the text content for each rect of a range. r?jaws
MozReview-Commit-ID: BKAx4TdBms0
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -155,17 +155,17 @@ FinderHighlighter.prototype = {
* For each window we track a number of properties which _at least_ consist of
* - {Boolean} detectedGeometryChange Whether the geometry of the found ranges'
* rectangles has changed substantially
* - {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
* - {Map} modalHighlightRectsMap Collection of ranges and their corresponding
- * Rects
+ * Rects and texts
*
* @param {nsIDOMWindow} window
* @return {Object}
*/
getForWindow(window, propName = null) {
if (!gWindows.has(window)) {
gWindows.set(window, {
detectedGeometryChange: false,
@@ -822,17 +822,17 @@ FinderHighlighter.prototype = {
* 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 {Object} [dict] Dictionary of properties belonging to
* the currently active window
* @return {Set} Set of rects that were found for the range
*/
- _getRangeRects(range, dict = null) {
+ _getRangeRectsAndTexts(range, 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);
@@ -840,25 +840,26 @@ FinderHighlighter.prototype = {
} else
bounds = this._getRootBounds(window);
let topBounds = this._getRootBounds(window.top, false);
let rects = [];
// 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 rect of range.getClientRects()) {
+ let {rectList, textList} = range.getClientRectsAndTexts();
+ for (let rect of rectList) {
rect = Rect.fromRect(rect);
rect.x += bounds.x;
rect.y += bounds.y;
// If the rect is not even visible from the top document, we can ignore it.
if (rect.intersects(topBounds))
rects.push(rect);
}
- return rects;
+ return {rectList: rects, textList};
},
/**
* Read and store the rectangles that encompass the entire region of a range
* for use by the drawing function of the highlighter and store them in the
* cache.
*
* @param {nsIDOMRange} range Range to fetch the rectangles from
@@ -867,29 +868,29 @@ FinderHighlighter.prototype = {
* `_isInDynamicContainer()`. Optional,
* defaults to `true`
* @param {Object} [dict] Dictionary of properties belonging to
* the currently active window
* @return {Set} Set of rects that were found for the range
*/
_updateRangeRects(range, checkIfDynamic = true, dict = null) {
let window = range.startContainer.ownerDocument.defaultView;
- let rects = this._getRangeRects(range, dict);
+ let rectsAndTexts = this._getRangeRectsAndTexts(range, dict);
// Only fetch the rect at this point, if not passed in as argument.
dict = dict || this.getForWindow(window.top);
- let oldRects = dict.modalHighlightRectsMap.get(range);
- dict.modalHighlightRectsMap.set(range, rects);
+ let oldRectsAndTexts = dict.modalHighlightRectsMap.get(range);
+ dict.modalHighlightRectsMap.set(range, rectsAndTexts);
// Check here if we suddenly went down to zero rects from more than zero before,
// which indicates that we should re-iterate the document.
- if (oldRects && oldRects.length && !rects.length)
+ if (oldRectsAndTexts && oldRectsAndTexts.rectList.length && !rectsAndTexts.rectList.length)
dict.detectedGeometryChange = true;
if (checkIfDynamic && this._isInDynamicContainer(range))
dict.dynamicRangesSet.add(range);
- return rects;
+ return rectsAndTexts;
},
/**
* Re-read the rectangles of the ranges that we keep track of separately,
* because they're enclosed by a position: fixed container DOM node or (i)frame.
*
* @param {Object} dict Dictionary of properties belonging to the currently
* active window
@@ -919,21 +920,21 @@ FinderHighlighter.prototype = {
let range = dict.currentFoundRange;
if (!range)
return;
fontStyle = fontStyle || this._getRangeFontStyle(range);
// Text color in the outline is determined by kModalStyles.
delete fontStyle.color;
- let rects = this._getRangeRects(range);
+ let rectsAndTexts = this._getRangeRectsAndTexts(range);
textContent = textContent || this._getRangeContentArray(range);
let outlineAnonNode = dict.modalHighlightOutline;
- let rectCount = rects.length;
+ let rectCount = rectsAndTexts.rectList.length;
// (re-)Building the outline is conditional and happens when one of the
// following conditions is met:
// 1. No outline nodes were built before, or
// 2. When the amount of rectangles to draw is different from before, or
// 3. When there's more than one rectangle to draw, because it's impossible
// to animate that consistently with AnonymousContent nodes.
let rebuildOutline = (!outlineAnonNode || rectCount !== dict.previousRangeRectsCount ||
rectCount != 1);
@@ -949,48 +950,45 @@ FinderHighlighter.prototype = {
try {
document.removeAnonymousContent(outlineAnonNode);
} catch (ex) {}
}
dict.modalHighlightOutline = null;
}
// Abort when there's no text to highlight.
- if (!textContent.length)
+ if (!rectsAndTexts.textList.length)
return;
let outlineBox;
if (rebuildOutline) {
// Create the main (yellow) highlight outline box.
outlineBox = document.createElementNS(kNSHTML, "div");
outlineBox.setAttribute("id", kModalOutlineId);
}
const kModalOutlineTextId = kModalOutlineId + "-text";
let i = 0;
- for (let rect of rects) {
- // if the current rect is the last rect, then text is set to the rest of
- // the textContent with single spaces injected between the text. Otherwise
- // text is set to the current textContent for the matching rect.
- let text = (i == rectCount - 1) ? textContent.slice(i).join(" ") : textContent[i];
+ for (let rect of rectsAndTexts.rectList) {
+ let text = rectsAndTexts.textList[i];
// Next up is to check of the outline box' borders will not overlap with
// rects that we drew before or will draw after this one.
// We're taking the width of the border into account, which is
// `kOutlineBoxBorderSize` pixels.
// When left and/ or right sides will overlap with the current, previous
// or next rect, make sure to make the necessary adjustments to the style.
// These adjustments will override the styles as defined in `kModalStyles.outlineNode`.
let intersectingSides = new Set();
- let previous = rects[i - 1];
+ let previous = rectsAndTexts.rectList[i - 1];
if (previous &&
rect.left - previous.right <= 2 * kOutlineBoxBorderSize) {
intersectingSides.add("left");
}
- let next = rects[i + 1];
+ let next = rectsAndTexts.rectList[i + 1];
if (next &&
next.left - rect.right <= 2 * kOutlineBoxBorderSize) {
intersectingSides.add("right");
}
let borderStyles = [...intersectingSides].map(side => [ "border-" + side, 0 ]);
if (intersectingSides.size) {
borderStyles.push([ "margin", `-${kOutlineBoxBorderSize}px 0 0 ${
intersectingSides.has("left") ? 0 : -kOutlineBoxBorderSize}px !important`]);
@@ -1129,26 +1127,26 @@ FinderHighlighter.prototype = {
this._updateRangeOutline(dict);
let allRects = [];
if (paintContent || dict.modalHighlightAllMask) {
this._updateDynamicRangesRects(dict);
let DOMRect = window.DOMRect;
- for (let [range, rects] of dict.modalHighlightRectsMap) {
+ for (let [range, rectsAndTexts] of dict.modalHighlightRectsMap) {
if (dict.updateAllRanges)
- rects = this._updateRangeRects(range);
+ rectsAndTexts = this._updateRangeRects(range);
// If a geometry change was detected, we bail out right away here, because
// the current set of ranges has been invalidated.
if (dict.detectedGeometryChange)
return;
- for (let rect of rects)
+ for (let rect of rectsAndTexts.rectList)
allRects.push(new DOMRect(rect.x, rect.y, rect.width, rect.height));
}
dict.updateAllRanges = false;
}
dict.modalHighlightAllMask.setCutoutRectsForElement(kMaskId, allRects);
},
--- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js
+++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js
@@ -453,8 +453,34 @@ add_task(function* testHideOnClear() {
removeCalls: [1, 2]
});
findbar.clear();
yield promise;
findbar.close(true);
});
});
+
+add_task(function* testRectsAndTexts() {
+ let url = "data:text/html;charset=utf-8," +
+ encodeURIComponent("<div style=\"width: 150px; border: 1px solid black\">" +
+ "Here are a lot of words Please use find to highlight some words that wrap" +
+ " across a line boundary and see what happens.</div>");
+ yield BrowserTestUtils.withNewTab(url, function* (browser) {
+ let findbar = gBrowser.getFindBar();
+ yield promiseOpenFindbar(findbar);
+
+ let word = "words please use find to";
+ let expectedResult = {
+ rectCount: 2,
+ insertCalls: [2, 4],
+ removeCalls: [0, 2]
+ };
+ let promise = promiseTestHighlighterOutput(browser, word, expectedResult, (maskNode, outlineNode) => {
+ let boxes = outlineNode.getElementsByTagName("span");
+ Assert.equal(boxes.length, 2, "There should be two outline boxes containing text");
+ Assert.equal(boxes[0].textContent.trim(), "words", "First text should match");
+ Assert.equal(boxes[1].textContent.trim(), "Please use find to", "Second word should match");
+ });
+ yield promiseEnterStringIntoFindField(findbar, word);
+ yield promise;
+ });
+});