This commit is contained in:
Ryan VanderMeulen 2016-08-26 09:26:49 -04:00
Родитель 9c0325a862 df364f3f56
Коммит aec08e3182
28 изменённых файлов: 831 добавлений и 351 удалений

Просмотреть файл

@ -538,20 +538,6 @@ Sanitizer.prototype = {
seenException = ex;
}
try {
// Clear "Never remember passwords for this site", which is not handled by
// the permission manager
// (Note the login manager doesn't support date ranges yet, and bug
// 1058438 is calling for loginSaving stuff to end up in the
// permission manager)
let hosts = Services.logins.getAllDisabledHosts();
for (let host of hosts) {
Services.logins.setLoginSavingEnabled(host, true);
}
} catch (ex) {
seenException = ex;
}
try {
// Clear site security settings - no support for ranges in this
// interface either, so we clearAll().

Просмотреть файл

@ -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

Просмотреть файл

@ -17,7 +17,7 @@
<form id="options-panel">
<div id="tools-box" class="options-vertical-pane">
<fieldset id="default-tools-box" class="options-groupbox">
<legend>&options.selectDefaultTools.label;</legend>
<legend>&options.selectDefaultTools.label2;</legend>
</fieldset>
<fieldset id="additional-tools-box" class="options-groupbox">
@ -171,7 +171,7 @@
data-pref="devtools.chrome.enabled"/>
<span>&options.enableChrome.label5;</span>
</label>
<label title="&options.enableRemote.tooltip;">
<label title="&options.enableRemote.tooltip2;">
<input type="checkbox"
data-pref="devtools.debugger.remote-enabled"/>
<span>&options.enableRemote.label3;</span>

Просмотреть файл

@ -240,16 +240,17 @@ InspectorPanel.prototype = {
// All the components are initialized. Let's select a node.
this.selection.setNodeFront(defaultSelection, "inspector-open");
this.markup.expandNode(this.selection.nodeFront);
// And setup the toolbar only now because it may depend on the document.
this.setupToolbar();
this.emit("ready");
deferred.resolve(this);
});
this.setupSearchBox();
this.setupSidebar();
this.setupToolbar();
return deferred.promise;
},
@ -509,6 +510,8 @@ InspectorPanel.prototype = {
},
setupToolbar: function () {
this.teardownToolbar();
// Setup the sidebar toggle button.
let SidebarToggle = this.React.createFactory(this.browserRequire(
"devtools/client/shared/components/sidebar-toggle"));
@ -528,21 +531,28 @@ InspectorPanel.prototype = {
this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
this.addNodeButton.addEventListener("click", this.addNode);
// Setup the eye-dropper icon.
this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
if (!value) {
return;
}
// Setup the eye-dropper icon if we're in an HTML document and we have actor support.
if (this.selection.nodeFront && this.selection.nodeFront.isInHTMLDocument) {
this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
if (!value) {
return;
}
this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
this.eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
this.eyeDropperButton.style.display = "initial";
this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
}, e => console.error(e));
this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
this.eyeDropperButton = this.panelDoc
.getElementById("inspector-eyedropper-toggle");
this.eyeDropperButton.style.display = "initial";
this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
}, e => console.error(e));
} else {
this.panelDoc.getElementById("inspector-eyedropper-toggle").style.display = "none";
}
},
teardownToolbar: function () {
this._sidebarToggle = null;
if (this.addNodeButton) {
this.addNodeButton.removeEventListener("click", this.addNode);
this.addNodeButton = null;
@ -580,6 +590,9 @@ InspectorPanel.prototype = {
this.markup.expandNode(this.selection.nodeFront);
this.emit("new-root");
});
// Setup the toolbar again, since its content may depend on the current document.
this.setupToolbar();
};
this._pendingSelection = onNodeSelected;
this._getDefaultNodeForSelection()
@ -1301,6 +1314,12 @@ InspectorPanel.prototype = {
* @return {Promise} resolves when the eyedropper is visible.
*/
showEyeDropper: function () {
// The eyedropper button doesn't exist, most probably because the actor doesn't
// support the pickColorFromPage, or because the page isn't HTML.
if (!this.eyeDropperButton) {
return null;
}
this.telemetry.toolOpened("toolbareyedropper");
this.eyeDropperButton.setAttribute("checked", "true");
this.startEyeDropperListeners();
@ -1313,6 +1332,12 @@ InspectorPanel.prototype = {
* @return {Promise} resolves when the eyedropper is hidden.
*/
hideEyeDropper: function () {
// The eyedropper button doesn't exist, most probably because the actor doesn't
// support the pickColorFromPage, or because the page isn't HTML.
if (!this.eyeDropperButton) {
return null;
}
this.eyeDropperButton.removeAttribute("checked");
this.stopEyeDropperListeners();
return this.inspector.cancelPickColorFromPage()

Просмотреть файл

@ -75,6 +75,7 @@ subsuite = clipboard
[browser_inspector_highlighter-eyedropper-events.js]
[browser_inspector_highlighter-eyedropper-label.js]
[browser_inspector_highlighter-eyedropper-show-hide.js]
[browser_inspector_highlighter-eyedropper-xul.js]
[browser_inspector_highlighter-geometry_01.js]
[browser_inspector_highlighter-geometry_02.js]
[browser_inspector_highlighter-geometry_03.js]

Просмотреть файл

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the eyedropper icons in the toolbar and in the color picker aren't displayed
// when the page isn't an HTML one.
const TEST_URL = URL_ROOT + "doc_inspector_highlighter_xbl.xul";
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
info("Check the inspector toolbar");
let button = inspector.panelDoc.querySelector("#inspector-eyedropper-toggle");
ok(!isVisible(button), "The button is hidden in the toolbar");
info("Check the color picker");
yield selectNode("#scale", inspector);
// Find the color swatch in the rule-view.
let ruleView = inspector.ruleview.view;
let ruleViewDocument = ruleView.styleDocument;
let swatchEl = ruleViewDocument.querySelector(".ruleview-colorswatch");
info("Open the color picker");
let cPicker = ruleView.tooltips.colorPicker;
let onColorPickerReady = cPicker.once("ready");
swatchEl.click();
yield onColorPickerReady;
button = cPicker.tooltip.doc.querySelector("#eyedropper-button");
ok(!isVisible(button), "The button is hidden in the color picker");
});
function isVisible(button) {
return button.getBoxQuads().length !== 0;
}

Просмотреть файл

@ -4,6 +4,6 @@
<window title="Test that the picker works correctly with XBL anonymous nodes"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<scale id="scale"/>
<scale id="scale" style="background:red"/>
</window>

Просмотреть файл

@ -93,7 +93,7 @@
- checkbox that toggles remote debugging, i.e. devtools.debugger.remote-enabled
- boolean preference in about:config, in the options panel. -->
<!ENTITY options.enableRemote.label3 "Enable remote debugging">
<!ENTITY options.enableRemote.tooltip "Turning this option on will allow the developer tools to debug remote Firefox instance like Firefox OS">
<!ENTITY options.enableRemote.tooltip2 "Turning this option on will allow the developer tools to debug a remote instance like Firefox OS">
<!-- LOCALIZATION NOTE (options.enableWorkers.label): This is the label for the
- checkbox that toggles worker debugging, i.e. devtools.debugger.workers
@ -119,10 +119,10 @@
<!ENTITY options.enableServiceWorkersHTTP.label "Enable Service Workers over HTTP (when toolbox is open)">
<!ENTITY options.enableServiceWorkersHTTP.tooltip "Turning this option on will enable the service workers over HTTP for all tabs that have the toolbox open.">
<!-- LOCALIZATION NOTE (options.selectDefaultTools.label): This is the label for
<!-- LOCALIZATION NOTE (options.selectDefaultTools.label2): This is the label for
- the heading of group of checkboxes corresponding to the default developer
- tools. -->
<!ENTITY options.selectDefaultTools.label "Default Firefox Developer Tools">
<!ENTITY options.selectDefaultTools.label2 "Default Developer Tools">
<!-- LOCALIZATION NOTE (options.selectAdditionalTools.label): This is the label for
- the heading of group of checkboxes corresponding to the developer tools

Просмотреть файл

@ -745,7 +745,7 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
let tooltipDoc = this.tooltip.doc;
let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
if (value) {
if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
eyeButton.addEventListener("click", this._openEyeDropper);
} else {
eyeButton.style.display = "none";

Просмотреть файл

@ -580,6 +580,10 @@ HighlighterEnvironment.prototype = {
return this._win || this._tabActor;
},
get isXUL() {
return isXUL(this.window);
},
get window() {
if (!this.isInitialized) {
throw new Error("Initialize HighlighterEnvironment with a tabActor " +

Просмотреть файл

@ -126,6 +126,10 @@ EyeDropper.prototype = {
* - {Boolean} copyOnSelect Whether selecting a color should copy it to the clipboard.
*/
show(node, options = {}) {
if (this.highlighterEnv.isXUL) {
return false;
}
this.options = options;
// Get the page's current zoom level.
@ -167,6 +171,10 @@ EyeDropper.prototype = {
* Hide the eye-dropper highlighter.
*/
hide() {
if (this.highlighterEnv.isXUL) {
return;
}
this.pageImage = null;
let {pageListenerTarget} = this.highlighterEnv;

Просмотреть файл

@ -95,13 +95,6 @@ Sanitizer.prototype = {
.getService(Ci.nsIContentPrefService2)
.removeAllDomains(null);
// Clear "Never remember passwords for this site", which is not handled by
// the permission manager
var hosts = Services.logins.getAllDisabledHosts({})
for (var host of hosts) {
Services.logins.setLoginSavingEnabled(host, true);
}
// Clear site security settings
var sss = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);

Просмотреть файл

@ -871,11 +871,11 @@ pref("accessibility.typeaheadfind.prefillwithselection", false);
#else
pref("accessibility.typeaheadfind.prefillwithselection", true);
#endif
pref("accessibility.typeaheadfind.matchesCountTimeout", 100);
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.modalHighlight", false);
pref("findbar.entireword", false);
pref("findbar.iteratorTimeout", 100);
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
pref("gfx.use_text_smoothing_setting", false);

Просмотреть файл

@ -47,7 +47,7 @@ skip-if = toolkit == 'android' # autocomplete
[test_maxlength.html]
[test_passwords_in_type_password.html]
[test_prompt.html]
skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
[test_prompt_promptAuth.html]
skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
[test_recipe_login_fields.html]

Просмотреть файл

@ -26,16 +26,26 @@ var isOk;
// Force parent to not look for tab-modal prompts, as they're not used for auth prompts.
isTabModal = false;
const Cc_promptFac= Cc["@mozilla.org/passwordmanager/authpromptfactory;1"];
ok(Cc_promptFac != null, "Access Cc[@mozilla.org/passwordmanager/authpromptfactory;1]");
let prompterParent = runInParent(() => {
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const promptFac = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].
getService(Ci.nsIPromptFactory);
const Ci_promptFac = Ci.nsIPromptFactory;
ok(Ci_promptFac != null, "Access Ci.nsIPromptFactory");
Cu.import("resource://gre/modules/Services.jsm");
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let prompter1 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt);
const promptFac = Cc_promptFac.getService(Ci_promptFac);
ok(promptFac != null, "promptFac getService()");
addMessageListener("proxyPrompter", function onMessage(msg) {
let rv = prompter1[msg.methodName](...msg.args);
return {
rv,
// Send the args back to content so out/inout args can be checked.
args: msg.args,
};
});
});
var prompter1 = promptFac.getPrompt(window, Ci.nsIAuthPrompt);
let prompter1 = new PrompterProxy(prompterParent);
const defaultTitle = "the title";
const defaultMsg = "the message";
@ -84,7 +94,7 @@ add_task(function* setup() {
runInParent(initLogins);
});
add_task(function* test_1() {
add_task(function* test_prompt_accept() {
state = {
msg : "the message",
title : "the title",
@ -113,7 +123,7 @@ add_task(function* test_1() {
is(result.value, "xyz", "Checking prompt() returned value");
});
add_task(function* test_2() {
add_task(function* test_prompt_cancel() {
state = {
msg : "the message",
title : "the title",
@ -139,7 +149,7 @@ add_task(function* test_2() {
ok(!isOk, "Checking dialog return value (cancel)");
});
add_task(function* test_10() {
add_task(function* test_promptPassword_defaultAccept() {
// Default password provided, existing logins are ignored.
state = {
msg : "the message",
@ -169,7 +179,7 @@ add_task(function* test_10() {
is(pword.value, "secret", "Checking returned password");
});
add_task(function* test_11() {
add_task(function* test_promptPassword_defaultCancel() {
// Default password provided, existing logins are ignored.
state = {
msg : "the message",
@ -197,7 +207,7 @@ add_task(function* test_11() {
ok(!isOk, "Checking dialog return value (cancel)");
});
add_task(function* test_12() {
add_task(function* test_promptPassword_emptyAccept() {
// No default password provided, realm does not match existing login.
state = {
msg : "the message",
@ -227,7 +237,7 @@ add_task(function* test_12() {
is(pword.value, "secret", "Checking returned password");
});
add_task(function* test_13() {
add_task(function* test_promptPassword_saved() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com",
@ -236,7 +246,7 @@ add_task(function* test_13() {
is(pword.value, "examplepass", "Checking returned password");
});
add_task(function* test_14() {
add_task(function* test_promptPassword_noMatchingPasswordForEmptyUN() {
// No default password provided, none of the logins from this host are
// password-only so the user is prompted.
state = {
@ -267,7 +277,7 @@ add_task(function* test_14() {
is(pword.value, "secret", "Checking returned password");
});
add_task(function* test_15() {
add_task(function* test_promptPassword_matchingPWForUN() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user1name@example2.com",
@ -276,7 +286,7 @@ add_task(function* test_15() {
is(pword.value, "user1pass", "Checking returned password");
});
add_task(function* test_16() {
add_task(function* test_promptPassword_matchingPWForUN2() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user2name@example2.com",
@ -285,7 +295,7 @@ add_task(function* test_16() {
is(pword.value, "user2pass", "Checking returned password");
});
add_task(function* test_17() {
add_task(function* test_promptPassword_matchingPWForUN3() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user3%2Ename%40host@example2.com",
@ -294,7 +304,7 @@ add_task(function* test_17() {
is(pword.value, "user3pass", "Checking returned password");
});
add_task(function* test_18() {
add_task(function* test_promptPassword_extraAt() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100@beef@example2.com",
@ -303,7 +313,7 @@ add_task(function* test_18() {
is(pword.value, "user3pass", "Checking returned password");
});
add_task(function* test_19() {
add_task(function* test_promptPassword_usernameEncoding() {
// No default password provided, matching login is returned w/o prompting.
pword.value = null;
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100%25beef@example2.com",
@ -314,7 +324,7 @@ add_task(function* test_19() {
// XXX test saving a password with Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY
});
add_task(function* test_30() {
add_task(function* test_promptPassword_realm() {
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
state = {
msg : "the message",
@ -344,7 +354,7 @@ add_task(function* test_30() {
is(pword.value, "fill2pass", "Checking returned password");
});
add_task(function* test_31() {
add_task(function* test_promptPassword_realm2() {
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
state = {
msg : "the message",
@ -374,7 +384,7 @@ add_task(function* test_31() {
is(pword.value, "fill2pass", "Checking returned password");
});
add_task(function* test_100() {
add_task(function* test_promptUsernameAndPassword_accept() {
state = {
msg : "the message",
title : "the title",
@ -399,14 +409,14 @@ add_task(function* test_100() {
pword.value = "inpass";
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "outuser", "Checking returned username");
is(pword.value, "outpass", "Checking returned password");
});
add_task(function* test_101() {
add_task(function* test_promptUsernameAndPassword_cancel() {
state = {
msg : "the message",
title : "the title",
@ -429,12 +439,12 @@ add_task(function* test_101() {
pword.value = "inpass";
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
yield promptDone;
ok(!isOk, "Checking dialog return value (cancel)");
});
add_task(function* test_102() {
add_task(function* test_promptUsernameAndPassword_autofill() {
// test filling in existing password-only login
state = {
msg : "the message",
@ -458,14 +468,14 @@ add_task(function* test_102() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "", "Checking returned username");
is(pword.value, "examplepass", "Checking returned password");
});
add_task(function* test_103() {
add_task(function* test_promptUsernameAndPassword_multipleExisting() {
// test filling in existing login (undetermined from multiple selection)
// user2name/user2pass would also be valid to fill here.
state = {
@ -490,14 +500,14 @@ add_task(function* test_103() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
ok(uname.value == "user1name" || uname.value == "user2name", "Checking returned username");
ok(pword.value == "user1pass" || uname.value == "user2pass", "Checking returned password");
});
add_task(function* test_104() {
add_task(function* test_promptUsernameAndPassword_multipleExisting1() {
// test filling in existing login (user1 from multiple selection)
state = {
msg : "the message",
@ -521,14 +531,14 @@ add_task(function* test_104() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "user1name", "Checking returned username");
is(pword.value, "user1pass", "Checking returned password");
});
add_task(function* test_105() {
add_task(function* test_promptUsernameAndPassword_multipleExisting2() {
// test filling in existing login (user2 from multiple selection)
state = {
msg : "the message",
@ -552,14 +562,14 @@ add_task(function* test_105() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "user2name", "Checking returned username");
is(pword.value, "user2pass", "Checking returned password");
});
add_task(function* test_106() {
add_task(function* test_promptUsernameAndPassword_passwordChange() {
// test changing password
state = {
msg : "the message",
@ -584,14 +594,14 @@ add_task(function* test_106() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "user2name", "Checking returned username");
is(pword.value, "NEWuser2pass", "Checking returned password");
});
add_task(function* test_107() {
add_task(function* test_promptUsernameAndPassword_changePasswordBack() {
// test changing password (back to original value)
state = {
msg : "the message",
@ -616,14 +626,14 @@ add_task(function* test_107() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "user2name", "Checking returned username");
is(pword.value, "user2pass", "Checking returned password");
});
add_task(function* test_120() {
add_task(function* test_promptUsernameAndPassword_realm() {
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
state = {
msg : "the message",
@ -649,14 +659,14 @@ add_task(function* test_120() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)",
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "fill2user", "Checking returned username");
is(pword.value, "fill2pass", "Checking returned password");
});
add_task(function* test_121() {
add_task(function* test_promptUsernameAndPassword_realm2() {
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
state = {
msg : "the message",
@ -682,7 +692,7 @@ add_task(function* test_121() {
pword.value = null;
promptDone = handlePrompt(state, action);
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)",
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
yield promptDone;
ok(isOk, "Checking dialog return value (accept)");
is(uname.value, "fill2user", "Checking returned username");

Просмотреть файл

@ -93,3 +93,57 @@ function checkEchoedAuthInfo(expectedState, doc) {
is(username, expectedState.user, "Checking for echoed username");
is(password, expectedState.pass, "Checking for echoed password");
}
/**
* Create a Proxy to relay method calls on an nsIAuthPrompt prompter to a chrome script which can
* perform the calls in the parent. Out and inout params will be copied back from the parent to
* content.
*
* @param chromeScript The reference to the chrome script that will listen to `proxyPrompter`
* messages in the parent and call the `methodName` method.
* The return value from the message handler should be an object with properties:
* `rv` - containing the return value of the method call.
* `args` - containing the array of arguments passed to the method since out or inout ones could have
* been modified.
*/
function PrompterProxy(chromeScript) {
return new Proxy({}, {
get(target, prop, receiver) {
return (...args) => {
// Array of indices of out/inout params to copy from the parent back to the caller.
let outParams = [];
switch (prop) {
case "prompt": {
outParams = [/* result */ 5];
break;
}
case "promptPassword": {
outParams = [/* pwd */ 4];
break;
}
case "promptUsernameAndPassword": {
outParams = [/* user */ 4, /* pwd */ 5];
break;
}
default: {
throw new Error("Unknown nsIAuthPrompt method");
break;
}
}
let result = chromeScript.sendSyncMessage("proxyPrompter", {
args,
methodName: prop,
})[0][0];
for (let outParam of outParams) {
// Copy the out or inout param value over the original
args[outParam].value = result.args[outParam].value;
}
return result.rv;
};
},
});
}

Просмотреть файл

@ -22,6 +22,8 @@
const SEARCH_TEXT = "minefield";
let gFindBar = null;
let gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
const kIteratorTimeout = gPrefsvc.getIntPref("findbar.iteratorTimeout") + 20;
let gBrowser;
let sendCtrl = true;
@ -53,8 +55,10 @@
// Turn off highlighting
let highlightButton = gFindBar.getElement("highlight");
if (highlightButton.checked)
if (highlightButton.checked) {
highlightButton.click();
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
}
// Initialise input
info(`setting element value to ${aText}`);
@ -71,6 +75,7 @@
// Perform search and turn on highlighting
gFindBar._find();
highlightButton.click();
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
// Move caret to start of element
info(`focusing element`);
@ -110,8 +115,10 @@
// Initialize the findbar
let matchCase = gFindBar.getElement("find-case-sensitive");
if (matchCase.checked)
if (matchCase.checked) {
matchCase.doCommand();
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
}
// First check match has been correctly highlighted
yield resetForNextTest(elementId);

Просмотреть файл

@ -169,21 +169,31 @@
return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise;
}
function promiseFindResult() {
function promiseFindResult(searchString) {
return new Promise(resolve => {
let data = {};
let listener = {
onFindResult: maybeResolve.bind(null, "find"),
onMatchesCountResult: maybeResolve.bind(null, "matches")
};
onFindResult: res => {
if (searchString && res.searchString != searchString)
return;
function maybeResolve(which, res) {
data[which] = res;
if (!data.find || !data.matches)
return;
gFindBar.browser.finder.removeResultListener(listener);
resolve(data);
}
gFindBar.browser.finder.removeResultListener(listener);
data.find = res;
if (res.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
data.matches = { total: 0, current: 0 };
resolve(data);
return;
}
listener = {
onMatchesCountResult: res => {
gFindBar.browser.finder.removeResultListener(listener);
data.matches = res;
resolve(data);
}
};
gFindBar.browser.finder.addResultListener(listener);
}
};
gFindBar.browser.finder.addResultListener(listener);
});
@ -193,11 +203,12 @@
for (let searchString of Object.getOwnPropertyNames(tests)) {
gFindBar.clear();
let promise = promiseFindResult();
let promise = promiseFindResult(searchString);
yield enterStringIntoFindField(searchString, false);
tests[searchString](yield promise);
let result = yield promise;
tests[searchString](result);
}
}

Просмотреть файл

@ -133,9 +133,11 @@
if (gHasFindClipboard) {
yield testStatusText();
}
yield testFindCountUI();
gFindBar.close();
ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
if (!gBrowser.hasAttribute("remote")) {
yield testFindCountUI();
gFindBar.close();
ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
}
yield openFindbar();
yield testFindAfterCaseChanged();
gFindBar.close();
@ -539,13 +541,12 @@
total: 0
}];
let regex = /([\d]*)\sof\s([\d]*)/;
let timeout = gFindBar._matchesCountTimeoutLength + 20;
function assertMatches(aTest, aMatches) {
is(aMatches[1], String(aTest.current),
"Currently highlighted match should be at " + aTest.current);
`Currently highlighted match should be at ${aTest.current} for '${aTest.text}'`);
is(aMatches[2], String(aTest.total),
"Total amount of matches should be " + aTest.total);
`Total amount of matches should be ${aTest.total} for '${aTest.text}'`);
}
for (let test of tests) {
@ -566,6 +567,7 @@
// test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
let current = (test.current + i - 1) % test.total + 1;
assertMatches({
text: test.text,
current: current,
total: test.total
}, foundMatches.value.match(regex));

Просмотреть файл

@ -374,8 +374,6 @@
prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
this._flashFindBar =
prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
this._matchesCountTimeoutLength =
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountTimeout");
this._matchesCountLimit =
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
@ -456,18 +454,10 @@
clearTimeout(this._quickFindTimeout);
this._quickFindTimeout = null;
}
if (this._highlightTimeout) {
clearTimeout(this._highlightTimeout);
this._highlightTimeout = null;
}
if (this._findResetTimeout) {
clearTimeout(this._findResetTimeout);
this._findResetTimeout = null;
}
if (this._updateMatchesCountTimeout) {
clearTimeout(this._updateMatchesCountTimeout);
this._updateMatchesCountTimeout = null;
}
]]></body>
</method>
@ -512,14 +502,8 @@
if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
return;
if (this._updateMatchesCountTimeout) {
window.clearTimeout(this._updateMatchesCountTimeout);
}
this._updateMatchesCountTimeout =
window.setTimeout(() => {
this.browser.finder.requestMatchesCount(this._findField.value, this._matchesCountLimit,
this._findMode == this.FIND_LINKS);
}, this._matchesCountTimeoutLength);
this.browser.finder.requestMatchesCount(this._findField.value,
this._matchesCountLimit, this._findMode == this.FIND_LINKS);
]]></body>
</method>
@ -585,20 +569,15 @@
]]></body>
</method>
<method name="_setHighlightTimeout">
<method name="_maybeHighlightAll">
<body><![CDATA[
if (this._highlightTimeout)
clearTimeout(this._highlightTimeout);
let word = this._findField.value;
// Bug 429723. Don't attempt to highlight ""
if (!this._highlightAll || !word)
return;
this._highlightTimeout = setTimeout(() => {
this.browser.finder.highlight(true, word,
this._findMode == this.FIND_LINKS);
}, 500);
this.browser.finder.highlight(true, word,
this._findMode == this.FIND_LINKS);
]]></body>
</method>
@ -649,8 +628,7 @@
this._updateCaseSensitivity();
this._findFailedString = null;
this._find();
if (this.getElement("highlight").checked)
this._setHighlightTimeout();
this._maybeHighlightAll();
this._dispatchFindEvent("casesensitivitychange");
]]></body>
@ -696,7 +674,7 @@
// Just set the pref; our observer will change the find bar behavior.
prefsvc.setBoolPref("findbar.entireword", aEntireWord);
this._setHighlightTimeout();
this._maybeHighlightAll();
]]></body>
</method>
@ -1042,7 +1020,7 @@
}
this._enableFindButtons(val);
this._setHighlightTimeout();
this._maybeHighlightAll();
this._updateCaseSensitivity(val);
this._updateEntireWord();

Просмотреть файл

@ -225,19 +225,7 @@ Finder.prototype = {
highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) {
let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly);
this.highlighter.notifyFinished(aHighlight);
if (aHighlight) {
let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
: Ci.nsITypeAheadFind.FIND_NOTFOUND;
this._notify({
searchString: aWord,
result,
findBackwards: false,
findAgain: false,
drawOutline: false,
storeResult: false
});
}
this.highlighter.notifyFinished({ highlight: aHighlight, found });
}),
getInitialSelection: function() {
@ -390,8 +378,6 @@ Finder.prototype = {
},
_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)
@ -420,22 +406,27 @@ Finder.prototype = {
this._currentFoundRange = this._fastFind.getFoundRange();
this._currentMatchLimit = aMatchLimit;
this._currentMatchesCountResult = {
total: 0,
current: 0,
_currentFound: false
};
this.iterator.start({
let params = {
caseSensitive: this._fastFind.caseSensitive,
entireWord: this._fastFind.entireWord,
linksOnly: aLinksOnly,
word: aWord
};
if (!this.iterator.continueRunning(params))
this.iterator.stop();
this.iterator.start(Object.assign(params, {
finder: this,
limit: aMatchLimit,
linksOnly: aLinksOnly,
listener: this,
useCache: true,
word: aWord
}).then(this._notifyMatchesCount.bind(this));
})).then(() => {
// Without a valid result, there's nothing to notify about. This happens
// when the iterator was started before and won the race.
if (!this._currentMatchesCountResult || !this._currentMatchesCountResult.total)
return;
this._notifyMatchesCount();
});
},
// FinderIterator listener implementation
@ -460,9 +451,15 @@ Finder.prototype = {
onIteratorRestart({ word, linksOnly }) {
this.requestMatchesCount(word, this._currentMatchLimit, linksOnly);
},
},
onIteratorStart() {},
onIteratorStart() {
this._currentMatchesCountResult = {
total: 0,
current: 0,
_currentFound: false
};
},
_getWindow: function () {
return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);

Просмотреть файл

@ -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 = 10;
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;
@ -219,6 +248,7 @@ FinderHighlighter.prototype = {
if (highlight) {
let params = {
allowDistance: 1,
caseSensitive: this.finder._fastFind.caseSensitive,
entireWord: this.finder._fastFind.entireWord,
linksOnly, word,
@ -226,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);
@ -251,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());
},
/**
@ -301,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);
},
@ -326,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);
@ -343,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;
},
/**
@ -382,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);
@ -402,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) {
@ -411,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) {}
@ -453,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();
},
/**
@ -468,19 +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();
try {
this.finder._getWindow().document
.removeAnonymousContent(this._modalHighlightOutline);
} catch (ex) {}
if (kDebug) {
dict.modalHighlightOutline.remove();
} else {
try {
window.document.removeAnonymousContent(dict.modalHighlightOutline);
} catch (ex) {}
}
this._modalHighlightOutline = null;
dict.modalHighlightOutline = null;
},
/**
@ -520,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) {
@ -543,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;
},
/**
@ -569,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 :'(
@ -655,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.
@ -666,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
@ -698,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);
@ -735,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);
@ -752,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";
@ -759,23 +936,23 @@ FinderHighlighter.prototype = {
// Make sure the dimmed mask node takes the full width and height that's available.
let {width, height} = this._getWindowDimensions(window);
this._lastWindowDimensions = { width, height };
dict.lastWindowDimensions = { width, height };
maskNode.setAttribute("id", kMaskId);
maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
if (this._brightText)
if (dict.brightText)
maskNode.setAttribute("brighttext", "true");
if (paintContent || this._modalHighlightAllMask) {
if (paintContent || dict.modalHighlightAllMask) {
this._updateRangeOutline(dict);
this._updateFixedRangesRects(dict);
// Create a DOM node for each rectangle representing the ranges we found.
let maskContent = [];
const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
if (this._modalHighlightRectsMap) {
for (let [range, rects] of this._modalHighlightRectsMap) {
for (let rect of rects) {
maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
}
for (let [range, rects] of dict.modalHighlightRectsMap) {
for (let rect of rects) {
maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
}
}
maskNode.innerHTML = maskContent.join("");
@ -785,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);
},
@ -796,16 +973,21 @@ FinderHighlighter.prototype = {
* @param {nsIDOMWindow} window
*/
_removeHighlightAllMask(window) {
if (this._modalHighlightAllMask) {
// 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();
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) {
dict.modalHighlightAllMask.remove();
} else {
try {
window.document.removeAnonymousContent(this._modalHighlightAllMask);
window.document.removeAnonymousContent(dict.modalHighlightAllMask);
} catch (ex) {}
this._modalHighlightAllMask = null;
}
dict.modalHighlightAllMask = null;
},
/**
@ -814,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);
},
@ -858,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);
@ -871,7 +1059,7 @@ FinderHighlighter.prototype = {
try {
dwu.loadSheetUsingURIString(uri, dwu.AGENT_SHEET);
} catch (e) {}
this._modalInstalledSheets.set(document, uri);
dict.installedSheet = true;
},
/**
@ -881,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]);
},
/**
@ -899,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;
},
/**

Просмотреть файл

@ -8,9 +8,16 @@ this.EXPORTED_SYMBOLS = ["FinderIterator"];
const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NLP", "resource://gre/modules/NLP.jsm");
const kDebug = false;
const kIterationSizeMax = 100;
const kTimeoutPref = "findbar.iteratorTimeout";
/**
* FinderIterator singleton. See the documentation for the `start()` method to
@ -23,6 +30,8 @@ this.FinderIterator = {
_previousParams: null,
_previousRanges: [],
_spawnId: 0,
_timeout: Services.prefs.getIntPref(kTimeoutPref),
_timer: null,
ranges: [],
running: false,
@ -50,30 +59,35 @@ this.FinderIterator = {
* 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.
*
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
* mode
* @param {Boolean} options.entireWord Whether to search in entire-word mode
* @param {Finder} options.finder Currently active Finder instance
* @param {Number} [options.limit] Limit the amount of results to be
* passed back. Optional, defaults to no
* limit.
* @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
* hyperlink (used by QuickFind).
* Optional, defaults to `false`.
* @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
* @param {Number} [options.allowDistance] Allowed edit distance between the
* current word and `options.word`
* when the iterator is already running
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
* mode
* @param {Boolean} options.entireWord Whether to search in entire-word mode
* @param {Finder} options.finder Currently active Finder instance
* @param {Number} [options.limit] Limit the amount of results to be
* passed back. Optional, defaults to no
* limit.
* @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
* hyperlink (used by QuickFind).
* Optional, defaults to `false`.
* @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, listener, useCache, word }) {
start({ allowDistance, caseSensitive, entireWord, finder, limit, linksOnly, listener, useCache, word }) {
// Take care of default values for non-required options.
if (typeof allowDistance != "number")
allowDistance = 0;
if (typeof limit != "number")
limit = -1;
if (typeof linksOnly != "boolean")
@ -116,9 +130,16 @@ this.FinderIterator = {
if (this.running) {
// Double-check if we're not running the iterator with a different set of
// parameters, otherwise throw an error with the most common reason.
if (!this._areParamsEqual(this._currentParams, iterParams))
throw new Error(`We're currently iterating over '${this._currentParams.word}', not '${word}'`);
// parameters, otherwise report an error with the most common reason.
if (!this._areParamsEqual(this._currentParams, iterParams, allowDistance)) {
if (kDebug) {
Cu.reportError(`We're currently iterating over '${this._currentParams.word}', not '${word}'\n` +
new Error().stack);
}
this._listeners.delete(listener);
resolver();
return promise;
}
// if we're still running, yield the set we have built up this far.
this._yieldIntermediateResult(listener, window);
@ -145,6 +166,11 @@ this.FinderIterator = {
if (!this.running)
return;
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
if (cachePrevious) {
this._previousRanges = [].concat(this.ranges);
this._previousParams = Object.assign({}, this._currentParams);
@ -190,6 +216,11 @@ this.FinderIterator = {
* If the iterator is running, it will be stopped as soon as possible.
*/
reset() {
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
this._catchingUp.clear();
this._currentParams = this._previousParams = null;
this._previousRanges = [];
@ -266,16 +297,19 @@ this.FinderIterator = {
/**
* Internal; compare if two sets of iterator parameters are equivalent.
*
* @param {Object} paramSet1 First set of params (left hand side)
* @param {Object} paramSet2 Second set of params (right hand side)
* @param {Object} paramSet1 First set of params (left hand side)
* @param {Object} paramSet2 Second set of params (right hand side)
* @param {Number} [allowDistance] Allowed edit distance between the two words.
* Optional, defaults to '0', which means 'no
* distance'.
* @return {Boolean}
*/
_areParamsEqual(paramSet1, paramSet2) {
_areParamsEqual(paramSet1, paramSet2, allowDistance = 0) {
return (!!paramSet1 && !!paramSet2 &&
paramSet1.caseSensitive === paramSet2.caseSensitive &&
paramSet1.entireWord === paramSet2.entireWord &&
paramSet1.linksOnly === paramSet2.linksOnly &&
paramSet1.word == paramSet2.word);
NLP.levenshtein(paramSet1.word, paramSet2.word) <= allowDistance);
},
/**
@ -384,6 +418,17 @@ this.FinderIterator = {
* @yield {nsIDOMRange}
*/
_findAllRanges: Task.async(function* (finder, window, spawnId) {
if (this._timeout) {
if (this._timer)
clearTimeout(this._timer);
yield new Promise(resolve => this._timer = setTimeout(resolve, this._timeout));
this._timer = null;
// During the timeout, we could have gotten the signal to stop iterating.
// Make sure we do here.
if (!this.running || spawnId !== this._spawnId)
return;
}
this._notifyListeners("start", this.params);
// First we collect all frames we need to search through, whilst making sure

76
toolkit/modules/NLP.jsm Normal file
Просмотреть файл

@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["NLP"];
/**
* NLP, which stands for Natural Language Processing, is a module that provides
* an entry point to various methods to interface with human language.
*
* At least, that's the goal. Eventually. Right now, the find toolbar only really
* needs the Levenshtein distance algorithm.
*/
this.NLP = {
/**
* Calculate the Levenshtein distance between two words.
* The implementation of this method was heavily inspired by
* http://locutus.io/php/strings/levenshtein/index.html
* License: MIT.
*
* @param {String} word1 Word to compare against
* @param {String} word2 Word that may be different
* @param {Number} costIns The cost to insert a character
* @param {Number} costRep The cost to replace a character
* @param {Number} costDel The cost to delete a character
* @return {Number}
*/
levenshtein(word1 = "", word2 = "", costIns = 1, costRep = 1, costDel = 1) {
if (word1 === word2)
return 0;
let l1 = word1.length;
let l2 = word2.length;
if (!l1)
return l2 * costIns;
if (!l2)
return l1 * costDel;
let p1 = new Array(l2 + 1)
let p2 = new Array(l2 + 1)
let i1, i2, c0, c1, c2, tmp;
for (i2 = 0; i2 <= l2; i2++)
p1[i2] = i2 * costIns;
for (i1 = 0; i1 < l1; i1++) {
p2[0] = p1[0] + costDel;
for (i2 = 0; i2 < l2; i2++) {
c0 = p1[i2] + ((word1[i1] === word2[i2]) ? 0 : costRep);
c1 = p1[i2 + 1] + costDel;
if (c1 < c0)
c0 = c1;
c2 = p2[i2] + costIns;
if (c2 < c0)
c0 = c2;
p2[i2 + 1] = c0;
}
tmp = p1;
p1 = p2;
p2 = tmp;
}
c0 = p1[l2];
return c0;
}
};

Просмотреть файл

@ -53,6 +53,7 @@ EXTRA_JS_MODULES += [
'Locale.jsm',
'Log.jsm',
'NewTabUtils.jsm',
'NLP.jsm',
'ObjectUtils.jsm',
'PageMenu.jsm',
'PageMetadata.jsm',

Просмотреть файл

@ -13,26 +13,29 @@ add_task(function* () {
let finder = tab.linkedBrowser.finder;
let listener = {
onFindResult: function () {
ok(false, "callback wasn't replaced");
ok(false, "onFindResult callback wasn't replaced");
},
onHighlightFinished: function () {
ok(false, "onHighlightFinished callback wasn't replaced");
}
};
finder.addResultListener(listener);
function waitForFind() {
function waitForFind(which = "onFindResult") {
return new Promise(resolve => {
listener.onFindResult = resolve;
listener[which] = resolve;
})
}
let promiseFind = waitForFind();
let promiseFind = waitForFind("onHighlightFinished");
finder.highlight(true, "content");
let findResult = yield promiseFind;
is(findResult.result, Ci.nsITypeAheadFind.FIND_FOUND, "should find string");
Assert.ok(findResult.found, "should find string");
promiseFind = waitForFind();
promiseFind = waitForFind("onHighlightFinished");
finder.highlight(true, "Bla");
findResult = yield promiseFind;
is(findResult.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "should not find string");
Assert.ok(!findResult.found, "should not find string");
// Search only for links and draw outlines.
promiseFind = waitForFind();

Просмотреть файл

@ -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 ]] });

Просмотреть файл

@ -141,7 +141,7 @@ add_task(function* test_stop() {
yield whenDone;
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
Assert.equal(count, 0, "Number of ranges should be 0");
FinderIterator.reset();
});
@ -161,9 +161,8 @@ add_task(function* test_reset() {
});
Assert.ok(FinderIterator.running, "Yup, running we are");
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
Assert.equal(FinderIterator.ranges.length, 100,
"Number of ranges should match `kIterationSizeMax`");
Assert.equal(count, 0, "Number of ranges should match 0");
Assert.equal(FinderIterator.ranges.length, 0, "Number of ranges should match 0");
FinderIterator.reset();
@ -173,7 +172,7 @@ add_task(function* test_reset() {
yield whenDone;
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
Assert.equal(count, 0, "Number of ranges should match 0");
});
add_task(function* test_parallel_starts() {
@ -191,8 +190,7 @@ add_task(function* test_parallel_starts() {
word: findText
});
// Start again after a few milliseconds.
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 20));
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 120));
Assert.ok(FinderIterator.running, "We ought to be running here");
let count2 = 0;
@ -219,4 +217,49 @@ add_task(function* test_parallel_starts() {
Assert.less(count2, rangeCount, "Not all ranges should've been found");
Assert.equal(count2, count, "The second start was later, but should have caught up");
FinderIterator.reset();
});
add_task(function* test_allowDistance() {
let findText = "gup";
let rangeCount = 20;
prepareIterator(findText, rangeCount);
// Start off the iterator.
let count = 0;
let whenDone = FinderIterator.start({
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count; } },
word: findText
});
let count2 = 0;
let whenDone2 = FinderIterator.start({
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count2; } },
word: "gu"
});
let count3 = 0;
let whenDone3 = FinderIterator.start({
allowDistance: 1,
caseSensitive: false,
entireWord: false,
finder: gMockFinder,
listener: { onIteratorRangeFound(range) { ++count3; } },
word: "gu"
});
yield Promise.all([whenDone, whenDone2, whenDone3]);
Assert.equal(count, rangeCount, "The first iterator invocation should yield all results");
Assert.equal(count2, 0, "The second iterator invocation should yield _no_ results");
Assert.equal(count3, rangeCount, "The first iterator invocation should yield all results");
FinderIterator.reset();
});