зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to central, a=merge
This commit is contained in:
Коммит
1adf793988
|
@ -128,6 +128,13 @@ var StarUI = {
|
|||
}
|
||||
this.panel.hidePopup();
|
||||
break;
|
||||
// This case is for catching character-generating keypresses
|
||||
case 0:
|
||||
let accessKey = document.getElementById("key_close");
|
||||
if (eventMatchesKey(aEvent, accessKey)) {
|
||||
this.panel.hidePopup();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "mouseout":
|
||||
|
|
|
@ -1367,6 +1367,15 @@ var gBrowserInit = {
|
|||
|
||||
PanicButtonNotifier.init();
|
||||
});
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", function() {
|
||||
for (let panel of document.querySelectorAll("panel[tabspecific='true']")) {
|
||||
if (panel.state == "open") {
|
||||
panel.hidePopup();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.delayedStartupFinished = true;
|
||||
|
||||
Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
|
||||
|
@ -6613,10 +6622,18 @@ var gIdentityHandler = {
|
|||
return this._identityPopupInsecureLoginFormsLearnMore =
|
||||
document.getElementById("identity-popup-insecure-login-forms-learn-more");
|
||||
},
|
||||
get _identityIconLabels () {
|
||||
delete this._identityIconLabels;
|
||||
return this._identityIconLabels = document.getElementById("identity-icon-labels");
|
||||
},
|
||||
get _identityIconLabel () {
|
||||
delete this._identityIconLabel;
|
||||
return this._identityIconLabel = document.getElementById("identity-icon-label");
|
||||
},
|
||||
get _connectionIcon () {
|
||||
delete this._connectionIcon;
|
||||
return this._connectionIcon = document.getElementById("connection-icon");
|
||||
},
|
||||
get _overrideService () {
|
||||
delete this._overrideService;
|
||||
return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
|
||||
|
@ -6905,7 +6922,6 @@ var gIdentityHandler = {
|
|||
// pages, either already insecure or with mixed active content loaded.
|
||||
this._identityBox.classList.add("insecureLoginForms");
|
||||
}
|
||||
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
|
||||
}
|
||||
|
||||
if (this._isCertUserOverridden) {
|
||||
|
@ -6944,7 +6960,8 @@ var gIdentityHandler = {
|
|||
}
|
||||
|
||||
// Push the appropriate strings out to the UI
|
||||
this._identityBox.tooltipText = tooltip;
|
||||
this._connectionIcon.tooltipText = tooltip;
|
||||
this._identityIconLabels.tooltipText = tooltip;
|
||||
this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
|
||||
this._identityIconLabel.value = icon_label;
|
||||
this._identityIconCountryLabel.value = icon_country_label;
|
||||
|
@ -7103,14 +7120,14 @@ var gIdentityHandler = {
|
|||
|
||||
// Fill in the CA name if we have a valid TLS certificate.
|
||||
if (this._isSecure || this._isCertUserOverridden) {
|
||||
verifier = this._identityBox.tooltipText;
|
||||
verifier = this._identityIconLabels.tooltipText;
|
||||
}
|
||||
|
||||
// Fill in organization information if we have a valid EV certificate.
|
||||
if (this._isEV) {
|
||||
let iData = this.getIdentityData();
|
||||
host = owner = iData.subjectOrg;
|
||||
verifier = this._identityBox.tooltipText;
|
||||
verifier = this._identityIconLabels.tooltipText;
|
||||
|
||||
// Build an appropriate supplemental block out of whatever location data we have
|
||||
if (iData.city)
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
orient="vertical"
|
||||
ignorekeys="true"
|
||||
hidden="true"
|
||||
tabspecific="true"
|
||||
onpopupshown="StarUI.panelShown(event);"
|
||||
aria-labelledby="editBookmarkPanelTitle">
|
||||
<row id="editBookmarkPanelHeader" align="center" hidden="true">
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const gTests = [
|
||||
test_eventMatchesKey,
|
||||
test_getTopWin,
|
||||
test_getBoolPref,
|
||||
test_openNewTabWith,
|
||||
|
@ -25,6 +26,57 @@ function runNextTest() {
|
|||
}
|
||||
}
|
||||
|
||||
function test_eventMatchesKey() {
|
||||
let eventMatchResult;
|
||||
let key;
|
||||
let checkEvent = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
eventMatchResult = eventMatchesKey(e, key);
|
||||
}
|
||||
document.addEventListener("keypress", checkEvent);
|
||||
|
||||
try {
|
||||
key = document.createElement("key");
|
||||
let keyset = document.getElementById("mainKeyset");
|
||||
key.setAttribute("key", "t");
|
||||
key.setAttribute("modifiers", "accel");
|
||||
keyset.appendChild(key);
|
||||
EventUtils.synthesizeKey("t", {accelKey: true});
|
||||
is(eventMatchResult, true, "eventMatchesKey: one modifier");
|
||||
keyset.removeChild(key);
|
||||
|
||||
key = document.createElement("key");
|
||||
key.setAttribute("key", "g");
|
||||
key.setAttribute("modifiers", "accel,shift");
|
||||
keyset.appendChild(key);
|
||||
EventUtils.synthesizeKey("g", {accelKey: true, shiftKey: true});
|
||||
is(eventMatchResult, true, "eventMatchesKey: combination modifiers");
|
||||
keyset.removeChild(key);
|
||||
|
||||
key = document.createElement("key");
|
||||
key.setAttribute("key", "w");
|
||||
key.setAttribute("modifiers", "accel");
|
||||
keyset.appendChild(key);
|
||||
EventUtils.synthesizeKey("f", {accelKey: true});
|
||||
is(eventMatchResult, false, "eventMatchesKey: mismatch keys");
|
||||
keyset.removeChild(key);
|
||||
|
||||
key = document.createElement("key");
|
||||
key.setAttribute("keycode", "VK_DELETE");
|
||||
keyset.appendChild(key);
|
||||
EventUtils.synthesizeKey("VK_DELETE", {accelKey: true});
|
||||
is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers");
|
||||
keyset.removeChild(key);
|
||||
} finally {
|
||||
// Make sure to remove the event listener so future tests don't
|
||||
// fail when they simulate key presses.
|
||||
document.removeEventListener("keypress", checkEvent);
|
||||
};
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
function test_getTopWin() {
|
||||
is(getTopWin(), window, "got top window");
|
||||
runNextTest();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[browser_closeTabSpecificPanels.js]
|
||||
[browser_multiplePrompts.js]
|
||||
[browser_openPromptInBackgroundTab.js]
|
||||
support-files = openPromptOffTimeout.html
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
"use strict";
|
||||
|
||||
/*
|
||||
* This test creates multiple panels, one that has been tagged as specific to its tab's content
|
||||
* and one that isn't. When a tab loses focus, panel specific to that tab should close.
|
||||
* The non-specific panel should remain open.
|
||||
*
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let tab1 = gBrowser.addTab("http://mochi.test:8888/#0");
|
||||
let tab2 = gBrowser.addTab("http://mochi.test:8888/#1");
|
||||
let specificPanel = document.createElement("panel");
|
||||
specificPanel.setAttribute("tabspecific", "true");
|
||||
let generalPanel = document.createElement("panel");
|
||||
let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
anchor.appendChild(specificPanel);
|
||||
anchor.appendChild(generalPanel);
|
||||
is(specificPanel.state, "closed", "specificPanel starts as closed");
|
||||
is(generalPanel.state, "closed", "generalPanel starts as closed");
|
||||
|
||||
let specificPanelPromise = BrowserTestUtils.waitForEvent(specificPanel, "popupshown");
|
||||
specificPanel.openPopupAtScreen(210, 210);
|
||||
yield specificPanelPromise;
|
||||
is(specificPanel.state, "open", "specificPanel has been opened");
|
||||
|
||||
let generalPanelPromise = BrowserTestUtils.waitForEvent(generalPanel, "popupshown");
|
||||
generalPanel.openPopupAtScreen(510,510);
|
||||
yield generalPanelPromise;
|
||||
is(generalPanel.state, "open", "generalPanel has been opened");
|
||||
|
||||
gBrowser.tabContainer.advanceSelectedTab(-1, true);
|
||||
is(specificPanel.state, "closed", "specificPanel panel is closed after its tab loses focus");
|
||||
is(generalPanel.state, "open", "generalPanel is still open after tab switch");
|
||||
|
||||
specificPanel.remove();
|
||||
generalPanel.remove();
|
||||
gBrowser.removeTab(tab1);
|
||||
gBrowser.removeTab(tab2);
|
||||
});
|
|
@ -478,6 +478,46 @@ function closeMenus(node)
|
|||
}
|
||||
}
|
||||
|
||||
/** This function takes in a key element and compares it to the keys pressed during an event.
|
||||
*
|
||||
* @param aEvent
|
||||
* The KeyboardEvent event you want to compare against your key.
|
||||
*
|
||||
* @param aKey
|
||||
* The <key> element checked to see if it was called in aEvent.
|
||||
* For example, aKey can be a variable set to document.getElementById("key_close")
|
||||
* to check if the close command key was pressed in aEvent.
|
||||
*/
|
||||
function eventMatchesKey(aEvent, aKey)
|
||||
{
|
||||
let keyPressed = aKey.getAttribute("key").toLowerCase();
|
||||
let keyModifiers = aKey.getAttribute("modifiers");
|
||||
let modifiers = ["Alt", "Control", "Meta", "Shift"];
|
||||
|
||||
if (aEvent.key != keyPressed) {
|
||||
return false;
|
||||
}
|
||||
let eventModifiers = modifiers.filter(modifier => aEvent.getModifierState(modifier));
|
||||
// Check if aEvent has a modifier and aKey doesn't
|
||||
if (eventModifiers.length > 0 && keyModifiers.length == 0) {
|
||||
return false;
|
||||
}
|
||||
// Check whether aKey's modifiers match aEvent's modifiers
|
||||
if (keyModifiers) {
|
||||
keyModifiers = keyModifiers.split(/[\s,]+/);
|
||||
// Capitalize first letter of aKey's modifers to compare to aEvent's modifier
|
||||
keyModifiers.forEach(function(modifier, index) {
|
||||
if (modifier == "accel") {
|
||||
keyModifiers[index] = AppConstants.platform == "macosx" ? "Meta" : "Control";
|
||||
} else {
|
||||
keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
|
||||
}
|
||||
});
|
||||
return modifiers.every(modifier => keyModifiers.includes(modifier) == aEvent.getModifierState(modifier));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gather all descendent text under given document node.
|
||||
function gatherTextUnder ( root )
|
||||
{
|
||||
|
|
|
@ -1358,6 +1358,9 @@ var CustomizableUIInternal = {
|
|||
}
|
||||
node.setAttribute("removable", aWidget.removable);
|
||||
node.setAttribute("overflows", aWidget.overflows);
|
||||
if (aWidget.tabSpecific) {
|
||||
node.setAttribute("tabspecific", aWidget.tabSpecific);
|
||||
}
|
||||
node.setAttribute("label", this.getLocalizedProperty(aWidget, "label"));
|
||||
let additionalTooltipArguments = [];
|
||||
if (aWidget.shortcutId) {
|
||||
|
@ -2310,6 +2313,7 @@ var CustomizableUIInternal = {
|
|||
overflows: true,
|
||||
defaultArea: null,
|
||||
shortcutId: null,
|
||||
tabSpecific: false,
|
||||
tooltiptext: null,
|
||||
showInPrivateBrowsing: true,
|
||||
_introducedInVersion: -1,
|
||||
|
@ -2340,7 +2344,7 @@ var CustomizableUIInternal = {
|
|||
}
|
||||
}
|
||||
|
||||
const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows"];
|
||||
const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows", "tabSpecific"];
|
||||
for (let prop of kOptBoolProps) {
|
||||
if (typeof aData[prop] == "boolean") {
|
||||
widget[prop] = aData[prop];
|
||||
|
|
|
@ -335,6 +335,9 @@ const PanelUI = {
|
|||
tempPanel.setAttribute("id", "customizationui-widget-panel");
|
||||
tempPanel.setAttribute("class", "cui-widget-panel");
|
||||
tempPanel.setAttribute("viewId", aViewId);
|
||||
if (aAnchor.getAttribute("tabspecific")) {
|
||||
tempPanel.setAttribute("tabspecific", true);
|
||||
}
|
||||
if (this._disableAnimations) {
|
||||
tempPanel.setAttribute("animate", "false");
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ function CreatePocketWidget(reason) {
|
|||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||
introducedInVersion: "pref",
|
||||
type: "view",
|
||||
tabSpecific: true,
|
||||
viewId: "PanelUI-pocketView",
|
||||
label: gPocketBundle.GetStringFromName("pocket-button.label"),
|
||||
tooltiptext: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
|
||||
|
|
|
@ -330,8 +330,6 @@ identity.identified.verifier=Verified by: %S
|
|||
identity.identified.verified_by_you=You have added a security exception for this site.
|
||||
identity.identified.state_and_country=%S, %S
|
||||
|
||||
identity.unknown.tooltip=This website does not supply identity information.
|
||||
|
||||
identity.icon.tooltip=Show site information
|
||||
|
||||
trackingProtection.intro.title=How Tracking Protection works
|
||||
|
|
|
@ -11,7 +11,7 @@ define(function (require, exports, module) {
|
|||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { isGrip, getFileName } = require("./rep-utils");
|
||||
const { isGrip, getURLDisplayString } = require("./rep-utils");
|
||||
|
||||
// Shortcuts
|
||||
const { span } = React.DOM;
|
||||
|
@ -28,7 +28,7 @@ define(function (require, exports, module) {
|
|||
|
||||
getLocation: function (grip) {
|
||||
let location = grip.preview.location;
|
||||
return location ? getFileName(location) : "";
|
||||
return location ? getURLDisplayString(location) : "";
|
||||
},
|
||||
|
||||
getTitle: function (grip) {
|
||||
|
|
|
@ -11,7 +11,7 @@ define(function (require, exports, module) {
|
|||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { isGrip } = require("./rep-utils");
|
||||
const { isGrip, getURLDisplayString } = require("./rep-utils");
|
||||
|
||||
// Shortcuts
|
||||
const { span } = React.DOM;
|
||||
|
@ -42,7 +42,7 @@ define(function (require, exports, module) {
|
|||
},
|
||||
|
||||
getDescription: function (grip) {
|
||||
return grip.preview.url;
|
||||
return getURLDisplayString(grip.preview.url);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
|
|
|
@ -109,6 +109,10 @@ define(function (require, exports, module) {
|
|||
return {};
|
||||
}
|
||||
|
||||
function getURLDisplayString(url) {
|
||||
return cropString(url);
|
||||
}
|
||||
|
||||
function isDataURL(url) {
|
||||
return (url && url.substr(0, 5) == "data:");
|
||||
}
|
||||
|
@ -147,4 +151,5 @@ define(function (require, exports, module) {
|
|||
exports.parseURLParams = parseURLParams;
|
||||
exports.parseURLEncodedText = parseURLEncodedText;
|
||||
exports.getFileName = getFileName;
|
||||
exports.getURLDisplayString = getURLDisplayString;
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ define(function (require, exports, module) {
|
|||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { isGrip, getFileName } = require("./rep-utils");
|
||||
const { isGrip, getURLDisplayString } = require("./rep-utils");
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
@ -41,7 +41,7 @@ define(function (require, exports, module) {
|
|||
getLocation: function (grip) {
|
||||
// Embedded stylesheets don't have URL and so, no preview.
|
||||
let url = grip.preview ? grip.preview.url : "";
|
||||
return url ? getFileName(url) : "";
|
||||
return url ? getURLDisplayString(url) : "";
|
||||
},
|
||||
|
||||
render: function () {
|
||||
|
|
|
@ -11,7 +11,7 @@ define(function (require, exports, module) {
|
|||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { isGrip, cropString } = require("./rep-utils");
|
||||
const { isGrip, getURLDisplayString } = require("./rep-utils");
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
@ -38,7 +38,7 @@ define(function (require, exports, module) {
|
|||
},
|
||||
|
||||
getLocation: function (grip) {
|
||||
return cropString(grip.preview.url);
|
||||
return getURLDisplayString(grip.preview.url);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
|
|
|
@ -41,7 +41,7 @@ window.onload = Task.async(function* () {
|
|||
|
||||
// Test rendering
|
||||
const renderedComponent = renderComponent(Document.rep, { object: gripStub });
|
||||
is(renderedComponent.textContent, "/en-US/firefox/new/", "Document rep has expected text content");
|
||||
is(renderedComponent.textContent, "https://www.mozilla.org/en-US/firefox/new/", "Document rep has expected text content");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
|
|
|
@ -39,7 +39,7 @@ window.onload = Task.async(function* () {
|
|||
|
||||
// Test rendering
|
||||
const renderedComponent = renderComponent(StyleSheet.rep, { object: gripStub });
|
||||
is(renderedComponent.textContent, "StyleSheet styles.css", "StyleSheet rep has expected text content");
|
||||
is(renderedComponent.textContent, "StyleSheet https://example.com/styles.css", "StyleSheet rep has expected text content");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
|
|
|
@ -436,7 +436,8 @@ html, body, #app, #memory-tool {
|
|||
* These subcolumns may have specific widths or need to flex.
|
||||
*/
|
||||
display: flex;
|
||||
text-align: end;
|
||||
/* Make sure units/decimals/... are always vertically aligned to right in both LTR and RTL locales */
|
||||
text-align: right;
|
||||
border-inline-end: var(--cell-border-color) 1px solid;
|
||||
}
|
||||
|
||||
|
@ -486,7 +487,11 @@ html, body, #app, #memory-tool {
|
|||
}
|
||||
|
||||
.heap-tree-number {
|
||||
padding-inline-start: 3px;
|
||||
padding: 0 3px;
|
||||
flex: 1;
|
||||
color: var(--theme-content-color3);
|
||||
/* Make sure number doesn't appear backwards on RTL locales */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.heap-tree-percent {
|
||||
|
@ -499,11 +504,6 @@ html, body, #app, #memory-tool {
|
|||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
|
||||
.heap-tree-number {
|
||||
flex: 1;
|
||||
color: var(--theme-content-color3);
|
||||
}
|
||||
|
||||
.heap-tree-percent {
|
||||
width: 4ch;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ function Finder(docShell) {
|
|||
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
|
||||
this._fastFind.init(docShell);
|
||||
|
||||
this._currentFoundRange = null;
|
||||
this._docShell = docShell;
|
||||
this._listeners = [];
|
||||
this._previousLink = null;
|
||||
|
@ -40,6 +41,8 @@ function Finder(docShell) {
|
|||
docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress)
|
||||
.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
BrowserUtils.getRootWindow(this._docShell).addEventListener("unload",
|
||||
this.onLocationChange.bind(this, { isTopLevel: true }));
|
||||
}
|
||||
|
||||
Finder.prototype = {
|
||||
|
@ -62,7 +65,8 @@ Finder.prototype = {
|
|||
.getInterface(Ci.nsIWebProgress)
|
||||
.removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
this._listeners = [];
|
||||
this._fastFind = this._docShell = this._previousLink = this._highlighter = null;
|
||||
this._currentFoundRange = this._fastFind = this._docShell = this._previousLink =
|
||||
this._highlighter = null;
|
||||
},
|
||||
|
||||
addResultListener: function (aListener) {
|
||||
|
@ -139,11 +143,15 @@ Finder.prototype = {
|
|||
},
|
||||
|
||||
set caseSensitive(aSensitive) {
|
||||
if (this._fastFind.caseSensitive === aSensitive)
|
||||
return;
|
||||
this._fastFind.caseSensitive = aSensitive;
|
||||
this.iterator.reset();
|
||||
},
|
||||
|
||||
set entireWord(aEntireWord) {
|
||||
if (this._fastFind.entireWord === aEntireWord)
|
||||
return;
|
||||
this._fastFind.entireWord = aEntireWord;
|
||||
this.iterator.reset();
|
||||
},
|
||||
|
@ -335,6 +343,8 @@ Finder.prototype = {
|
|||
},
|
||||
|
||||
onHighlightAllChange(highlightAll) {
|
||||
if (this._iterator)
|
||||
this._iterator.reset();
|
||||
if (this._highlighter)
|
||||
this._highlighter.onHighlightAllChange(highlightAll);
|
||||
},
|
||||
|
@ -379,12 +389,21 @@ Finder.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_notifyMatchesCount: function(result) {
|
||||
_notifyMatchesCount: function(result = this._currentMatchesCountResult) {
|
||||
if (!result)
|
||||
return;
|
||||
// The `_currentFound` property is only used for internal bookkeeping.
|
||||
delete result._currentFound;
|
||||
if (result.total == this._currentMatchLimit)
|
||||
result.total = -1;
|
||||
|
||||
for (let l of this._listeners) {
|
||||
try {
|
||||
l.onMatchesCountResult(result);
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
this._currentMatchesCountResult = null;
|
||||
},
|
||||
|
||||
requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
|
||||
|
@ -398,12 +417,14 @@ Finder.prototype = {
|
|||
}
|
||||
|
||||
let window = this._getWindow();
|
||||
let result = {
|
||||
this._currentFoundRange = this._fastFind.getFoundRange();
|
||||
this._currentMatchLimit = aMatchLimit;
|
||||
|
||||
this._currentMatchesCountResult = {
|
||||
total: 0,
|
||||
current: 0,
|
||||
_currentFound: false
|
||||
};
|
||||
let foundRange = this._fastFind.getFoundRange();
|
||||
|
||||
this.iterator.start({
|
||||
caseSensitive: this._fastFind.caseSensitive,
|
||||
|
@ -411,30 +432,38 @@ Finder.prototype = {
|
|||
finder: this,
|
||||
limit: aMatchLimit,
|
||||
linksOnly: aLinksOnly,
|
||||
onRange: range => {
|
||||
++result.total;
|
||||
if (!result._currentFound) {
|
||||
++result.current;
|
||||
result._currentFound = (foundRange &&
|
||||
range.startContainer == foundRange.startContainer &&
|
||||
range.startOffset == foundRange.startOffset &&
|
||||
range.endContainer == foundRange.endContainer &&
|
||||
range.endOffset == foundRange.endOffset);
|
||||
}
|
||||
},
|
||||
listener: this,
|
||||
useCache: true,
|
||||
word: aWord
|
||||
}).then(() => {
|
||||
// The `_currentFound` property is only used for internal bookkeeping.
|
||||
delete result._currentFound;
|
||||
|
||||
if (result.total == aMatchLimit)
|
||||
result.total = -1;
|
||||
|
||||
this._notifyMatchesCount(result);
|
||||
});
|
||||
}).then(this._notifyMatchesCount.bind(this));
|
||||
},
|
||||
|
||||
// FinderIterator listener implementation
|
||||
|
||||
onIteratorRangeFound(range) {
|
||||
let result = this._currentMatchesCountResult;
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
++result.total;
|
||||
if (!result._currentFound) {
|
||||
++result.current;
|
||||
result._currentFound = (this._currentFoundRange &&
|
||||
range.startContainer == this._currentFoundRange.startContainer &&
|
||||
range.startOffset == this._currentFoundRange.startOffset &&
|
||||
range.endContainer == this._currentFoundRange.endContainer &&
|
||||
range.endOffset == this._currentFoundRange.endOffset);
|
||||
}
|
||||
},
|
||||
|
||||
onIteratorReset() {},
|
||||
|
||||
onIteratorRestart({ word, linksOnly }) {
|
||||
this.requestMatchesCount(word, this._currentMatchLimit, linksOnly);
|
||||
},
|
||||
|
||||
onIteratorStart() {},
|
||||
|
||||
_getWindow: function () {
|
||||
return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
},
|
||||
|
@ -555,7 +584,7 @@ Finder.prototype = {
|
|||
return;
|
||||
|
||||
// Avoid leaking if we change the page.
|
||||
this._previousLink = null;
|
||||
this._previousLink = this._currentFoundRange = null;
|
||||
this.highlighter.onLocationChange();
|
||||
this.iterator.reset();
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
|
|||
return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
|
||||
});
|
||||
|
||||
const kContentChangeThresholdPx = 5;
|
||||
const kModalHighlightRepaintFreqMs = 10;
|
||||
const kHighlightAllPref = "findbar.highlightAll";
|
||||
const kModalHighlightPref = "findbar.modalHighlight";
|
||||
|
@ -153,6 +154,7 @@ function FinderHighlighter(finder) {
|
|||
this._currentFoundRange = null;
|
||||
this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
|
||||
this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
|
||||
this._lastIteratorParams = null;
|
||||
this.finder = finder;
|
||||
this.visible = false;
|
||||
}
|
||||
|
@ -207,56 +209,72 @@ FinderHighlighter.prototype = {
|
|||
let window = this.finder._getWindow();
|
||||
let controller = this.finder._getSelectionController(window);
|
||||
let doc = window.document;
|
||||
let found = false;
|
||||
|
||||
this.clear();
|
||||
this._found = false;
|
||||
|
||||
if (!controller || !doc || !doc.documentElement) {
|
||||
// Without the selection controller,
|
||||
// we are unable to (un)highlight any matches
|
||||
return found;
|
||||
return this._found;
|
||||
}
|
||||
|
||||
if (highlight) {
|
||||
yield this.iterator.start({
|
||||
let params = {
|
||||
caseSensitive: this.finder._fastFind.caseSensitive,
|
||||
entireWord: this.finder._fastFind.entireWord,
|
||||
linksOnly, word,
|
||||
finder: this.finder,
|
||||
onRange: range => {
|
||||
this.highlightRange(range, controller, window);
|
||||
found = true;
|
||||
},
|
||||
listener: this,
|
||||
useCache: true
|
||||
});
|
||||
if (found)
|
||||
this.finder._outlineLink(true);
|
||||
};
|
||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
||||
return this._found;
|
||||
if (params) {
|
||||
yield this.iterator.start(params);
|
||||
if (this._found)
|
||||
this.finder._outlineLink(true);
|
||||
}
|
||||
} else {
|
||||
this.hide(window);
|
||||
this.clear();
|
||||
this.iterator.reset();
|
||||
|
||||
// Removing the highlighting always succeeds, so return true.
|
||||
found = true;
|
||||
this._found = true;
|
||||
}
|
||||
|
||||
return found;
|
||||
return this._found;
|
||||
}),
|
||||
|
||||
// FinderIterator listener implementation
|
||||
|
||||
onIteratorRangeFound(range) {
|
||||
this.highlightRange(range);
|
||||
this._found = true;
|
||||
},
|
||||
|
||||
onIteratorReset() {
|
||||
this.clear();
|
||||
},
|
||||
|
||||
onIteratorRestart() {},
|
||||
|
||||
onIteratorStart(params) {
|
||||
// Save a clean params set for use later in the `update()` method.
|
||||
this._lastIteratorParams = params;
|
||||
this.clear();
|
||||
if (!this._modal)
|
||||
this.hide(this.finder._getWindow(), this.finder._fastFind.getFoundRange());
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a range to the find selection, i.e. highlight it, and if it's inside an
|
||||
* editable node, track it.
|
||||
*
|
||||
* @param {nsIDOMRange} range Range object to be highlighted
|
||||
* @param {nsISelectionController} controller Selection controller of the
|
||||
* document that the range belongs
|
||||
* to
|
||||
* @param {nsIDOMWindow} window Window object, whose DOM tree
|
||||
* is being traversed
|
||||
* @param {nsIDOMRange} range Range object to be highlighted
|
||||
*/
|
||||
highlightRange(range, controller, window) {
|
||||
highlightRange(range) {
|
||||
let node = range.startContainer;
|
||||
let editableNode = this._getEditableNode(node);
|
||||
let window = node.ownerDocument.defaultView;
|
||||
let controller = this.finder._getSelectionController(window);
|
||||
if (editableNode) {
|
||||
controller = editableNode.editor.selectionController;
|
||||
}
|
||||
|
@ -325,8 +343,11 @@ FinderHighlighter.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
if (!this._modal || !this.visible)
|
||||
return;
|
||||
if (this._modalRepaintScheduler) {
|
||||
window.clearTimeout(this._modalRepaintScheduler);
|
||||
this._modalRepaintScheduler = null;
|
||||
}
|
||||
this._lastWindowDimensions = null;
|
||||
|
||||
if (this._modalHighlightOutline)
|
||||
this._modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
|
||||
|
@ -364,15 +385,12 @@ FinderHighlighter.prototype = {
|
|||
let foundRange = this.finder._fastFind.getFoundRange();
|
||||
if (!this._modal) {
|
||||
if (this._highlightAll) {
|
||||
this._currentFoundRange = foundRange;
|
||||
let params = this.iterator.params;
|
||||
if (this._lastIteratorParams &&
|
||||
this.iterator._areParamsEqual(params, this._lastIteratorParams)) {
|
||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
||||
return;
|
||||
}
|
||||
this.hide(window, foundRange);
|
||||
if (params.word)
|
||||
if (params)
|
||||
this.highlight(true, params.word, params.linksOnly);
|
||||
this._lastIteratorParams = params;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -436,8 +454,7 @@ FinderHighlighter.prototype = {
|
|||
* keep to build the mask for.
|
||||
*/
|
||||
clear() {
|
||||
if (!this._modal)
|
||||
return;
|
||||
this._currentFoundRange = null;
|
||||
|
||||
// Reset the Map, because no range references a node anymore.
|
||||
if (this._modalHighlightRectsMap)
|
||||
|
@ -451,6 +468,8 @@ FinderHighlighter.prototype = {
|
|||
* everything when the user starts to find in page again.
|
||||
*/
|
||||
onLocationChange() {
|
||||
this.clear();
|
||||
|
||||
if (!this._modalHighlightOutline)
|
||||
return;
|
||||
|
||||
|
@ -740,13 +759,14 @@ 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 };
|
||||
maskNode.setAttribute("id", kMaskId);
|
||||
maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
|
||||
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
|
||||
if (this._brightText)
|
||||
maskNode.setAttribute("brighttext", "true");
|
||||
|
||||
if (paintContent) {
|
||||
if (paintContent || this._modalHighlightAllMask) {
|
||||
// Create a DOM node for each rectangle representing the ranges we found.
|
||||
let maskContent = [];
|
||||
const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
|
||||
|
@ -794,12 +814,39 @@ 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.
|
||||
*/
|
||||
_scheduleRepaintOfMask(window) {
|
||||
if (this._modalRepaintScheduler)
|
||||
_scheduleRepaintOfMask(window, contentChanged = false) {
|
||||
if (this._modalRepaintScheduler) {
|
||||
window.clearTimeout(this._modalRepaintScheduler);
|
||||
this._modalRepaintScheduler = window.setTimeout(
|
||||
this._repaintHighlightAllMask.bind(this, window), kModalHighlightRepaintFreqMs);
|
||||
this._modalRepaintScheduler = null;
|
||||
}
|
||||
|
||||
// When we request to repaint unconditionally, we mean to call
|
||||
// `_repaintHighlightAllMask()` right after the timeout.
|
||||
if (!this._unconditionalRepaintRequested)
|
||||
this._unconditionalRepaintRequested = !contentChanged;
|
||||
|
||||
this._modalRepaintScheduler = window.setTimeout(() => {
|
||||
if (this._unconditionalRepaintRequested) {
|
||||
this._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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.iterator.restart(this.finder);
|
||||
this._lastWindowDimensions = { width, height };
|
||||
this._repaintHighlightAllMask(window);
|
||||
}, kModalHighlightRepaintFreqMs);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -838,12 +885,12 @@ FinderHighlighter.prototype = {
|
|||
return;
|
||||
|
||||
this._highlightListeners = [
|
||||
this._scheduleRepaintOfMask.bind(this, window),
|
||||
this._scheduleRepaintOfMask.bind(this, window, true),
|
||||
this.hide.bind(this, window, null)
|
||||
];
|
||||
window.addEventListener("DOMContentLoaded", this._highlightListeners[0]);
|
||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||
target.addEventListener("MozAfterPaint", this._highlightListeners[0]);
|
||||
window.addEventListener("click", this._highlightListeners[1]);
|
||||
window.addEventListener("resize", this._highlightListeners[1]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -855,9 +902,9 @@ FinderHighlighter.prototype = {
|
|||
if (!this._highlightListeners)
|
||||
return;
|
||||
|
||||
window.removeEventListener("DOMContentLoaded", this._highlightListeners[0]);
|
||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||
target.removeEventListener("MozAfterPaint", this._highlightListeners[0]);
|
||||
window.removeEventListener("click", this._highlightListeners[1]);
|
||||
window.removeEventListener("resize", this._highlightListeners[1]);
|
||||
|
||||
this._highlightListeners = null;
|
||||
},
|
||||
|
|
|
@ -30,6 +30,8 @@ this.FinderIterator = {
|
|||
get kIterationSizeMax() { return kIterationSizeMax },
|
||||
|
||||
get params() {
|
||||
if (!this._currentParams && !this._previousParams)
|
||||
return null;
|
||||
return Object.assign({}, this._currentParams || this._previousParams);
|
||||
},
|
||||
|
||||
|
@ -42,9 +44,9 @@ this.FinderIterator = {
|
|||
* yield `undefined`, instead of a range.
|
||||
* Upon re-entrance after a break, we check if `stop()` was called during the
|
||||
* break and if so, we stop iterating.
|
||||
* Results are also passed to the `onRange` callback method, along with a flag
|
||||
* that specifies if the result comes from the cache or is fresh. The callback
|
||||
* also adheres to the `limit` flag.
|
||||
* Results are also passed to the `listener.onIteratorRangeFound` callback
|
||||
* method, along with a flag that specifies if the result comes from the cache
|
||||
* or is fresh. The callback also adheres to the `limit` flag.
|
||||
* The returned promise is resolved when 1) the limit is reached, 2) when all
|
||||
* the ranges have been found or 3) when `stop()` is called whilst iterating.
|
||||
*
|
||||
|
@ -58,14 +60,19 @@ this.FinderIterator = {
|
|||
* @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
|
||||
* hyperlink (used by QuickFind).
|
||||
* Optional, defaults to `false`.
|
||||
* @param {Function} options.onRange Callback invoked when a range is found
|
||||
* @param {Object} options.listener Listener object that implements the
|
||||
* following callback functions:
|
||||
* - onIteratorRangeFound({nsIDOMRange} range);
|
||||
* - onIteratorReset();
|
||||
* - onIteratorRestart({Object} iterParams);
|
||||
* - onIteratorStart({Object} iterParams);
|
||||
* @param {Boolean} [options.useCache] Whether to allow results already
|
||||
* present in the cache or demand fresh.
|
||||
* Optional, defaults to `false`.
|
||||
* @param {String} options.word Word to search for
|
||||
* @return {Promise}
|
||||
*/
|
||||
start({ caseSensitive, entireWord, finder, limit, linksOnly, onRange, useCache, word }) {
|
||||
start({ caseSensitive, entireWord, finder, limit, linksOnly, listener, useCache, word }) {
|
||||
// Take care of default values for non-required options.
|
||||
if (typeof limit != "number")
|
||||
limit = -1;
|
||||
|
@ -83,23 +90,27 @@ this.FinderIterator = {
|
|||
throw new Error("Missing required option 'finder'");
|
||||
if (!word)
|
||||
throw new Error("Missing required option 'word'");
|
||||
if (typeof onRange != "function")
|
||||
throw new TypeError("Missing valid, required option 'onRange'");
|
||||
if (typeof listener != "object" || !listener.onIteratorRangeFound)
|
||||
throw new TypeError("Missing valid, required option 'listener'");
|
||||
|
||||
// Don't add the same listener twice.
|
||||
if (this._listeners.has(onRange))
|
||||
throw new Error("Already listening to iterator results");
|
||||
// If the listener was added before, make sure the promise is resolved before
|
||||
// we replace it with another.
|
||||
if (this._listeners.has(listener)) {
|
||||
let { onEnd } = this._listeners.get(listener);
|
||||
if (onEnd)
|
||||
onEnd();
|
||||
}
|
||||
|
||||
let window = finder._getWindow();
|
||||
let resolver;
|
||||
let promise = new Promise(resolve => resolver = resolve);
|
||||
let iterParams = { caseSensitive, entireWord, linksOnly, useCache, word };
|
||||
|
||||
this._listeners.set(onRange, { limit, onEnd: resolver });
|
||||
this._listeners.set(listener, { limit, onEnd: resolver });
|
||||
|
||||
// If we're not running anymore and we're requesting the previous result, use it.
|
||||
if (!this.running && this._previousResultAvailable(iterParams)) {
|
||||
this._yieldPreviousResult(onRange, window);
|
||||
this._yieldPreviousResult(listener, window);
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
@ -110,7 +121,7 @@ this.FinderIterator = {
|
|||
throw new Error(`We're currently iterating over '${this._currentParams.word}', not '${word}'`);
|
||||
|
||||
// if we're still running, yield the set we have built up this far.
|
||||
this._yieldIntermediateResult(onRange, window);
|
||||
this._yieldIntermediateResult(listener, window);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
@ -149,7 +160,27 @@ this.FinderIterator = {
|
|||
|
||||
for (let [, { onEnd }] of this._listeners)
|
||||
onEnd();
|
||||
this._listeners.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the iteration that currently running, if it is, and start a new one
|
||||
* with the exact same params as before.
|
||||
*
|
||||
* @param {Finder} finder Currently active Finder instance
|
||||
*/
|
||||
restart(finder) {
|
||||
// Capture current iterator params before we stop the show.
|
||||
let iterParams = this.params;
|
||||
if (!iterParams)
|
||||
return;
|
||||
this.stop();
|
||||
|
||||
// Restart manually.
|
||||
this.running = true;
|
||||
this._currentParams = iterParams;
|
||||
|
||||
this._findAllRanges(finder, finder._getWindow(), ++this._spawnId);
|
||||
this._notifyListeners("restart", iterParams);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -165,6 +196,7 @@ this.FinderIterator = {
|
|||
this.ranges = [];
|
||||
this.running = false;
|
||||
|
||||
this._notifyListeners("reset");
|
||||
for (let [, { onEnd }] of this._listeners)
|
||||
onEnd();
|
||||
this._listeners.clear();
|
||||
|
@ -191,13 +223,33 @@ this.FinderIterator = {
|
|||
this._currentParams.word == word);
|
||||
},
|
||||
|
||||
/**
|
||||
* Safely notify all registered listeners that an event has occurred.
|
||||
*
|
||||
* @param {String} callback Name of the callback to invoke
|
||||
* @param {mixed} [params] Optional argument that will be passed to the
|
||||
* callback
|
||||
* @param {Iterable} [listeners] Set of listeners to notify. Optional, defaults
|
||||
* to `this._listeners.keys()`.
|
||||
*/
|
||||
_notifyListeners(callback, params, listeners = this._listeners.keys()) {
|
||||
callback = "onIterator" + callback.charAt(0).toUpperCase() + callback.substr(1);
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
listener[callback](params);
|
||||
} catch (ex) {
|
||||
Cu.reportError("FinderIterator Error: " + ex);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal; check if an iteration request is available in the previous result
|
||||
* that we cached.
|
||||
*
|
||||
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
|
||||
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
|
||||
* mode
|
||||
* @param {Boolean} options.entireWord Whether to search in entire-word mode
|
||||
* @param {Boolean} options.entireWord Whether to search in entire-word mode
|
||||
* @param {Boolean} options.linksOnly Whether to search for the word to be
|
||||
* present in links only
|
||||
* @param {Boolean} options.useCache Whether the consumer wants to use the
|
||||
|
@ -233,17 +285,20 @@ this.FinderIterator = {
|
|||
* make sure we don't block the host process too long. In the case of a break
|
||||
* like this, we yield `undefined`, instead of a range.
|
||||
*
|
||||
* @param {Function} onRange Callback invoked when a range is found
|
||||
* @param {Object} listener Listener object
|
||||
* @param {Array} rangeSource Set of ranges to iterate over
|
||||
* @param {nsIDOMWindow} window The window object is only really used
|
||||
* for access to `setTimeout`
|
||||
* @param {Boolean} [withPause] Whether to pause after each `kIterationSizeMax`
|
||||
* number of ranges yielded. Optional, defaults
|
||||
* to `true`.
|
||||
* @yield {nsIDOMRange}
|
||||
*/
|
||||
_yieldResult: function* (onRange, rangeSource, window) {
|
||||
_yieldResult: function* (listener, rangeSource, window, withPause = true) {
|
||||
// We keep track of the number of iterations to allow a short pause between
|
||||
// every `kIterationSizeMax` number of iterations.
|
||||
let iterCount = 0;
|
||||
let { limit, onEnd } = this._listeners.get(onRange);
|
||||
let { limit, onEnd } = this._listeners.get(listener);
|
||||
let ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
|
||||
for (let range of ranges) {
|
||||
try {
|
||||
|
@ -256,13 +311,13 @@ this.FinderIterator = {
|
|||
|
||||
// Pass a flag that is `true` when we're returning the result from a
|
||||
// cached previous iteration.
|
||||
onRange(range, !this.running);
|
||||
listener.onIteratorRangeFound(range, !this.running);
|
||||
yield range;
|
||||
|
||||
if (++iterCount >= kIterationSizeMax) {
|
||||
if (withPause && ++iterCount >= kIterationSizeMax) {
|
||||
iterCount = 0;
|
||||
// Make sure to save the current limit for later.
|
||||
this._listeners.set(onRange, { limit, onEnd });
|
||||
this._listeners.set(listener, { limit, onEnd });
|
||||
// Sleep for the rest of this cycle.
|
||||
yield new Promise(resolve => window.setTimeout(resolve, 0));
|
||||
// After a sleep, the set of ranges may have updated.
|
||||
|
@ -271,14 +326,14 @@ this.FinderIterator = {
|
|||
|
||||
if (limit !== -1 && --limit === 0) {
|
||||
// We've reached our limit; no need to do more work.
|
||||
this._listeners.delete(onRange);
|
||||
this._listeners.delete(listener);
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the updated limit globally.
|
||||
this._listeners.set(onRange, { limit, onEnd });
|
||||
this._listeners.set(listener, { limit, onEnd });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -286,20 +341,19 @@ this.FinderIterator = {
|
|||
* mark the listener as 'catching up', meaning it will not receive fresh
|
||||
* results from a running iterator.
|
||||
*
|
||||
* @param {Function} onRange Callback invoked when a range is found
|
||||
* @param {nsIDOMWindow} window The window object is only really used
|
||||
* for access to `setTimeout`
|
||||
* @param {Object} listener Listener object
|
||||
* @param {nsIDOMWindow} window The window object is only really used
|
||||
* for access to `setTimeout`
|
||||
* @yield {nsIDOMRange}
|
||||
*/
|
||||
_yieldPreviousResult: Task.async(function* (onRange, window) {
|
||||
this._catchingUp.add(onRange);
|
||||
yield* this._yieldResult(onRange, this._previousRanges, window);
|
||||
this._catchingUp.delete(onRange);
|
||||
let { onEnd } = this._listeners.get(onRange);
|
||||
if (onEnd) {
|
||||
_yieldPreviousResult: Task.async(function* (listener, window) {
|
||||
this._notifyListeners("start", this.params, [listener]);
|
||||
this._catchingUp.add(listener);
|
||||
yield* this._yieldResult(listener, this._previousRanges, window);
|
||||
this._catchingUp.delete(listener);
|
||||
let { onEnd } = this._listeners.get(listener);
|
||||
if (onEnd)
|
||||
onEnd();
|
||||
this._listeners.delete(onRange);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -307,15 +361,16 @@ this.FinderIterator = {
|
|||
* mark the listener as 'catching up', meaning it will not receive fresh
|
||||
* results from the running iterator.
|
||||
*
|
||||
* @param {Function} onRange Callback invoked when a range is found
|
||||
* @param {nsIDOMWindow} window The window object is only really used
|
||||
* for access to `setTimeout`
|
||||
* @param {Object} listener Listener object
|
||||
* @param {nsIDOMWindow} window The window object is only really used
|
||||
* for access to `setTimeout`
|
||||
* @yield {nsIDOMRange}
|
||||
*/
|
||||
_yieldIntermediateResult: Task.async(function* (onRange, window) {
|
||||
this._catchingUp.add(onRange);
|
||||
yield* this._yieldResult(onRange, this.ranges, window);
|
||||
this._catchingUp.delete(onRange);
|
||||
_yieldIntermediateResult: Task.async(function* (listener, window) {
|
||||
this._notifyListeners("start", this.params, [listener]);
|
||||
this._catchingUp.add(listener);
|
||||
yield* this._yieldResult(listener, this.ranges, window, false);
|
||||
this._catchingUp.delete(listener);
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -329,6 +384,8 @@ this.FinderIterator = {
|
|||
* @yield {nsIDOMRange}
|
||||
*/
|
||||
_findAllRanges: Task.async(function* (finder, window, spawnId) {
|
||||
this._notifyListeners("start", this.params);
|
||||
|
||||
// First we collect all frames we need to search through, whilst making sure
|
||||
// that the parent window gets dibs.
|
||||
let frames = [window].concat(this._collectFrames(window, finder));
|
||||
|
@ -348,21 +405,21 @@ this.FinderIterator = {
|
|||
this.ranges.push(range);
|
||||
|
||||
// Call each listener with the range we just found.
|
||||
for (let [onRange, { limit, onEnd }] of this._listeners) {
|
||||
if (this._catchingUp.has(onRange))
|
||||
for (let [listener, { limit, onEnd }] of this._listeners) {
|
||||
if (this._catchingUp.has(listener))
|
||||
continue;
|
||||
|
||||
onRange(range);
|
||||
listener.onIteratorRangeFound(range);
|
||||
|
||||
if (limit !== -1 && --limit === 0) {
|
||||
// We've reached our limit; no need to do more work for this listener.
|
||||
this._listeners.delete(onRange);
|
||||
this._listeners.delete(listener);
|
||||
onEnd();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the updated limit globally.
|
||||
this._listeners.set(onRange, { limit, onEnd });
|
||||
this._listeners.set(listener, { limit, onEnd });
|
||||
}
|
||||
|
||||
yield range;
|
||||
|
|
|
@ -227,6 +227,7 @@ function RemoteFinderListener(global) {
|
|||
for (let msg of this.MESSAGES) {
|
||||
global.addMessageListener(msg, this);
|
||||
}
|
||||
global.addEventListener("unload", this.destroy.bind(this));
|
||||
}
|
||||
|
||||
RemoteFinderListener.prototype = {
|
||||
|
@ -250,6 +251,13 @@ RemoteFinderListener.prototype = {
|
|||
"Finder:ModalHighlightChange"
|
||||
],
|
||||
|
||||
destroy() {
|
||||
this._finder.destroy();
|
||||
for (let msg of this.MESSAGES)
|
||||
this._global.removeMessageListener(msg, this);
|
||||
this._finder = this._global = null;
|
||||
},
|
||||
|
||||
onFindResult: function (aData) {
|
||||
this._global.sendAsyncMessage("Finder:Result", aData);
|
||||
},
|
||||
|
|
|
@ -145,17 +145,17 @@ add_task(function* testModalResults() {
|
|||
}],
|
||||
["ro", {
|
||||
rectCount: 41,
|
||||
insertCalls: [1, 2],
|
||||
removeCalls: [1, 2]
|
||||
insertCalls: [1, 4],
|
||||
removeCalls: [1, 3]
|
||||
}],
|
||||
["new", {
|
||||
rectCount: 2,
|
||||
insertCalls: [1, 2],
|
||||
removeCalls: [1, 2]
|
||||
insertCalls: [1, 4],
|
||||
removeCalls: [1, 3]
|
||||
}],
|
||||
["o", {
|
||||
rectCount: 492,
|
||||
insertCalls: [1, 2],
|
||||
insertCalls: [1, 3],
|
||||
removeCalls: [1, 2]
|
||||
}]
|
||||
]);
|
||||
|
@ -293,7 +293,7 @@ add_task(function* testHighlightAllToggle() {
|
|||
// to flip the pref.
|
||||
expectedResult = {
|
||||
rectCount: 0,
|
||||
insertCalls: [1, 1],
|
||||
insertCalls: [0, 1],
|
||||
removeCalls: [1, 1]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
|
|
|
@ -45,9 +45,11 @@ add_task(function* test_start() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => {
|
||||
++count;
|
||||
Assert.equal(range.toString(), findText, "Text content should match");
|
||||
listener: {
|
||||
onIteratorRangeFound(range) {
|
||||
++count;
|
||||
Assert.equal(range.toString(), findText, "Text content should match");
|
||||
}
|
||||
},
|
||||
word: findText
|
||||
});
|
||||
|
@ -55,6 +57,8 @@ add_task(function* test_start() {
|
|||
Assert.equal(rangeCount, count, "Amount of ranges yielded should match!");
|
||||
Assert.ok(!FinderIterator.running, "Running state should match");
|
||||
Assert.equal(FinderIterator._previousRanges.length, rangeCount, "Ranges cache should match");
|
||||
|
||||
FinderIterator.reset();
|
||||
});
|
||||
|
||||
add_task(function* test_valid_arguments() {
|
||||
|
@ -68,7 +72,7 @@ add_task(function* test_valid_arguments() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
});
|
||||
|
||||
|
@ -80,14 +84,14 @@ add_task(function* test_valid_arguments() {
|
|||
count = 0;
|
||||
Assert.throws(() => FinderIterator.start({
|
||||
entireWord: false,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
}), /Missing required option 'caseSensitive'/, "Should throw when missing an argument");
|
||||
FinderIterator.reset();
|
||||
|
||||
Assert.throws(() => FinderIterator.start({
|
||||
caseSensitive: false,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
}), /Missing required option 'entireWord'/, "Should throw when missing an argument");
|
||||
FinderIterator.reset();
|
||||
|
@ -95,7 +99,7 @@ add_task(function* test_valid_arguments() {
|
|||
Assert.throws(() => FinderIterator.start({
|
||||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
}), /Missing required option 'finder'/, "Should throw when missing an argument");
|
||||
FinderIterator.reset();
|
||||
|
@ -105,14 +109,14 @@ add_task(function* test_valid_arguments() {
|
|||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
word: findText
|
||||
}), /Missing valid, required option 'onRange'/, "Should throw when missing an argument");
|
||||
}), /Missing valid, required option 'listener'/, "Should throw when missing an argument");
|
||||
FinderIterator.reset();
|
||||
|
||||
Assert.throws(() => FinderIterator.start({
|
||||
caseSensitive: false,
|
||||
entireWord: true,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
}), /Missing required option 'word'/, "Should throw when missing an argument");
|
||||
FinderIterator.reset();
|
||||
|
||||
|
@ -129,7 +133,7 @@ add_task(function* test_stop() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
});
|
||||
|
||||
|
@ -138,6 +142,8 @@ add_task(function* test_stop() {
|
|||
yield whenDone;
|
||||
|
||||
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
|
||||
|
||||
FinderIterator.reset();
|
||||
});
|
||||
|
||||
add_task(function* test_reset() {
|
||||
|
@ -150,7 +156,7 @@ add_task(function* test_reset() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
});
|
||||
|
||||
|
@ -181,12 +187,12 @@ add_task(function* test_parallel_starts() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count,
|
||||
listener: { onIteratorRangeFound(range) { ++count; } },
|
||||
word: findText
|
||||
});
|
||||
|
||||
// Start again after a few milliseconds.
|
||||
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 2));
|
||||
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 20));
|
||||
Assert.ok(FinderIterator.running, "We ought to be running here");
|
||||
|
||||
let count2 = 0;
|
||||
|
@ -194,7 +200,7 @@ add_task(function* test_parallel_starts() {
|
|||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
finder: gMockFinder,
|
||||
onRange: range => ++count2,
|
||||
listener: { onIteratorRangeFound(range) { ++count2; } },
|
||||
word: findText
|
||||
});
|
||||
|
||||
|
@ -212,5 +218,5 @@ add_task(function* test_parallel_starts() {
|
|||
Assert.greater(count2, FinderIterator.kIterationSizeMax, "At least one range should've been found");
|
||||
Assert.less(count2, rangeCount, "Not all ranges should've been found");
|
||||
|
||||
Assert.less(count2, count, "The second start was later, so should have fewer results");
|
||||
Assert.equal(count2, count, "The second start was later, but should have caught up");
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче