зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1366646 - Include borders and padding when calculating the offset of a window inside an (i)frame. r=jaws
MozReview-Commit-ID: 58fBRcw1lg3 --HG-- extra : rebase_source : 7e125ba0203ce56e9782152b036ab5c26e0a3aa5
This commit is contained in:
Родитель
3930673f33
Коммит
8678d63172
|
@ -5,7 +5,7 @@
|
|||
function frameScript() {
|
||||
function getSelectedText() {
|
||||
let frame = this.content.frames[0].frames[1];
|
||||
let Ci = Components.interfaces;
|
||||
let {interfaces: Ci, utils: Cu} = Components;
|
||||
let docShell = frame.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
@ -14,10 +14,18 @@ function frameScript() {
|
|||
.QueryInterface(Ci.nsISelectionController);
|
||||
let selection = controller.getSelection(controller.SELECTION_FIND);
|
||||
let range = selection.getRangeAt(0);
|
||||
let scope = {};
|
||||
Cu.import("resource://gre/modules/FindContent.jsm", scope);
|
||||
let highlighter = (new scope.FindContent(docShell)).highlighter;
|
||||
let r1 = frame.parent.frameElement.getBoundingClientRect();
|
||||
let f1 = highlighter._getFrameElementOffsets(frame.parent);
|
||||
let r2 = frame.frameElement.getBoundingClientRect();
|
||||
let f2 = highlighter._getFrameElementOffsets(frame);
|
||||
let r3 = range.getBoundingClientRect();
|
||||
let rect = {top: (r1.top + r2.top + r3.top), left: (r1.left + r2.left + r3.left)};
|
||||
let rect = {
|
||||
top: (r1.top + r2.top + r3.top + f1.y + f2.y),
|
||||
left: (r1.left + r2.left + r3.left + f1.x + f2.x),
|
||||
};
|
||||
this.sendAsyncMessage("test:find:selectionTest", {text: selection.toString(), rect});
|
||||
}
|
||||
getSelectedText();
|
||||
|
|
|
@ -292,11 +292,12 @@ FinderHighlighter.prototype = {
|
|||
} else {
|
||||
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||
findSelection.addRange(range);
|
||||
// Check if the range is inside an iframe.
|
||||
// Check if the range is inside an (i)frame.
|
||||
if (window != window.top) {
|
||||
let dict = this.getForWindow(window.top);
|
||||
if (!dict.frames.has(window))
|
||||
dict.frames.set(window, null);
|
||||
// Add this frame to the list, so that we'll be able to find it later
|
||||
// when we need to clear its selection(s).
|
||||
dict.frames.set(window, {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,6 +573,9 @@ FinderHighlighter.prototype = {
|
|||
// If we're in a frame, update the position of the rect (top/ left).
|
||||
let currWin = window;
|
||||
while (currWin != window.top) {
|
||||
let frameOffsets = this._getFrameElementOffsets(currWin);
|
||||
cssPageRect.translate(frameOffsets.x, frameOffsets.y);
|
||||
|
||||
// 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;
|
||||
|
@ -594,10 +598,48 @@ FinderHighlighter.prototype = {
|
|||
|
||||
cssPageRect.translate(parentRect.left, parentRect.top);
|
||||
}
|
||||
let frameOffsets = this._getFrameElementOffsets(currWin);
|
||||
cssPageRect.translate(frameOffsets.x, frameOffsets.y);
|
||||
|
||||
return cssPageRect;
|
||||
},
|
||||
|
||||
/**
|
||||
* (I)Frame elements may have a border and/ or padding set, which is not
|
||||
* included in the bounds returned by nsDOMWindowUtils#getRootBounds() for the
|
||||
* window it hosts.
|
||||
* This method fetches this offset of the frame element to the respective window.
|
||||
*
|
||||
* @param {nsIDOMWindow} window Window to read the boundary rect from
|
||||
* @return {Object} Simple object that contains the following two properties:
|
||||
* - {Number} x Offset along the horizontal axis.
|
||||
* - {Number} y Offset along the vertical axis.
|
||||
*/
|
||||
_getFrameElementOffsets(window) {
|
||||
let frame = window.frameElement;
|
||||
if (!frame)
|
||||
return { x: 0, y: 0 };
|
||||
|
||||
// Getting style info is super expensive, causing reflows, so let's cache
|
||||
// frame border widths and padding values aggressively.
|
||||
let dict = this.getForWindow(window.top);
|
||||
let frameData = dict.frames.get(window);
|
||||
if (!frameData)
|
||||
dict.frames.set(window, frameData = {});
|
||||
if (frameData.offset)
|
||||
return frameData.offset;
|
||||
|
||||
let style = frame.ownerGlobal.getComputedStyle(frame);
|
||||
// We only need to left sides, because ranges are offset from point 0,0 in
|
||||
// the top-left corner.
|
||||
let borderOffset = [parseInt(style.borderLeftWidth, 10) || 0, parseInt(style.borderTopWidth, 10) || 0];
|
||||
let paddingOffset = [parseInt(style.paddingLeft, 10) || 0, parseInt(style.paddingTop, 10) || 0];
|
||||
return frameData.offset = {
|
||||
x: borderOffset[0] + paddingOffset[0],
|
||||
y: borderOffset[1] + paddingOffset[1]
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility; fetch the full width and height of the current window, excluding
|
||||
* scrollbars.
|
||||
|
@ -761,7 +803,7 @@ FinderHighlighter.prototype = {
|
|||
// Check if we're in a frameset (including iframes).
|
||||
if (window != window.top) {
|
||||
if (!dict.frames.has(window))
|
||||
dict.frames.set(window, null);
|
||||
dict.frames.set(window, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -791,13 +833,13 @@ FinderHighlighter.prototype = {
|
|||
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
|
||||
let frameData = dict.frames.get(window);
|
||||
bounds = frameData.bounds;
|
||||
if (!bounds)
|
||||
bounds = frameData.bounds = this._getRootBounds(window);
|
||||
} else {
|
||||
bounds = this._getRootBounds(window);
|
||||
}
|
||||
|
||||
let topBounds = this._getRootBounds(window.top, false);
|
||||
let rects = [];
|
||||
|
@ -856,8 +898,8 @@ FinderHighlighter.prototype = {
|
|||
*/
|
||||
_updateDynamicRangesRects(dict) {
|
||||
// Reset the frame bounds cache.
|
||||
for (let frame of dict.frames.keys())
|
||||
dict.frames.set(frame, null);
|
||||
for (let frameData of dict.frames.values())
|
||||
frameData.bounds = null;
|
||||
for (let range of dict.dynamicRangesSet)
|
||||
this._updateRangeRects(range, false, dict);
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@ support-files =
|
|||
metadata_*.html
|
||||
testremotepagemanager.html
|
||||
testremotepagemanager2.html
|
||||
file_FinderIframeTest.html
|
||||
file_FinderSample.html
|
||||
file_WebNavigation_page1.html
|
||||
file_WebNavigation_page2.html
|
||||
file_WebNavigation_page3.html
|
||||
|
@ -19,6 +21,7 @@ support-files =
|
|||
file_script_bad.js
|
||||
file_script_redirect.js
|
||||
file_script_xhr.js
|
||||
head.js
|
||||
WebRequest_dynamic.sjs
|
||||
WebRequest_redirection.sjs
|
||||
|
||||
|
@ -30,7 +33,8 @@ support-files =
|
|||
[browser_Finder_offscreen_text.js]
|
||||
[browser_FinderHighlighter.js]
|
||||
skip-if = debug || os = "linux"
|
||||
support-files = file_FinderSample.html
|
||||
[browser_FinderHighlighter2.js]
|
||||
skip-if = debug || os = "linux"
|
||||
[browser_Geometry.js]
|
||||
[browser_InlineSpellChecker.js]
|
||||
[browser_WebNavigation.js]
|
||||
|
|
|
@ -1,193 +1,13 @@
|
|||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/BrowserTestUtils.jsm", this);
|
||||
Cu.import("resource://testing-common/ContentTask.jsm", this);
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://gre/modules/Timer.jsm", this);
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
const kHighlightAllPref = "findbar.highlightAll";
|
||||
const kPrefModalHighlight = "findbar.modalHighlight";
|
||||
const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/";
|
||||
const kIteratorTimeout = Services.prefs.getIntPref("findbar.iteratorTimeout");
|
||||
|
||||
function promiseOpenFindbar(findbar) {
|
||||
findbar.onFindCommand();
|
||||
return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise;
|
||||
}
|
||||
|
||||
function promiseFindResult(findbar, str = null) {
|
||||
let highlightFinished = false;
|
||||
let findFinished = false;
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
onFindResult({ searchString }) {
|
||||
if (str !== null && str != searchString) {
|
||||
return;
|
||||
}
|
||||
findFinished = true;
|
||||
if (highlightFinished) {
|
||||
findbar.browser.finder.removeResultListener(listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onHighlightFinished() {
|
||||
highlightFinished = true;
|
||||
if (findFinished) {
|
||||
findbar.browser.finder.removeResultListener(listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onMatchesCountResult: () => {}
|
||||
};
|
||||
findbar.browser.finder.addResultListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseEnterStringIntoFindField(findbar, str) {
|
||||
let promise = promiseFindResult(findbar, str);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let event = document.createEvent("KeyboardEvent");
|
||||
event.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, 0, str.charCodeAt(i));
|
||||
findbar._findField.inputField.dispatchEvent(event);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
function promiseTestHighlighterOutput(browser, word, expectedResult, extraTest = () => {}) {
|
||||
return ContentTask.spawn(browser, { word, expectedResult, extraTest: extraTest.toSource() },
|
||||
async function({ word, expectedResult, extraTest }) {
|
||||
Cu.import("resource://gre/modules/Timer.jsm", this);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let stubbed = {};
|
||||
let callCounts = {
|
||||
insertCalls: [],
|
||||
removeCalls: [],
|
||||
animationCalls: []
|
||||
};
|
||||
let lastMaskNode, lastOutlineNode;
|
||||
let rects = [];
|
||||
|
||||
// Amount of milliseconds to wait after the last time one of our stubs
|
||||
// was called.
|
||||
const kTimeoutMs = 1000;
|
||||
// The initial timeout may wait for a while for results to come in.
|
||||
let timeout = setTimeout(() => finish(false, "Timeout"), kTimeoutMs * 5);
|
||||
|
||||
function finish(ok = true, message = "finished with error") {
|
||||
// Restore the functions we stubbed out.
|
||||
try {
|
||||
content.document.insertAnonymousContent = stubbed.insert;
|
||||
content.document.removeAnonymousContent = stubbed.remove;
|
||||
} catch (ex) {}
|
||||
stubbed = {};
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (expectedResult.rectCount !== 0)
|
||||
Assert.ok(ok, message);
|
||||
|
||||
Assert.greaterOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[0],
|
||||
`Min. insert calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[1],
|
||||
`Max. insert calls should match for '${word}'.`);
|
||||
Assert.greaterOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[0],
|
||||
`Min. remove calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[1],
|
||||
`Max. remove calls should match for '${word}'.`);
|
||||
|
||||
// We reached the amount of calls we expected, so now we can check
|
||||
// the amount of rects.
|
||||
if (!lastMaskNode && expectedResult.rectCount !== 0) {
|
||||
Assert.ok(false, `No mask node found, but expected ${expectedResult.rectCount} rects.`);
|
||||
}
|
||||
|
||||
Assert.equal(rects.length, expectedResult.rectCount,
|
||||
`Amount of inserted rects should match for '${word}'.`);
|
||||
|
||||
if ("animationCalls" in expectedResult) {
|
||||
Assert.greaterOrEqual(callCounts.animationCalls.length,
|
||||
expectedResult.animationCalls[0], `Min. animation calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.animationCalls.length,
|
||||
expectedResult.animationCalls[1], `Max. animation calls should match for '${word}'.`);
|
||||
}
|
||||
|
||||
// Allow more specific assertions to be tested in `extraTest`.
|
||||
// eslint-disable-next-line no-eval
|
||||
extraTest = eval(extraTest);
|
||||
extraTest(lastMaskNode, lastOutlineNode, rects);
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
function stubAnonymousContentNode(domNode, anonNode) {
|
||||
let originals = [anonNode.setTextContentForElement,
|
||||
anonNode.setAttributeForElement, anonNode.removeAttributeForElement,
|
||||
anonNode.setCutoutRectsForElement, anonNode.setAnimationForElement];
|
||||
anonNode.setTextContentForElement = (id, text) => {
|
||||
try {
|
||||
(domNode.querySelector("#" + id) || domNode).textContent = text;
|
||||
} catch (ex) {}
|
||||
return originals[0].call(anonNode, id, text);
|
||||
};
|
||||
anonNode.setAttributeForElement = (id, attrName, attrValue) => {
|
||||
try {
|
||||
(domNode.querySelector("#" + id) || domNode).setAttribute(attrName, attrValue);
|
||||
} catch (ex) {}
|
||||
return originals[1].call(anonNode, id, attrName, attrValue);
|
||||
};
|
||||
anonNode.removeAttributeForElement = (id, attrName) => {
|
||||
try {
|
||||
let node = domNode.querySelector("#" + id) || domNode;
|
||||
if (node.hasAttribute(attrName))
|
||||
node.removeAttribute(attrName);
|
||||
} catch (ex) {}
|
||||
return originals[2].call(anonNode, id, attrName);
|
||||
};
|
||||
anonNode.setCutoutRectsForElement = (id, cutoutRects) => {
|
||||
rects = cutoutRects;
|
||||
return originals[3].call(anonNode, id, cutoutRects);
|
||||
};
|
||||
anonNode.setAnimationForElement = (id, keyframes, options) => {
|
||||
callCounts.animationCalls.push([keyframes, options]);
|
||||
return originals[4].call(anonNode, id, keyframes, options);
|
||||
};
|
||||
}
|
||||
|
||||
// Create a function that will stub the original version and collects
|
||||
// the arguments so we can check the results later.
|
||||
function stub(which) {
|
||||
stubbed[which] = content.document[which + "AnonymousContent"];
|
||||
let prop = which + "Calls";
|
||||
return function(node) {
|
||||
callCounts[prop].push(node);
|
||||
if (which == "insert") {
|
||||
if (node.outerHTML.indexOf("outlineMask") > -1)
|
||||
lastMaskNode = node;
|
||||
else
|
||||
lastOutlineNode = node;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
finish();
|
||||
}, kTimeoutMs);
|
||||
let res = stubbed[which].call(content.document, node);
|
||||
if (which == "insert")
|
||||
stubAnonymousContentNode(node, res);
|
||||
return res;
|
||||
};
|
||||
}
|
||||
content.document.insertAnonymousContent = stub("insert");
|
||||
content.document.removeAnonymousContent = stub("remove");
|
||||
});
|
||||
});
|
||||
}
|
||||
const kPrefHighlightAll = "findbar.highlightAll";
|
||||
const kPrefModalHighlight = "findbar.modalHighlight";
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({ set: [
|
||||
[kHighlightAllPref, true],
|
||||
[kPrefHighlightAll, true],
|
||||
[kPrefModalHighlight, true]
|
||||
]});
|
||||
});
|
||||
|
@ -205,7 +25,8 @@ add_task(async function testModalResults() {
|
|||
rectCount: 2,
|
||||
insertCalls: [5, 6],
|
||||
removeCalls: [4, 5],
|
||||
extraTest(maskNode, outlineNode, rects) {
|
||||
// eslint-disable-next-line object-shorthand
|
||||
extraTest: function(maskNode, outlineNode, rects) {
|
||||
Assert.equal(outlineNode.getElementsByTagName("div").length, 2,
|
||||
"There should be multiple rects drawn");
|
||||
}
|
||||
|
@ -373,7 +194,7 @@ add_task(async function testHighlightAllToggle() {
|
|||
removeCalls: [1, 2]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
await SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
|
||||
await SpecialPowers.pushPrefEnv({ "set": [[ kPrefHighlightAll, false ]] });
|
||||
await promise;
|
||||
|
||||
// For posterity, let's switch back.
|
||||
|
@ -383,7 +204,7 @@ add_task(async function testHighlightAllToggle() {
|
|||
removeCalls: [0, 1]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
await SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, true ]] });
|
||||
await SpecialPowers.pushPrefEnv({ "set": [[ kPrefHighlightAll, true ]] });
|
||||
await promise;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
"use strict";
|
||||
|
||||
const kPrefHighlightAll = "findbar.highlightAll";
|
||||
const kPrefModalHighlight = "findbar.modalHighlight";
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({ set: [
|
||||
[kPrefHighlightAll, true],
|
||||
[kPrefModalHighlight, true]
|
||||
]});
|
||||
});
|
||||
|
||||
add_task(async function testIframeOffset() {
|
||||
let url = kFixtureBaseURL + "file_FinderIframeTest.html";
|
||||
|
||||
await BrowserTestUtils.withNewTab(url, async function(browser) {
|
||||
let findbar = gBrowser.getFindBar();
|
||||
await promiseOpenFindbar(findbar);
|
||||
|
||||
let word = "frame";
|
||||
let expectedResult = {
|
||||
rectCount: 12,
|
||||
insertCalls: [2, 4],
|
||||
removeCalls: [0, 2]
|
||||
};
|
||||
let promise = promiseTestHighlighterOutput(browser, word, expectedResult, (maskNode, outlineNode, rects) => {
|
||||
Assert.equal(rects.length, expectedResult.rectCount, "Rect counts should match");
|
||||
// Checks to guard against regressing this functionality:
|
||||
let expectedOffsets = [
|
||||
{ x: 16, y: 60 },
|
||||
{ x: 68, y: 104 },
|
||||
{ x: 21, y: 215 },
|
||||
{ x: 78, y: 264 },
|
||||
{ x: 21, y: 375 },
|
||||
{ x: 78, y: 424 },
|
||||
{ x: 20, y: 534 },
|
||||
{ x: 93, y: 534 },
|
||||
{ x: 71, y: 577 },
|
||||
{ x: 145, y: 577 }
|
||||
]
|
||||
for (let i = 1, l = rects.length - 1; i < l; ++i) {
|
||||
let rect = rects[i];
|
||||
let expected = expectedOffsets[i - 1];
|
||||
Assert.equal(Math.floor(rect.x), expected.x, "Horizontal offset should match for rect " + i);
|
||||
Assert.equal(Math.floor(rect.y), expected.y, "Vertical offset should match for rect " + i);
|
||||
}
|
||||
});
|
||||
await promiseEnterStringIntoFindField(findbar, word);
|
||||
await promise;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Test (i)frame offsets, bug 1366646</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>top level frame</p>
|
||||
<iframe src="data:text/html,<p>frame without border</p>
|
||||
<iframe src='data:text/html,<p>nested frame without border</p>' height='50' width='100%' style='background-color: yellow; border: 0'></iframe>
|
||||
" style="background-color: pink; border: 0" width="100%" height="150"></iframe>
|
||||
<iframe src="data:text/html,<p>frame with 5px border</p>
|
||||
<iframe src='data:text/html,<p>nested frame with 5px border</p>' height='50' width='100%' style='background-color: yellow; border: solid 5px black'></iframe>
|
||||
" style="background-color: pink; border: solid 5px black" width="100%" height="150"></iframe>
|
||||
<iframe src="data:text/html,<p>frame with 5px padding</p>
|
||||
<iframe src='data:text/html,<p>nested frame with 5px padding</p>' height='50' width='100%' style='background-color: yellow; border: 0; padding: 5px'></iframe>
|
||||
" style="background-color: pink; border: 0; padding: 5px" width="100%" height="150"></iframe>
|
||||
<!-- Testing deprecated HTML4 iframe properties too: -->
|
||||
<iframe src="data:text/html,<p>frame with frameborder, marginwidth/ height and 5px padding</p>
|
||||
<iframe src='data:text/html,<p>nested frame with frameborder, marginwidth/ height</p>' height='50' width='100%' frameborder='1' marginheight='5' marginwidth='5' style='background-color: yellow;'></iframe>
|
||||
" frameborder="1" marginheight="5" marginwidth="5" style="background-color: pink; padding: 5px" width="100%" height="150"></iframe>
|
||||
</body></html>
|
|
@ -1,3 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", "resource://gre/modules/Timer.jsm");
|
||||
|
||||
const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/";
|
||||
|
||||
function removeDupes(list) {
|
||||
let j = 0;
|
||||
for (let i = 1; i < list.length; i++) {
|
||||
|
@ -19,3 +25,173 @@ function compareLists(list1, list2, kind) {
|
|||
is(String(list1), String(list2), `${kind} URLs correct`);
|
||||
}
|
||||
|
||||
function promiseOpenFindbar(findbar) {
|
||||
findbar.onFindCommand();
|
||||
return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise;
|
||||
}
|
||||
|
||||
function promiseFindResult(findbar, str = null) {
|
||||
let highlightFinished = false;
|
||||
let findFinished = false;
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
onFindResult({ searchString }) {
|
||||
if (str !== null && str != searchString) {
|
||||
return;
|
||||
}
|
||||
findFinished = true;
|
||||
if (highlightFinished) {
|
||||
findbar.browser.finder.removeResultListener(listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onHighlightFinished() {
|
||||
highlightFinished = true;
|
||||
if (findFinished) {
|
||||
findbar.browser.finder.removeResultListener(listener);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
onMatchesCountResult: () => {}
|
||||
};
|
||||
findbar.browser.finder.addResultListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseEnterStringIntoFindField(findbar, str) {
|
||||
let promise = promiseFindResult(findbar, str);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let event = document.createEvent("KeyboardEvent");
|
||||
event.initKeyEvent("keypress", true, true, null, false, false,
|
||||
false, false, 0, str.charCodeAt(i));
|
||||
findbar._findField.inputField.dispatchEvent(event);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
function promiseTestHighlighterOutput(browser, word, expectedResult, extraTest = () => {}) {
|
||||
return ContentTask.spawn(browser, { word, expectedResult, extraTest: extraTest.toSource() },
|
||||
async function({ word, expectedResult, extraTest }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let stubbed = {};
|
||||
let callCounts = {
|
||||
insertCalls: [],
|
||||
removeCalls: [],
|
||||
animationCalls: []
|
||||
};
|
||||
let lastMaskNode, lastOutlineNode;
|
||||
let rects = [];
|
||||
|
||||
// Amount of milliseconds to wait after the last time one of our stubs
|
||||
// was called.
|
||||
const kTimeoutMs = 1000;
|
||||
// The initial timeout may wait for a while for results to come in.
|
||||
let timeout = setTimeout(() => finish(false, "Timeout"), kTimeoutMs * 5);
|
||||
|
||||
function finish(ok = true, message = "finished with error") {
|
||||
// Restore the functions we stubbed out.
|
||||
try {
|
||||
content.document.insertAnonymousContent = stubbed.insert;
|
||||
content.document.removeAnonymousContent = stubbed.remove;
|
||||
} catch (ex) {}
|
||||
stubbed = {};
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (expectedResult.rectCount !== 0)
|
||||
Assert.ok(ok, message);
|
||||
|
||||
Assert.greaterOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[0],
|
||||
`Min. insert calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.insertCalls.length, expectedResult.insertCalls[1],
|
||||
`Max. insert calls should match for '${word}'.`);
|
||||
Assert.greaterOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[0],
|
||||
`Min. remove calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.removeCalls.length, expectedResult.removeCalls[1],
|
||||
`Max. remove calls should match for '${word}'.`);
|
||||
|
||||
// We reached the amount of calls we expected, so now we can check
|
||||
// the amount of rects.
|
||||
if (!lastMaskNode && expectedResult.rectCount !== 0) {
|
||||
Assert.ok(false, `No mask node found, but expected ${expectedResult.rectCount} rects.`);
|
||||
}
|
||||
|
||||
Assert.equal(rects.length, expectedResult.rectCount,
|
||||
`Amount of inserted rects should match for '${word}'.`);
|
||||
|
||||
if ("animationCalls" in expectedResult) {
|
||||
Assert.greaterOrEqual(callCounts.animationCalls.length,
|
||||
expectedResult.animationCalls[0], `Min. animation calls should match for '${word}'.`);
|
||||
Assert.lessOrEqual(callCounts.animationCalls.length,
|
||||
expectedResult.animationCalls[1], `Max. animation calls should match for '${word}'.`);
|
||||
}
|
||||
|
||||
// Allow more specific assertions to be tested in `extraTest`.
|
||||
// eslint-disable-next-line no-eval
|
||||
extraTest = eval(extraTest);
|
||||
extraTest(lastMaskNode, lastOutlineNode, rects);
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
function stubAnonymousContentNode(domNode, anonNode) {
|
||||
let originals = [anonNode.setTextContentForElement,
|
||||
anonNode.setAttributeForElement, anonNode.removeAttributeForElement,
|
||||
anonNode.setCutoutRectsForElement, anonNode.setAnimationForElement];
|
||||
anonNode.setTextContentForElement = (id, text) => {
|
||||
try {
|
||||
(domNode.querySelector("#" + id) || domNode).textContent = text;
|
||||
} catch (ex) {}
|
||||
return originals[0].call(anonNode, id, text);
|
||||
};
|
||||
anonNode.setAttributeForElement = (id, attrName, attrValue) => {
|
||||
try {
|
||||
(domNode.querySelector("#" + id) || domNode).setAttribute(attrName, attrValue);
|
||||
} catch (ex) {}
|
||||
return originals[1].call(anonNode, id, attrName, attrValue);
|
||||
};
|
||||
anonNode.removeAttributeForElement = (id, attrName) => {
|
||||
try {
|
||||
let node = domNode.querySelector("#" + id) || domNode;
|
||||
if (node.hasAttribute(attrName))
|
||||
node.removeAttribute(attrName);
|
||||
} catch (ex) {}
|
||||
return originals[2].call(anonNode, id, attrName);
|
||||
};
|
||||
anonNode.setCutoutRectsForElement = (id, cutoutRects) => {
|
||||
rects = cutoutRects;
|
||||
return originals[3].call(anonNode, id, cutoutRects);
|
||||
};
|
||||
anonNode.setAnimationForElement = (id, keyframes, options) => {
|
||||
callCounts.animationCalls.push([keyframes, options]);
|
||||
return originals[4].call(anonNode, id, keyframes, options);
|
||||
};
|
||||
}
|
||||
|
||||
// Create a function that will stub the original version and collects
|
||||
// the arguments so we can check the results later.
|
||||
function stub(which) {
|
||||
stubbed[which] = content.document[which + "AnonymousContent"];
|
||||
let prop = which + "Calls";
|
||||
return function(node) {
|
||||
callCounts[prop].push(node);
|
||||
if (which == "insert") {
|
||||
if (node.outerHTML.indexOf("outlineMask") > -1)
|
||||
lastMaskNode = node;
|
||||
else
|
||||
lastOutlineNode = node;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
finish();
|
||||
}, kTimeoutMs);
|
||||
let res = stubbed[which].call(content.document, node);
|
||||
if (which == "insert")
|
||||
stubAnonymousContentNode(node, res);
|
||||
return res;
|
||||
};
|
||||
}
|
||||
content.document.insertAnonymousContent = stub("insert");
|
||||
content.document.removeAnonymousContent = stub("remove");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче