diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index 13dbbc4350e1..04f49f6caff4 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -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 diff --git a/toolkit/modules/FinderHighlighter.jsm b/toolkit/modules/FinderHighlighter.jsm index 450e1629ebb7..60f47769668b 100644 --- a/toolkit/modules/FinderHighlighter.jsm +++ b/toolkit/modules/FinderHighlighter.jsm @@ -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(`
`); - } + for (let [range, rects] of dict.modalHighlightRectsMap) { + for (let rect of rects) { + maskContent.push(`
`); } } 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; }, /** diff --git a/toolkit/modules/tests/browser/browser_FinderHighlighter.js b/toolkit/modules/tests/browser/browser_FinderHighlighter.js index eda6711731ef..e7304431738b 100644 --- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js +++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js @@ -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 ]] });