зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c. a=merge
This commit is contained in:
Коммит
aec08e3182
|
@ -538,20 +538,6 @@ Sanitizer.prototype = {
|
||||||
seenException = ex;
|
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 {
|
try {
|
||||||
// Clear site security settings - no support for ranges in this
|
// Clear site security settings - no support for ranges in this
|
||||||
// interface either, so we clearAll().
|
// interface either, so we clearAll().
|
||||||
|
|
|
@ -484,7 +484,7 @@ skip-if = (os == "win" && !debug)
|
||||||
[browser_windowopen_reflows.js]
|
[browser_windowopen_reflows.js]
|
||||||
skip-if = buildapp == 'mulet'
|
skip-if = buildapp == 'mulet'
|
||||||
[browser_zbug569342.js]
|
[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_registerProtocolHandler_notification.js]
|
||||||
[browser_no_mcb_on_http_site.js]
|
[browser_no_mcb_on_http_site.js]
|
||||||
tags = mcb
|
tags = mcb
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<form id="options-panel">
|
<form id="options-panel">
|
||||||
<div id="tools-box" class="options-vertical-pane">
|
<div id="tools-box" class="options-vertical-pane">
|
||||||
<fieldset id="default-tools-box" class="options-groupbox">
|
<fieldset id="default-tools-box" class="options-groupbox">
|
||||||
<legend>&options.selectDefaultTools.label;</legend>
|
<legend>&options.selectDefaultTools.label2;</legend>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset id="additional-tools-box" class="options-groupbox">
|
<fieldset id="additional-tools-box" class="options-groupbox">
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
data-pref="devtools.chrome.enabled"/>
|
data-pref="devtools.chrome.enabled"/>
|
||||||
<span>&options.enableChrome.label5;</span>
|
<span>&options.enableChrome.label5;</span>
|
||||||
</label>
|
</label>
|
||||||
<label title="&options.enableRemote.tooltip;">
|
<label title="&options.enableRemote.tooltip2;">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
data-pref="devtools.debugger.remote-enabled"/>
|
data-pref="devtools.debugger.remote-enabled"/>
|
||||||
<span>&options.enableRemote.label3;</span>
|
<span>&options.enableRemote.label3;</span>
|
||||||
|
|
|
@ -240,16 +240,17 @@ InspectorPanel.prototype = {
|
||||||
|
|
||||||
// All the components are initialized. Let's select a node.
|
// All the components are initialized. Let's select a node.
|
||||||
this.selection.setNodeFront(defaultSelection, "inspector-open");
|
this.selection.setNodeFront(defaultSelection, "inspector-open");
|
||||||
|
|
||||||
this.markup.expandNode(this.selection.nodeFront);
|
this.markup.expandNode(this.selection.nodeFront);
|
||||||
|
|
||||||
|
// And setup the toolbar only now because it may depend on the document.
|
||||||
|
this.setupToolbar();
|
||||||
|
|
||||||
this.emit("ready");
|
this.emit("ready");
|
||||||
deferred.resolve(this);
|
deferred.resolve(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setupSearchBox();
|
this.setupSearchBox();
|
||||||
this.setupSidebar();
|
this.setupSidebar();
|
||||||
this.setupToolbar();
|
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
@ -509,6 +510,8 @@ InspectorPanel.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
setupToolbar: function () {
|
setupToolbar: function () {
|
||||||
|
this.teardownToolbar();
|
||||||
|
|
||||||
// Setup the sidebar toggle button.
|
// Setup the sidebar toggle button.
|
||||||
let SidebarToggle = this.React.createFactory(this.browserRequire(
|
let SidebarToggle = this.React.createFactory(this.browserRequire(
|
||||||
"devtools/client/shared/components/sidebar-toggle"));
|
"devtools/client/shared/components/sidebar-toggle"));
|
||||||
|
@ -528,21 +531,28 @@ InspectorPanel.prototype = {
|
||||||
this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
|
this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
|
||||||
this.addNodeButton.addEventListener("click", this.addNode);
|
this.addNodeButton.addEventListener("click", this.addNode);
|
||||||
|
|
||||||
// Setup the eye-dropper icon.
|
// Setup the eye-dropper icon if we're in an HTML document and we have actor support.
|
||||||
this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
|
if (this.selection.nodeFront && this.selection.nodeFront.isInHTMLDocument) {
|
||||||
if (!value) {
|
this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
|
||||||
return;
|
if (!value) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
|
this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
|
||||||
this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
|
this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
|
||||||
this.eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
|
this.eyeDropperButton = this.panelDoc
|
||||||
this.eyeDropperButton.style.display = "initial";
|
.getElementById("inspector-eyedropper-toggle");
|
||||||
this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
|
this.eyeDropperButton.style.display = "initial";
|
||||||
}, e => console.error(e));
|
this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
|
||||||
|
}, e => console.error(e));
|
||||||
|
} else {
|
||||||
|
this.panelDoc.getElementById("inspector-eyedropper-toggle").style.display = "none";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
teardownToolbar: function () {
|
teardownToolbar: function () {
|
||||||
|
this._sidebarToggle = null;
|
||||||
|
|
||||||
if (this.addNodeButton) {
|
if (this.addNodeButton) {
|
||||||
this.addNodeButton.removeEventListener("click", this.addNode);
|
this.addNodeButton.removeEventListener("click", this.addNode);
|
||||||
this.addNodeButton = null;
|
this.addNodeButton = null;
|
||||||
|
@ -580,6 +590,9 @@ InspectorPanel.prototype = {
|
||||||
this.markup.expandNode(this.selection.nodeFront);
|
this.markup.expandNode(this.selection.nodeFront);
|
||||||
this.emit("new-root");
|
this.emit("new-root");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup the toolbar again, since its content may depend on the current document.
|
||||||
|
this.setupToolbar();
|
||||||
};
|
};
|
||||||
this._pendingSelection = onNodeSelected;
|
this._pendingSelection = onNodeSelected;
|
||||||
this._getDefaultNodeForSelection()
|
this._getDefaultNodeForSelection()
|
||||||
|
@ -1301,6 +1314,12 @@ InspectorPanel.prototype = {
|
||||||
* @return {Promise} resolves when the eyedropper is visible.
|
* @return {Promise} resolves when the eyedropper is visible.
|
||||||
*/
|
*/
|
||||||
showEyeDropper: function () {
|
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.telemetry.toolOpened("toolbareyedropper");
|
||||||
this.eyeDropperButton.setAttribute("checked", "true");
|
this.eyeDropperButton.setAttribute("checked", "true");
|
||||||
this.startEyeDropperListeners();
|
this.startEyeDropperListeners();
|
||||||
|
@ -1313,6 +1332,12 @@ InspectorPanel.prototype = {
|
||||||
* @return {Promise} resolves when the eyedropper is hidden.
|
* @return {Promise} resolves when the eyedropper is hidden.
|
||||||
*/
|
*/
|
||||||
hideEyeDropper: function () {
|
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.eyeDropperButton.removeAttribute("checked");
|
||||||
this.stopEyeDropperListeners();
|
this.stopEyeDropperListeners();
|
||||||
return this.inspector.cancelPickColorFromPage()
|
return this.inspector.cancelPickColorFromPage()
|
||||||
|
|
|
@ -75,6 +75,7 @@ subsuite = clipboard
|
||||||
[browser_inspector_highlighter-eyedropper-events.js]
|
[browser_inspector_highlighter-eyedropper-events.js]
|
||||||
[browser_inspector_highlighter-eyedropper-label.js]
|
[browser_inspector_highlighter-eyedropper-label.js]
|
||||||
[browser_inspector_highlighter-eyedropper-show-hide.js]
|
[browser_inspector_highlighter-eyedropper-show-hide.js]
|
||||||
|
[browser_inspector_highlighter-eyedropper-xul.js]
|
||||||
[browser_inspector_highlighter-geometry_01.js]
|
[browser_inspector_highlighter-geometry_01.js]
|
||||||
[browser_inspector_highlighter-geometry_02.js]
|
[browser_inspector_highlighter-geometry_02.js]
|
||||||
[browser_inspector_highlighter-geometry_03.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"
|
<window title="Test that the picker works correctly with XBL anonymous nodes"
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
<scale id="scale"/>
|
<scale id="scale" style="background:red"/>
|
||||||
|
|
||||||
</window>
|
</window>
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
- checkbox that toggles remote debugging, i.e. devtools.debugger.remote-enabled
|
- checkbox that toggles remote debugging, i.e. devtools.debugger.remote-enabled
|
||||||
- boolean preference in about:config, in the options panel. -->
|
- boolean preference in about:config, in the options panel. -->
|
||||||
<!ENTITY options.enableRemote.label3 "Enable remote debugging">
|
<!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
|
<!-- LOCALIZATION NOTE (options.enableWorkers.label): This is the label for the
|
||||||
- checkbox that toggles worker debugging, i.e. devtools.debugger.workers
|
- 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.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.">
|
<!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
|
- the heading of group of checkboxes corresponding to the default developer
|
||||||
- tools. -->
|
- 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
|
<!-- LOCALIZATION NOTE (options.selectAdditionalTools.label): This is the label for
|
||||||
- the heading of group of checkboxes corresponding to the developer tools
|
- 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 => {
|
target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
|
||||||
let tooltipDoc = this.tooltip.doc;
|
let tooltipDoc = this.tooltip.doc;
|
||||||
let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
|
let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
|
||||||
if (value) {
|
if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
|
||||||
eyeButton.addEventListener("click", this._openEyeDropper);
|
eyeButton.addEventListener("click", this._openEyeDropper);
|
||||||
} else {
|
} else {
|
||||||
eyeButton.style.display = "none";
|
eyeButton.style.display = "none";
|
||||||
|
|
|
@ -580,6 +580,10 @@ HighlighterEnvironment.prototype = {
|
||||||
return this._win || this._tabActor;
|
return this._win || this._tabActor;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get isXUL() {
|
||||||
|
return isXUL(this.window);
|
||||||
|
},
|
||||||
|
|
||||||
get window() {
|
get window() {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized) {
|
||||||
throw new Error("Initialize HighlighterEnvironment with a tabActor " +
|
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.
|
* - {Boolean} copyOnSelect Whether selecting a color should copy it to the clipboard.
|
||||||
*/
|
*/
|
||||||
show(node, options = {}) {
|
show(node, options = {}) {
|
||||||
|
if (this.highlighterEnv.isXUL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
// Get the page's current zoom level.
|
// Get the page's current zoom level.
|
||||||
|
@ -167,6 +171,10 @@ EyeDropper.prototype = {
|
||||||
* Hide the eye-dropper highlighter.
|
* Hide the eye-dropper highlighter.
|
||||||
*/
|
*/
|
||||||
hide() {
|
hide() {
|
||||||
|
if (this.highlighterEnv.isXUL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.pageImage = null;
|
this.pageImage = null;
|
||||||
|
|
||||||
let {pageListenerTarget} = this.highlighterEnv;
|
let {pageListenerTarget} = this.highlighterEnv;
|
||||||
|
|
|
@ -95,13 +95,6 @@ Sanitizer.prototype = {
|
||||||
.getService(Ci.nsIContentPrefService2)
|
.getService(Ci.nsIContentPrefService2)
|
||||||
.removeAllDomains(null);
|
.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
|
// Clear site security settings
|
||||||
var sss = Cc["@mozilla.org/ssservice;1"]
|
var sss = Cc["@mozilla.org/ssservice;1"]
|
||||||
.getService(Ci.nsISiteSecurityService);
|
.getService(Ci.nsISiteSecurityService);
|
||||||
|
|
|
@ -871,11 +871,11 @@ pref("accessibility.typeaheadfind.prefillwithselection", false);
|
||||||
#else
|
#else
|
||||||
pref("accessibility.typeaheadfind.prefillwithselection", true);
|
pref("accessibility.typeaheadfind.prefillwithselection", true);
|
||||||
#endif
|
#endif
|
||||||
pref("accessibility.typeaheadfind.matchesCountTimeout", 100);
|
|
||||||
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
|
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
|
||||||
pref("findbar.highlightAll", false);
|
pref("findbar.highlightAll", false);
|
||||||
pref("findbar.modalHighlight", false);
|
pref("findbar.modalHighlight", false);
|
||||||
pref("findbar.entireword", false);
|
pref("findbar.entireword", false);
|
||||||
|
pref("findbar.iteratorTimeout", 100);
|
||||||
|
|
||||||
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
|
// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
|
||||||
pref("gfx.use_text_smoothing_setting", false);
|
pref("gfx.use_text_smoothing_setting", false);
|
||||||
|
|
|
@ -47,7 +47,7 @@ skip-if = toolkit == 'android' # autocomplete
|
||||||
[test_maxlength.html]
|
[test_maxlength.html]
|
||||||
[test_passwords_in_type_password.html]
|
[test_passwords_in_type_password.html]
|
||||||
[test_prompt.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]
|
[test_prompt_promptAuth.html]
|
||||||
skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
|
skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
|
||||||
[test_recipe_login_fields.html]
|
[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.
|
// Force parent to not look for tab-modal prompts, as they're not used for auth prompts.
|
||||||
isTabModal = false;
|
isTabModal = false;
|
||||||
|
|
||||||
const Cc_promptFac= Cc["@mozilla.org/passwordmanager/authpromptfactory;1"];
|
let prompterParent = runInParent(() => {
|
||||||
ok(Cc_promptFac != null, "Access Cc[@mozilla.org/passwordmanager/authpromptfactory;1]");
|
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||||
|
const promptFac = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].
|
||||||
|
getService(Ci.nsIPromptFactory);
|
||||||
|
|
||||||
const Ci_promptFac = Ci.nsIPromptFactory;
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
ok(Ci_promptFac != null, "Access Ci.nsIPromptFactory");
|
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
|
let prompter1 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt);
|
||||||
|
|
||||||
const promptFac = Cc_promptFac.getService(Ci_promptFac);
|
addMessageListener("proxyPrompter", function onMessage(msg) {
|
||||||
ok(promptFac != null, "promptFac getService()");
|
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 defaultTitle = "the title";
|
||||||
const defaultMsg = "the message";
|
const defaultMsg = "the message";
|
||||||
|
@ -84,7 +94,7 @@ add_task(function* setup() {
|
||||||
runInParent(initLogins);
|
runInParent(initLogins);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_1() {
|
add_task(function* test_prompt_accept() {
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
title : "the title",
|
title : "the title",
|
||||||
|
@ -113,7 +123,7 @@ add_task(function* test_1() {
|
||||||
is(result.value, "xyz", "Checking prompt() returned value");
|
is(result.value, "xyz", "Checking prompt() returned value");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_2() {
|
add_task(function* test_prompt_cancel() {
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
title : "the title",
|
title : "the title",
|
||||||
|
@ -139,7 +149,7 @@ add_task(function* test_2() {
|
||||||
ok(!isOk, "Checking dialog return value (cancel)");
|
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.
|
// Default password provided, existing logins are ignored.
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -169,7 +179,7 @@ add_task(function* test_10() {
|
||||||
is(pword.value, "secret", "Checking returned password");
|
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.
|
// Default password provided, existing logins are ignored.
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -197,7 +207,7 @@ add_task(function* test_11() {
|
||||||
ok(!isOk, "Checking dialog return value (cancel)");
|
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.
|
// No default password provided, realm does not match existing login.
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -227,7 +237,7 @@ add_task(function* test_12() {
|
||||||
is(pword.value, "secret", "Checking returned password");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com",
|
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com",
|
||||||
|
@ -236,7 +246,7 @@ add_task(function* test_13() {
|
||||||
is(pword.value, "examplepass", "Checking returned password");
|
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
|
// No default password provided, none of the logins from this host are
|
||||||
// password-only so the user is prompted.
|
// password-only so the user is prompted.
|
||||||
state = {
|
state = {
|
||||||
|
@ -267,7 +277,7 @@ add_task(function* test_14() {
|
||||||
is(pword.value, "secret", "Checking returned password");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user1name@example2.com",
|
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");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user2name@example2.com",
|
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");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user3%2Ename%40host@example2.com",
|
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");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100@beef@example2.com",
|
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");
|
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.
|
// No default password provided, matching login is returned w/o prompting.
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100%25beef@example2.com",
|
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
|
// 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
|
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -344,7 +354,7 @@ add_task(function* test_30() {
|
||||||
is(pword.value, "fill2pass", "Checking returned password");
|
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
|
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -374,7 +384,7 @@ add_task(function* test_31() {
|
||||||
is(pword.value, "fill2pass", "Checking returned password");
|
is(pword.value, "fill2pass", "Checking returned password");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_100() {
|
add_task(function* test_promptUsernameAndPassword_accept() {
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
title : "the title",
|
title : "the title",
|
||||||
|
@ -399,14 +409,14 @@ add_task(function* test_100() {
|
||||||
pword.value = "inpass";
|
pword.value = "inpass";
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "outuser", "Checking returned username");
|
is(uname.value, "outuser", "Checking returned username");
|
||||||
is(pword.value, "outpass", "Checking returned password");
|
is(pword.value, "outpass", "Checking returned password");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_101() {
|
add_task(function* test_promptUsernameAndPassword_cancel() {
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
title : "the title",
|
title : "the title",
|
||||||
|
@ -429,12 +439,12 @@ add_task(function* test_101() {
|
||||||
pword.value = "inpass";
|
pword.value = "inpass";
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(!isOk, "Checking dialog return value (cancel)");
|
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
|
// test filling in existing password-only login
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -458,14 +468,14 @@ add_task(function* test_102() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "", "Checking returned username");
|
is(uname.value, "", "Checking returned username");
|
||||||
is(pword.value, "examplepass", "Checking returned password");
|
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)
|
// test filling in existing login (undetermined from multiple selection)
|
||||||
// user2name/user2pass would also be valid to fill here.
|
// user2name/user2pass would also be valid to fill here.
|
||||||
state = {
|
state = {
|
||||||
|
@ -490,14 +500,14 @@ add_task(function* test_103() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
ok(uname.value == "user1name" || uname.value == "user2name", "Checking returned username");
|
ok(uname.value == "user1name" || uname.value == "user2name", "Checking returned username");
|
||||||
ok(pword.value == "user1pass" || uname.value == "user2pass", "Checking returned password");
|
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)
|
// test filling in existing login (user1 from multiple selection)
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -521,14 +531,14 @@ add_task(function* test_104() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "user1name", "Checking returned username");
|
is(uname.value, "user1name", "Checking returned username");
|
||||||
is(pword.value, "user1pass", "Checking returned password");
|
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)
|
// test filling in existing login (user2 from multiple selection)
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -552,14 +562,14 @@ add_task(function* test_105() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "user2name", "Checking returned username");
|
is(uname.value, "user2name", "Checking returned username");
|
||||||
is(pword.value, "user2pass", "Checking returned password");
|
is(pword.value, "user2pass", "Checking returned password");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_106() {
|
add_task(function* test_promptUsernameAndPassword_passwordChange() {
|
||||||
// test changing password
|
// test changing password
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -584,14 +594,14 @@ add_task(function* test_106() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "user2name", "Checking returned username");
|
is(uname.value, "user2name", "Checking returned username");
|
||||||
is(pword.value, "NEWuser2pass", "Checking returned password");
|
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)
|
// test changing password (back to original value)
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -616,14 +626,14 @@ add_task(function* test_107() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com",
|
||||||
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword);
|
||||||
yield promptDone;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "user2name", "Checking returned username");
|
is(uname.value, "user2name", "Checking returned username");
|
||||||
is(pword.value, "user2pass", "Checking returned password");
|
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
|
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -649,14 +659,14 @@ add_task(function* test_120() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)",
|
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;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "fill2user", "Checking returned username");
|
is(uname.value, "fill2user", "Checking returned username");
|
||||||
is(pword.value, "fill2pass", "Checking returned password");
|
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
|
// We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt
|
||||||
state = {
|
state = {
|
||||||
msg : "the message",
|
msg : "the message",
|
||||||
|
@ -682,7 +692,7 @@ add_task(function* test_121() {
|
||||||
pword.value = null;
|
pword.value = null;
|
||||||
promptDone = handlePrompt(state, action);
|
promptDone = handlePrompt(state, action);
|
||||||
isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)",
|
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;
|
yield promptDone;
|
||||||
ok(isOk, "Checking dialog return value (accept)");
|
ok(isOk, "Checking dialog return value (accept)");
|
||||||
is(uname.value, "fill2user", "Checking returned username");
|
is(uname.value, "fill2user", "Checking returned username");
|
||||||
|
|
|
@ -93,3 +93,57 @@ function checkEchoedAuthInfo(expectedState, doc) {
|
||||||
is(username, expectedState.user, "Checking for echoed username");
|
is(username, expectedState.user, "Checking for echoed username");
|
||||||
is(password, expectedState.pass, "Checking for echoed password");
|
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";
|
const SEARCH_TEXT = "minefield";
|
||||||
|
|
||||||
let gFindBar = null;
|
let gFindBar = null;
|
||||||
|
let gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||||
|
const kIteratorTimeout = gPrefsvc.getIntPref("findbar.iteratorTimeout") + 20;
|
||||||
let gBrowser;
|
let gBrowser;
|
||||||
|
|
||||||
let sendCtrl = true;
|
let sendCtrl = true;
|
||||||
|
@ -53,8 +55,10 @@
|
||||||
|
|
||||||
// Turn off highlighting
|
// Turn off highlighting
|
||||||
let highlightButton = gFindBar.getElement("highlight");
|
let highlightButton = gFindBar.getElement("highlight");
|
||||||
if (highlightButton.checked)
|
if (highlightButton.checked) {
|
||||||
highlightButton.click();
|
highlightButton.click();
|
||||||
|
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
// Initialise input
|
// Initialise input
|
||||||
info(`setting element value to ${aText}`);
|
info(`setting element value to ${aText}`);
|
||||||
|
@ -71,6 +75,7 @@
|
||||||
// Perform search and turn on highlighting
|
// Perform search and turn on highlighting
|
||||||
gFindBar._find();
|
gFindBar._find();
|
||||||
highlightButton.click();
|
highlightButton.click();
|
||||||
|
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
|
||||||
|
|
||||||
// Move caret to start of element
|
// Move caret to start of element
|
||||||
info(`focusing element`);
|
info(`focusing element`);
|
||||||
|
@ -110,8 +115,10 @@
|
||||||
|
|
||||||
// Initialize the findbar
|
// Initialize the findbar
|
||||||
let matchCase = gFindBar.getElement("find-case-sensitive");
|
let matchCase = gFindBar.getElement("find-case-sensitive");
|
||||||
if (matchCase.checked)
|
if (matchCase.checked) {
|
||||||
matchCase.doCommand();
|
matchCase.doCommand();
|
||||||
|
yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
// First check match has been correctly highlighted
|
// First check match has been correctly highlighted
|
||||||
yield resetForNextTest(elementId);
|
yield resetForNextTest(elementId);
|
||||||
|
|
|
@ -169,21 +169,31 @@
|
||||||
return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise;
|
return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function promiseFindResult() {
|
function promiseFindResult(searchString) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let data = {};
|
let data = {};
|
||||||
let listener = {
|
let listener = {
|
||||||
onFindResult: maybeResolve.bind(null, "find"),
|
onFindResult: res => {
|
||||||
onMatchesCountResult: maybeResolve.bind(null, "matches")
|
if (searchString && res.searchString != searchString)
|
||||||
};
|
return;
|
||||||
|
|
||||||
function maybeResolve(which, res) {
|
gFindBar.browser.finder.removeResultListener(listener);
|
||||||
data[which] = res;
|
data.find = res;
|
||||||
if (!data.find || !data.matches)
|
if (res.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
||||||
return;
|
data.matches = { total: 0, current: 0 };
|
||||||
gFindBar.browser.finder.removeResultListener(listener);
|
resolve(data);
|
||||||
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);
|
gFindBar.browser.finder.addResultListener(listener);
|
||||||
});
|
});
|
||||||
|
@ -193,11 +203,12 @@
|
||||||
for (let searchString of Object.getOwnPropertyNames(tests)) {
|
for (let searchString of Object.getOwnPropertyNames(tests)) {
|
||||||
gFindBar.clear();
|
gFindBar.clear();
|
||||||
|
|
||||||
let promise = promiseFindResult();
|
let promise = promiseFindResult(searchString);
|
||||||
|
|
||||||
yield enterStringIntoFindField(searchString, false);
|
yield enterStringIntoFindField(searchString, false);
|
||||||
|
|
||||||
tests[searchString](yield promise);
|
let result = yield promise;
|
||||||
|
tests[searchString](result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,9 +133,11 @@
|
||||||
if (gHasFindClipboard) {
|
if (gHasFindClipboard) {
|
||||||
yield testStatusText();
|
yield testStatusText();
|
||||||
}
|
}
|
||||||
yield testFindCountUI();
|
if (!gBrowser.hasAttribute("remote")) {
|
||||||
gFindBar.close();
|
yield testFindCountUI();
|
||||||
ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
|
gFindBar.close();
|
||||||
|
ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
|
||||||
|
}
|
||||||
yield openFindbar();
|
yield openFindbar();
|
||||||
yield testFindAfterCaseChanged();
|
yield testFindAfterCaseChanged();
|
||||||
gFindBar.close();
|
gFindBar.close();
|
||||||
|
@ -539,13 +541,12 @@
|
||||||
total: 0
|
total: 0
|
||||||
}];
|
}];
|
||||||
let regex = /([\d]*)\sof\s([\d]*)/;
|
let regex = /([\d]*)\sof\s([\d]*)/;
|
||||||
let timeout = gFindBar._matchesCountTimeoutLength + 20;
|
|
||||||
|
|
||||||
function assertMatches(aTest, aMatches) {
|
function assertMatches(aTest, aMatches) {
|
||||||
is(aMatches[1], String(aTest.current),
|
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),
|
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) {
|
for (let test of tests) {
|
||||||
|
@ -566,6 +567,7 @@
|
||||||
// test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
|
// test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
|
||||||
let current = (test.current + i - 1) % test.total + 1;
|
let current = (test.current + i - 1) % test.total + 1;
|
||||||
assertMatches({
|
assertMatches({
|
||||||
|
text: test.text,
|
||||||
current: current,
|
current: current,
|
||||||
total: test.total
|
total: test.total
|
||||||
}, foundMatches.value.match(regex));
|
}, foundMatches.value.match(regex));
|
||||||
|
|
|
@ -374,8 +374,6 @@
|
||||||
prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
|
prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
|
||||||
this._flashFindBar =
|
this._flashFindBar =
|
||||||
prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
|
prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
|
||||||
this._matchesCountTimeoutLength =
|
|
||||||
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountTimeout");
|
|
||||||
this._matchesCountLimit =
|
this._matchesCountLimit =
|
||||||
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
|
prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
|
||||||
this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
|
this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
|
||||||
|
@ -456,18 +454,10 @@
|
||||||
clearTimeout(this._quickFindTimeout);
|
clearTimeout(this._quickFindTimeout);
|
||||||
this._quickFindTimeout = null;
|
this._quickFindTimeout = null;
|
||||||
}
|
}
|
||||||
if (this._highlightTimeout) {
|
|
||||||
clearTimeout(this._highlightTimeout);
|
|
||||||
this._highlightTimeout = null;
|
|
||||||
}
|
|
||||||
if (this._findResetTimeout) {
|
if (this._findResetTimeout) {
|
||||||
clearTimeout(this._findResetTimeout);
|
clearTimeout(this._findResetTimeout);
|
||||||
this._findResetTimeout = null;
|
this._findResetTimeout = null;
|
||||||
}
|
}
|
||||||
if (this._updateMatchesCountTimeout) {
|
|
||||||
clearTimeout(this._updateMatchesCountTimeout);
|
|
||||||
this._updateMatchesCountTimeout = null;
|
|
||||||
}
|
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
@ -512,14 +502,8 @@
|
||||||
if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
|
if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this._updateMatchesCountTimeout) {
|
this.browser.finder.requestMatchesCount(this._findField.value,
|
||||||
window.clearTimeout(this._updateMatchesCountTimeout);
|
this._matchesCountLimit, this._findMode == this.FIND_LINKS);
|
||||||
}
|
|
||||||
this._updateMatchesCountTimeout =
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.browser.finder.requestMatchesCount(this._findField.value, this._matchesCountLimit,
|
|
||||||
this._findMode == this.FIND_LINKS);
|
|
||||||
}, this._matchesCountTimeoutLength);
|
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
@ -585,20 +569,15 @@
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="_setHighlightTimeout">
|
<method name="_maybeHighlightAll">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
if (this._highlightTimeout)
|
|
||||||
clearTimeout(this._highlightTimeout);
|
|
||||||
|
|
||||||
let word = this._findField.value;
|
let word = this._findField.value;
|
||||||
// Bug 429723. Don't attempt to highlight ""
|
// Bug 429723. Don't attempt to highlight ""
|
||||||
if (!this._highlightAll || !word)
|
if (!this._highlightAll || !word)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._highlightTimeout = setTimeout(() => {
|
this.browser.finder.highlight(true, word,
|
||||||
this.browser.finder.highlight(true, word,
|
this._findMode == this.FIND_LINKS);
|
||||||
this._findMode == this.FIND_LINKS);
|
|
||||||
}, 500);
|
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
@ -649,8 +628,7 @@
|
||||||
this._updateCaseSensitivity();
|
this._updateCaseSensitivity();
|
||||||
this._findFailedString = null;
|
this._findFailedString = null;
|
||||||
this._find();
|
this._find();
|
||||||
if (this.getElement("highlight").checked)
|
this._maybeHighlightAll();
|
||||||
this._setHighlightTimeout();
|
|
||||||
|
|
||||||
this._dispatchFindEvent("casesensitivitychange");
|
this._dispatchFindEvent("casesensitivitychange");
|
||||||
]]></body>
|
]]></body>
|
||||||
|
@ -696,7 +674,7 @@
|
||||||
// Just set the pref; our observer will change the find bar behavior.
|
// Just set the pref; our observer will change the find bar behavior.
|
||||||
prefsvc.setBoolPref("findbar.entireword", aEntireWord);
|
prefsvc.setBoolPref("findbar.entireword", aEntireWord);
|
||||||
|
|
||||||
this._setHighlightTimeout();
|
this._maybeHighlightAll();
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
@ -1042,7 +1020,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this._enableFindButtons(val);
|
this._enableFindButtons(val);
|
||||||
this._setHighlightTimeout();
|
this._maybeHighlightAll();
|
||||||
|
|
||||||
this._updateCaseSensitivity(val);
|
this._updateCaseSensitivity(val);
|
||||||
this._updateEntireWord();
|
this._updateEntireWord();
|
||||||
|
|
|
@ -225,19 +225,7 @@ Finder.prototype = {
|
||||||
|
|
||||||
highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) {
|
highlight: Task.async(function* (aHighlight, aWord, aLinksOnly) {
|
||||||
let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly);
|
let found = yield this.highlighter.highlight(aHighlight, aWord, null, aLinksOnly);
|
||||||
this.highlighter.notifyFinished(aHighlight);
|
this.highlighter.notifyFinished({ highlight: aHighlight, found });
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getInitialSelection: function() {
|
getInitialSelection: function() {
|
||||||
|
@ -390,8 +378,6 @@ Finder.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_notifyMatchesCount: function(result = this._currentMatchesCountResult) {
|
_notifyMatchesCount: function(result = this._currentMatchesCountResult) {
|
||||||
if (!result)
|
|
||||||
return;
|
|
||||||
// The `_currentFound` property is only used for internal bookkeeping.
|
// The `_currentFound` property is only used for internal bookkeeping.
|
||||||
delete result._currentFound;
|
delete result._currentFound;
|
||||||
if (result.total == this._currentMatchLimit)
|
if (result.total == this._currentMatchLimit)
|
||||||
|
@ -420,22 +406,27 @@ Finder.prototype = {
|
||||||
this._currentFoundRange = this._fastFind.getFoundRange();
|
this._currentFoundRange = this._fastFind.getFoundRange();
|
||||||
this._currentMatchLimit = aMatchLimit;
|
this._currentMatchLimit = aMatchLimit;
|
||||||
|
|
||||||
this._currentMatchesCountResult = {
|
let params = {
|
||||||
total: 0,
|
|
||||||
current: 0,
|
|
||||||
_currentFound: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.iterator.start({
|
|
||||||
caseSensitive: this._fastFind.caseSensitive,
|
caseSensitive: this._fastFind.caseSensitive,
|
||||||
entireWord: this._fastFind.entireWord,
|
entireWord: this._fastFind.entireWord,
|
||||||
|
linksOnly: aLinksOnly,
|
||||||
|
word: aWord
|
||||||
|
};
|
||||||
|
if (!this.iterator.continueRunning(params))
|
||||||
|
this.iterator.stop();
|
||||||
|
|
||||||
|
this.iterator.start(Object.assign(params, {
|
||||||
finder: this,
|
finder: this,
|
||||||
limit: aMatchLimit,
|
limit: aMatchLimit,
|
||||||
linksOnly: aLinksOnly,
|
|
||||||
listener: this,
|
listener: this,
|
||||||
useCache: true,
|
useCache: true,
|
||||||
word: aWord
|
})).then(() => {
|
||||||
}).then(this._notifyMatchesCount.bind(this));
|
// 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
|
// FinderIterator listener implementation
|
||||||
|
@ -460,9 +451,15 @@ Finder.prototype = {
|
||||||
|
|
||||||
onIteratorRestart({ word, linksOnly }) {
|
onIteratorRestart({ word, linksOnly }) {
|
||||||
this.requestMatchesCount(word, this._currentMatchLimit, linksOnly);
|
this.requestMatchesCount(word, this._currentMatchLimit, linksOnly);
|
||||||
},
|
},
|
||||||
|
|
||||||
onIteratorStart() {},
|
onIteratorStart() {
|
||||||
|
this._currentMatchesCountResult = {
|
||||||
|
total: 0,
|
||||||
|
current: 0,
|
||||||
|
_currentFound: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
_getWindow: function () {
|
_getWindow: function () {
|
||||||
return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
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");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
|
XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
|
||||||
XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
|
XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
|
||||||
const kDebugPref = "findbar.modalHighlight.debug";
|
const kDebugPref = "findbar.modalHighlight.debug";
|
||||||
return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
|
return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
|
||||||
});
|
});
|
||||||
|
|
||||||
const kContentChangeThresholdPx = 5;
|
const kContentChangeThresholdPx = 5;
|
||||||
const kModalHighlightRepaintFreqMs = 10;
|
const kModalHighlightRepaintFreqMs = 200;
|
||||||
const kHighlightAllPref = "findbar.highlightAll";
|
const kHighlightAllPref = "findbar.highlightAll";
|
||||||
const kModalHighlightPref = "findbar.modalHighlight";
|
const kModalHighlightPref = "findbar.modalHighlight";
|
||||||
const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
|
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
|
* 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.
|
* '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
|
* @param {Finder} finder Finder.jsm instance
|
||||||
*/
|
*/
|
||||||
function FinderHighlighter(finder) {
|
function FinderHighlighter(finder) {
|
||||||
this._currentFoundRange = null;
|
|
||||||
this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
|
|
||||||
this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
|
this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
|
||||||
this._lastIteratorParams = null;
|
this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
|
||||||
this.finder = finder;
|
this.finder = finder;
|
||||||
this.visible = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FinderHighlighter.prototype = {
|
FinderHighlighter.prototype = {
|
||||||
|
@ -183,6 +183,34 @@ FinderHighlighter.prototype = {
|
||||||
return this._modalStyleSheetURI;
|
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.
|
* Notify all registered listeners that the 'Highlight All' operation finished.
|
||||||
*
|
*
|
||||||
|
@ -207,6 +235,7 @@ FinderHighlighter.prototype = {
|
||||||
*/
|
*/
|
||||||
highlight: Task.async(function* (highlight, word, linksOnly) {
|
highlight: Task.async(function* (highlight, word, linksOnly) {
|
||||||
let window = this.finder._getWindow();
|
let window = this.finder._getWindow();
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
let controller = this.finder._getSelectionController(window);
|
let controller = this.finder._getSelectionController(window);
|
||||||
let doc = window.document;
|
let doc = window.document;
|
||||||
this._found = false;
|
this._found = false;
|
||||||
|
@ -219,6 +248,7 @@ FinderHighlighter.prototype = {
|
||||||
|
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
let params = {
|
let params = {
|
||||||
|
allowDistance: 1,
|
||||||
caseSensitive: this.finder._fastFind.caseSensitive,
|
caseSensitive: this.finder._fastFind.caseSensitive,
|
||||||
entireWord: this.finder._fastFind.entireWord,
|
entireWord: this.finder._fastFind.entireWord,
|
||||||
linksOnly, word,
|
linksOnly, word,
|
||||||
|
@ -226,7 +256,7 @@ FinderHighlighter.prototype = {
|
||||||
listener: this,
|
listener: this,
|
||||||
useCache: true
|
useCache: true
|
||||||
};
|
};
|
||||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
if (this.iterator._areParamsEqual(params, dict.lastIteratorParams))
|
||||||
return this._found;
|
return this._found;
|
||||||
if (params) {
|
if (params) {
|
||||||
yield this.iterator.start(params);
|
yield this.iterator.start(params);
|
||||||
|
@ -251,17 +281,19 @@ FinderHighlighter.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onIteratorReset() {
|
onIteratorReset() {
|
||||||
this.clear();
|
this.clear(this.finder._getWindow());
|
||||||
},
|
},
|
||||||
|
|
||||||
onIteratorRestart() {},
|
onIteratorRestart() {},
|
||||||
|
|
||||||
onIteratorStart(params) {
|
onIteratorStart(params) {
|
||||||
|
let window = this.finder._getWindow();
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
// Save a clean params set for use later in the `update()` method.
|
// Save a clean params set for use later in the `update()` method.
|
||||||
this._lastIteratorParams = params;
|
dict.lastIteratorParams = params;
|
||||||
this.clear();
|
this.clear(window);
|
||||||
if (!this._modal)
|
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.
|
* Optional, defaults to the finder window.
|
||||||
*/
|
*/
|
||||||
show(window = null) {
|
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;
|
return;
|
||||||
|
|
||||||
this.visible = true;
|
dict.visible = true;
|
||||||
window = window || this.finder._getWindow();
|
|
||||||
this._maybeCreateModalHighlightNodes(window);
|
this._maybeCreateModalHighlightNodes(window);
|
||||||
this._addModalHighlightListeners(window);
|
this._addModalHighlightListeners(window);
|
||||||
},
|
},
|
||||||
|
@ -326,14 +360,17 @@ FinderHighlighter.prototype = {
|
||||||
if (event && event.type == "click" && event.button !== 0)
|
if (event && event.type == "click" && event.button !== 0)
|
||||||
return;
|
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);
|
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
|
// Next, check our editor cache, for editors belonging to this
|
||||||
// document
|
// document
|
||||||
if (this._editors) {
|
if (this._editors) {
|
||||||
|
let doc = window.document;
|
||||||
for (let x = this._editors.length - 1; x >= 0; --x) {
|
for (let x = this._editors.length - 1; x >= 0; --x) {
|
||||||
if (this._editors[x].document == doc) {
|
if (this._editors[x].document == doc) {
|
||||||
this._clearSelection(this._editors[x].selectionController, skipRange);
|
this._clearSelection(this._editors[x].selectionController, skipRange);
|
||||||
|
@ -343,20 +380,20 @@ FinderHighlighter.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._modalRepaintScheduler) {
|
if (dict.modalRepaintScheduler) {
|
||||||
window.clearTimeout(this._modalRepaintScheduler);
|
window.clearTimeout(dict.modalRepaintScheduler);
|
||||||
this._modalRepaintScheduler = null;
|
dict.modalRepaintScheduler = null;
|
||||||
}
|
}
|
||||||
this._lastWindowDimensions = null;
|
dict.lastWindowDimensions = null;
|
||||||
|
|
||||||
if (this._modalHighlightOutline)
|
if (dict.modalHighlightOutline)
|
||||||
this._modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
|
dict.modalHighlightOutline.setAttributeForElement(kModalOutlineId, "hidden", "true");
|
||||||
|
|
||||||
this._removeHighlightAllMask(window);
|
this._removeHighlightAllMask(window);
|
||||||
this._removeModalHighlightListeners(window);
|
this._removeModalHighlightListeners(window);
|
||||||
delete this._brightText;
|
delete dict.brightText;
|
||||||
|
|
||||||
this.visible = false;
|
dict.visible = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -382,12 +419,13 @@ FinderHighlighter.prototype = {
|
||||||
*/
|
*/
|
||||||
update(data) {
|
update(data) {
|
||||||
let window = this.finder._getWindow();
|
let window = this.finder._getWindow();
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
let foundRange = this.finder._fastFind.getFoundRange();
|
let foundRange = this.finder._fastFind.getFoundRange();
|
||||||
if (!this._modal) {
|
if (!this._modal) {
|
||||||
if (this._highlightAll) {
|
if (this._highlightAll) {
|
||||||
this._currentFoundRange = foundRange;
|
dict.currentFoundRange = foundRange;
|
||||||
let params = this.iterator.params;
|
let params = this.iterator.params;
|
||||||
if (this.iterator._areParamsEqual(params, this._lastIteratorParams))
|
if (this.iterator._areParamsEqual(params, dict.lastIteratorParams))
|
||||||
return;
|
return;
|
||||||
if (params)
|
if (params)
|
||||||
this.highlight(true, params.word, params.linksOnly);
|
this.highlight(true, params.word, params.linksOnly);
|
||||||
|
@ -402,8 +440,8 @@ FinderHighlighter.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
let outlineNode;
|
let outlineNode;
|
||||||
if (foundRange !== this._currentFoundRange || data.findAgain) {
|
if (foundRange !== dict.currentFoundRange || data.findAgain) {
|
||||||
this._currentFoundRange = foundRange;
|
dict.currentFoundRange = foundRange;
|
||||||
|
|
||||||
let textContent = this._getRangeContentArray(foundRange);
|
let textContent = this._getRangeContentArray(foundRange);
|
||||||
if (!textContent.length) {
|
if (!textContent.length) {
|
||||||
|
@ -411,36 +449,20 @@ FinderHighlighter.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rect = foundRange.getClientRects()[0];
|
|
||||||
let fontStyle = this._getRangeFontStyle(foundRange);
|
let fontStyle = this._getRangeFontStyle(foundRange);
|
||||||
if (typeof this._brightText == "undefined") {
|
if (typeof dict.brightText == "undefined") {
|
||||||
this._brightText = this._isColorBright(fontStyle.color);
|
dict.brightText = this._isColorBright(fontStyle.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text color in the outline is determined by our stylesheet.
|
if (!dict.visible)
|
||||||
delete fontStyle.color;
|
|
||||||
|
|
||||||
if (!this.visible)
|
|
||||||
this.show(window);
|
this.show(window);
|
||||||
else
|
else
|
||||||
this._maybeCreateModalHighlightNodes(window);
|
this._maybeCreateModalHighlightNodes(window);
|
||||||
|
|
||||||
outlineNode = this._modalHighlightOutline;
|
this._updateRangeOutline(dict, textContent, fontStyle);
|
||||||
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;`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outlineNode = this._modalHighlightOutline;
|
outlineNode = dict.modalHighlightOutline;
|
||||||
try {
|
try {
|
||||||
outlineNode.removeAttributeForElement(kModalOutlineId, "grow");
|
outlineNode.removeAttributeForElement(kModalOutlineId, "grow");
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
|
@ -453,12 +475,18 @@ FinderHighlighter.prototype = {
|
||||||
* Invalidates the list by clearing the map of highglighted ranges that we
|
* Invalidates the list by clearing the map of highglighted ranges that we
|
||||||
* keep to build the mask for.
|
* keep to build the mask for.
|
||||||
*/
|
*/
|
||||||
clear() {
|
clear(window = null) {
|
||||||
this._currentFoundRange = 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.
|
let dict = this.getForWindow(window.top);
|
||||||
if (this._modalHighlightRectsMap)
|
dict.currentFoundRange = null;
|
||||||
this._modalHighlightRectsMap.clear();
|
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.
|
* everything when the user starts to find in page again.
|
||||||
*/
|
*/
|
||||||
onLocationChange() {
|
onLocationChange() {
|
||||||
this.clear();
|
let window = this.finder._getWindow();
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
|
this.clear(window);
|
||||||
|
|
||||||
if (!this._modalHighlightOutline)
|
if (!dict.modalHighlightOutline)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (kDebug)
|
if (kDebug) {
|
||||||
this._modalHighlightOutline.remove();
|
dict.modalHighlightOutline.remove();
|
||||||
try {
|
} else {
|
||||||
this.finder._getWindow().document
|
try {
|
||||||
.removeAnonymousContent(this._modalHighlightOutline);
|
window.document.removeAnonymousContent(dict.modalHighlightOutline);
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
|
}
|
||||||
|
|
||||||
this._modalHighlightOutline = null;
|
dict.modalHighlightOutline = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -520,6 +551,8 @@ FinderHighlighter.prototype = {
|
||||||
* @param {nsIDOMRange} restoreRange
|
* @param {nsIDOMRange} restoreRange
|
||||||
*/
|
*/
|
||||||
_clearSelection(controller, restoreRange = null) {
|
_clearSelection(controller, restoreRange = null) {
|
||||||
|
if (!controller)
|
||||||
|
return;
|
||||||
let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
|
||||||
sel.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
if (restoreRange) {
|
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.
|
* @param {nsIDOMWindow} window
|
||||||
* @return {Object} The current scroll position.
|
* @return {Rect}
|
||||||
*/
|
*/
|
||||||
_getScrollPosition(window = null) {
|
_getRootBounds(window) {
|
||||||
|
let dwu = this._getDWU(window);
|
||||||
|
let cssPageRect = Rect.fromRect(dwu.getRootBounds());
|
||||||
|
|
||||||
let scrollX = {};
|
let scrollX = {};
|
||||||
let scrollY = {};
|
let scrollY = {};
|
||||||
this._getDWU(window).getScrollXY(false, scrollX, scrollY);
|
dwu.getScrollXY(false, scrollX, scrollY);
|
||||||
return {
|
cssPageRect.translate(scrollX.value, scrollY.value);
|
||||||
scrollX: scrollX.value,
|
|
||||||
scrollY: 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) {
|
_getWindowDimensions(window) {
|
||||||
// First we'll try without flushing layout, because it's way faster.
|
// First we'll try without flushing layout, because it's way faster.
|
||||||
let dwu = this._getDWU(window);
|
let dwu = this._getDWU(window);
|
||||||
let {width, height} = dwu.getBoundsWithoutFlushing(window.document.body);
|
let { width, height } = dwu.getRootBounds();
|
||||||
|
|
||||||
if (!width || !height) {
|
if (!width || !height) {
|
||||||
// We need a flush after all :'(
|
// We need a flush after all :'(
|
||||||
|
@ -655,6 +710,141 @@ FinderHighlighter.prototype = {
|
||||||
return new Color(...cssColor).isBright;
|
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
|
* Add a range to the list of ranges to highlight on, or cut out of, the dimmed
|
||||||
* background.
|
* background.
|
||||||
|
@ -666,24 +856,7 @@ FinderHighlighter.prototype = {
|
||||||
if (!this._getRangeContentArray(range).length)
|
if (!this._getRangeContentArray(range).length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let rects = new Set();
|
this._updateRangeRects(range);
|
||||||
// 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.show(window);
|
this.show(window);
|
||||||
// We don't repaint the mask right away, but pass it off to a render loop of
|
// 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.
|
* @param {nsIDOMWindow} window Window to draw in.
|
||||||
*/
|
*/
|
||||||
_maybeCreateModalHighlightNodes(window) {
|
_maybeCreateModalHighlightNodes(window) {
|
||||||
if (this._modalHighlightOutline) {
|
window = window.top;
|
||||||
if (!this._modalHighlightAllMask) {
|
let dict = this.getForWindow(window);
|
||||||
|
if (dict.modalHighlightOutline) {
|
||||||
|
if (!dict.modalHighlightAllMask) {
|
||||||
// Make sure to at least show the dimmed background.
|
// Make sure to at least show the dimmed background.
|
||||||
this._repaintHighlightAllMask(window, false);
|
this._repaintHighlightAllMask(window, false);
|
||||||
this._scheduleRepaintOfMask(window);
|
this._scheduleRepaintOfMask(window);
|
||||||
|
@ -735,7 +910,7 @@ FinderHighlighter.prototype = {
|
||||||
outlineBox.appendChild(outlineBoxText);
|
outlineBox.appendChild(outlineBoxText);
|
||||||
|
|
||||||
container.appendChild(outlineBox);
|
container.appendChild(outlineBox);
|
||||||
this._modalHighlightOutline = kDebug ?
|
dict.modalHighlightOutline = kDebug ?
|
||||||
mockAnonymousContentNode(document.body.appendChild(container.firstChild)) :
|
mockAnonymousContentNode(document.body.appendChild(container.firstChild)) :
|
||||||
document.insertAnonymousContent(container);
|
document.insertAnonymousContent(container);
|
||||||
|
|
||||||
|
@ -752,6 +927,8 @@ FinderHighlighter.prototype = {
|
||||||
* @param {Boolean} [paintContent]
|
* @param {Boolean} [paintContent]
|
||||||
*/
|
*/
|
||||||
_repaintHighlightAllMask(window, paintContent = true) {
|
_repaintHighlightAllMask(window, paintContent = true) {
|
||||||
|
window = window.top;
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
let document = window.document;
|
let document = window.document;
|
||||||
|
|
||||||
const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
|
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.
|
// Make sure the dimmed mask node takes the full width and height that's available.
|
||||||
let {width, height} = this._getWindowDimensions(window);
|
let {width, height} = this._getWindowDimensions(window);
|
||||||
this._lastWindowDimensions = { width, height };
|
dict.lastWindowDimensions = { width, height };
|
||||||
maskNode.setAttribute("id", kMaskId);
|
maskNode.setAttribute("id", kMaskId);
|
||||||
maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
|
maskNode.setAttribute("class", kMaskId + (kDebug ? ` ${kModalIdPrefix}-findbar-debug` : ""));
|
||||||
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
|
maskNode.setAttribute("style", `width: ${width}px; height: ${height}px;`);
|
||||||
if (this._brightText)
|
if (dict.brightText)
|
||||||
maskNode.setAttribute("brighttext", "true");
|
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.
|
// Create a DOM node for each rectangle representing the ranges we found.
|
||||||
let maskContent = [];
|
let maskContent = [];
|
||||||
const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
|
const kRectClassName = kModalIdPrefix + "-findbar-modalHighlight-rect";
|
||||||
if (this._modalHighlightRectsMap) {
|
for (let [range, rects] of dict.modalHighlightRectsMap) {
|
||||||
for (let [range, rects] of this._modalHighlightRectsMap) {
|
for (let rect of rects) {
|
||||||
for (let rect of rects) {
|
maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
|
||||||
maskContent.push(`<div class="${kRectClassName}" style="top: ${rect.y}px;
|
left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
|
||||||
left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maskNode.innerHTML = maskContent.join("");
|
maskNode.innerHTML = maskContent.join("");
|
||||||
|
@ -785,7 +962,7 @@ FinderHighlighter.prototype = {
|
||||||
// free to alter DOM nodes inside the CanvasFrame.
|
// free to alter DOM nodes inside the CanvasFrame.
|
||||||
this._removeHighlightAllMask(window);
|
this._removeHighlightAllMask(window);
|
||||||
|
|
||||||
this._modalHighlightAllMask = kDebug ?
|
dict.modalHighlightAllMask = kDebug ?
|
||||||
mockAnonymousContentNode(document.body.appendChild(maskNode)) :
|
mockAnonymousContentNode(document.body.appendChild(maskNode)) :
|
||||||
document.insertAnonymousContent(maskNode);
|
document.insertAnonymousContent(maskNode);
|
||||||
},
|
},
|
||||||
|
@ -796,16 +973,21 @@ FinderHighlighter.prototype = {
|
||||||
* @param {nsIDOMWindow} window
|
* @param {nsIDOMWindow} window
|
||||||
*/
|
*/
|
||||||
_removeHighlightAllMask(window) {
|
_removeHighlightAllMask(window) {
|
||||||
if (this._modalHighlightAllMask) {
|
window = window.top;
|
||||||
// If the current window isn't the one the content was inserted into, this
|
let dict = this.getForWindow(window);
|
||||||
// will fail, but that's fine.
|
if (!dict.modalHighlightAllMask)
|
||||||
if (kDebug)
|
return;
|
||||||
this._modalHighlightAllMask.remove();
|
|
||||||
|
// 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 {
|
try {
|
||||||
window.document.removeAnonymousContent(this._modalHighlightAllMask);
|
window.document.removeAnonymousContent(dict.modalHighlightAllMask);
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
this._modalHighlightAllMask = null;
|
|
||||||
}
|
}
|
||||||
|
dict.modalHighlightAllMask = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -814,37 +996,46 @@ FinderHighlighter.prototype = {
|
||||||
* `kModalHighlightRepaintFreqMs` milliseconds.
|
* `kModalHighlightRepaintFreqMs` milliseconds.
|
||||||
*
|
*
|
||||||
* @param {nsIDOMWindow} window
|
* @param {nsIDOMWindow} window
|
||||||
* @param {Boolean} contentChanged Whether the documents' content changed
|
* @param {Object} options Dictionary of painter hints that contains the
|
||||||
* in the meantime. This happens when the
|
* following properties:
|
||||||
* DOM is updated whilst the page is loaded.
|
* {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) {
|
_scheduleRepaintOfMask(window, { contentChanged, scrollOnly } = { contentChanged: false, scrollOnly: false }) {
|
||||||
if (this._modalRepaintScheduler) {
|
window = window.top;
|
||||||
window.clearTimeout(this._modalRepaintScheduler);
|
let dict = this.getForWindow(window);
|
||||||
this._modalRepaintScheduler = null;
|
let repaintFixedNodes = (scrollOnly && !!dict.dynamicRangesSet.size);
|
||||||
}
|
|
||||||
|
|
||||||
// When we request to repaint unconditionally, we mean to call
|
// When we request to repaint unconditionally, we mean to call
|
||||||
// `_repaintHighlightAllMask()` right after the timeout.
|
// `_repaintHighlightAllMask()` right after the timeout.
|
||||||
if (!this._unconditionalRepaintRequested)
|
if (!dict.unconditionalRepaintRequested)
|
||||||
this._unconditionalRepaintRequested = !contentChanged;
|
dict.unconditionalRepaintRequested = !contentChanged || repaintFixedNodes;
|
||||||
|
|
||||||
this._modalRepaintScheduler = window.setTimeout(() => {
|
if (dict.modalRepaintScheduler)
|
||||||
if (this._unconditionalRepaintRequested) {
|
return;
|
||||||
this._unconditionalRepaintRequested = false;
|
|
||||||
|
dict.modalRepaintScheduler = window.setTimeout(() => {
|
||||||
|
dict.modalRepaintScheduler = null;
|
||||||
|
|
||||||
|
if (dict.unconditionalRepaintRequested) {
|
||||||
|
dict.unconditionalRepaintRequested = false;
|
||||||
this._repaintHighlightAllMask(window);
|
this._repaintHighlightAllMask(window);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { width, height } = this._getWindowDimensions(window);
|
let { width, height } = this._getWindowDimensions(window);
|
||||||
if (!this._modalHighlightRectsMap ||
|
if (!dict.modalHighlightRectsMap.size ||
|
||||||
(Math.abs(this._lastWindowDimensions.width - width) < kContentChangeThresholdPx &&
|
(Math.abs(dict.lastWindowDimensions.width - width) < kContentChangeThresholdPx &&
|
||||||
Math.abs(this._lastWindowDimensions.height - height) < kContentChangeThresholdPx)) {
|
Math.abs(dict.lastWindowDimensions.height - height) < kContentChangeThresholdPx)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.iterator.restart(this.finder);
|
this.iterator.restart(this.finder);
|
||||||
this._lastWindowDimensions = { width, height };
|
dict.lastWindowDimensions = { width, height };
|
||||||
this._repaintHighlightAllMask(window);
|
this._repaintHighlightAllMask(window);
|
||||||
}, kModalHighlightRepaintFreqMs);
|
}, kModalHighlightRepaintFreqMs);
|
||||||
},
|
},
|
||||||
|
@ -858,12 +1049,9 @@ FinderHighlighter.prototype = {
|
||||||
* @param {nsIDOMWindow} window
|
* @param {nsIDOMWindow} window
|
||||||
*/
|
*/
|
||||||
_maybeInstallStyleSheet(window) {
|
_maybeInstallStyleSheet(window) {
|
||||||
let document = window.document;
|
window = window.top;
|
||||||
// The WeakMap is a cheap method to make sure we don't needlessly insert the
|
let dict = this.getForWindow(window);
|
||||||
// same sheet twice.
|
if (dict.installedSheet)
|
||||||
if (!this._modalInstalledSheets)
|
|
||||||
this._modalInstalledSheets = new WeakMap();
|
|
||||||
if (this._modalInstalledSheets.has(document))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let dwu = this._getDWU(window);
|
let dwu = this._getDWU(window);
|
||||||
|
@ -871,7 +1059,7 @@ FinderHighlighter.prototype = {
|
||||||
try {
|
try {
|
||||||
dwu.loadSheetUsingURIString(uri, dwu.AGENT_SHEET);
|
dwu.loadSheetUsingURIString(uri, dwu.AGENT_SHEET);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
this._modalInstalledSheets.set(document, uri);
|
dict.installedSheet = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -881,16 +1069,22 @@ FinderHighlighter.prototype = {
|
||||||
* @param {nsIDOMWindow} window
|
* @param {nsIDOMWindow} window
|
||||||
*/
|
*/
|
||||||
_addModalHighlightListeners(window) {
|
_addModalHighlightListeners(window) {
|
||||||
if (this._highlightListeners)
|
window = window.top;
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
|
if (dict.highlightListeners)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._highlightListeners = [
|
window = window.top;
|
||||||
this._scheduleRepaintOfMask.bind(this, window, true),
|
dict.highlightListeners = [
|
||||||
|
this._scheduleRepaintOfMask.bind(this, window, { contentChanged: true }),
|
||||||
|
this._scheduleRepaintOfMask.bind(this, window, { scrollOnly: true }),
|
||||||
this.hide.bind(this, window, null)
|
this.hide.bind(this, window, null)
|
||||||
];
|
];
|
||||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||||
target.addEventListener("MozAfterPaint", this._highlightListeners[0]);
|
target.addEventListener("MozAfterPaint", dict.highlightListeners[0]);
|
||||||
window.addEventListener("click", this._highlightListeners[1]);
|
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
|
* @param {nsIDOMWindow} window
|
||||||
*/
|
*/
|
||||||
_removeModalHighlightListeners(window) {
|
_removeModalHighlightListeners(window) {
|
||||||
if (!this._highlightListeners)
|
window = window.top;
|
||||||
|
let dict = this.getForWindow(window);
|
||||||
|
if (!dict.highlightListeners)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
let target = this.iterator._getDocShell(window).chromeEventHandler;
|
||||||
target.removeEventListener("MozAfterPaint", this._highlightListeners[0]);
|
target.removeEventListener("MozAfterPaint", dict.highlightListeners[0]);
|
||||||
window.removeEventListener("click", this._highlightListeners[1]);
|
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;
|
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/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 kIterationSizeMax = 100;
|
||||||
|
const kTimeoutPref = "findbar.iteratorTimeout";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FinderIterator singleton. See the documentation for the `start()` method to
|
* FinderIterator singleton. See the documentation for the `start()` method to
|
||||||
|
@ -23,6 +30,8 @@ this.FinderIterator = {
|
||||||
_previousParams: null,
|
_previousParams: null,
|
||||||
_previousRanges: [],
|
_previousRanges: [],
|
||||||
_spawnId: 0,
|
_spawnId: 0,
|
||||||
|
_timeout: Services.prefs.getIntPref(kTimeoutPref),
|
||||||
|
_timer: null,
|
||||||
ranges: [],
|
ranges: [],
|
||||||
running: false,
|
running: false,
|
||||||
|
|
||||||
|
@ -50,30 +59,35 @@ this.FinderIterator = {
|
||||||
* The returned promise is resolved when 1) the limit is reached, 2) when all
|
* 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.
|
* the ranges have been found or 3) when `stop()` is called whilst iterating.
|
||||||
*
|
*
|
||||||
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
|
* @param {Number} [options.allowDistance] Allowed edit distance between the
|
||||||
* mode
|
* current word and `options.word`
|
||||||
* @param {Boolean} options.entireWord Whether to search in entire-word mode
|
* when the iterator is already running
|
||||||
* @param {Finder} options.finder Currently active Finder instance
|
* @param {Boolean} options.caseSensitive Whether to search in case sensitive
|
||||||
* @param {Number} [options.limit] Limit the amount of results to be
|
* mode
|
||||||
* passed back. Optional, defaults to no
|
* @param {Boolean} options.entireWord Whether to search in entire-word mode
|
||||||
* limit.
|
* @param {Finder} options.finder Currently active Finder instance
|
||||||
* @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
|
* @param {Number} [options.limit] Limit the amount of results to be
|
||||||
* hyperlink (used by QuickFind).
|
* passed back. Optional, defaults to no
|
||||||
* Optional, defaults to `false`.
|
* limit.
|
||||||
* @param {Object} options.listener Listener object that implements the
|
* @param {Boolean} [options.linksOnly] Only yield ranges that are inside a
|
||||||
* following callback functions:
|
* hyperlink (used by QuickFind).
|
||||||
* - onIteratorRangeFound({nsIDOMRange} range);
|
* Optional, defaults to `false`.
|
||||||
* - onIteratorReset();
|
* @param {Object} options.listener Listener object that implements the
|
||||||
* - onIteratorRestart({Object} iterParams);
|
* following callback functions:
|
||||||
* - onIteratorStart({Object} iterParams);
|
* - onIteratorRangeFound({nsIDOMRange} range);
|
||||||
* @param {Boolean} [options.useCache] Whether to allow results already
|
* - onIteratorReset();
|
||||||
* present in the cache or demand fresh.
|
* - onIteratorRestart({Object} iterParams);
|
||||||
* Optional, defaults to `false`.
|
* - onIteratorStart({Object} iterParams);
|
||||||
* @param {String} options.word Word to search for
|
* @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}
|
* @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.
|
// Take care of default values for non-required options.
|
||||||
|
if (typeof allowDistance != "number")
|
||||||
|
allowDistance = 0;
|
||||||
if (typeof limit != "number")
|
if (typeof limit != "number")
|
||||||
limit = -1;
|
limit = -1;
|
||||||
if (typeof linksOnly != "boolean")
|
if (typeof linksOnly != "boolean")
|
||||||
|
@ -116,9 +130,16 @@ this.FinderIterator = {
|
||||||
|
|
||||||
if (this.running) {
|
if (this.running) {
|
||||||
// Double-check if we're not running the iterator with a different set of
|
// Double-check if we're not running the iterator with a different set of
|
||||||
// parameters, otherwise throw an error with the most common reason.
|
// parameters, otherwise report an error with the most common reason.
|
||||||
if (!this._areParamsEqual(this._currentParams, iterParams))
|
if (!this._areParamsEqual(this._currentParams, iterParams, allowDistance)) {
|
||||||
throw new Error(`We're currently iterating over '${this._currentParams.word}', not '${word}'`);
|
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.
|
// if we're still running, yield the set we have built up this far.
|
||||||
this._yieldIntermediateResult(listener, window);
|
this._yieldIntermediateResult(listener, window);
|
||||||
|
@ -145,6 +166,11 @@ this.FinderIterator = {
|
||||||
if (!this.running)
|
if (!this.running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (this._timer) {
|
||||||
|
clearTimeout(this._timer);
|
||||||
|
this._timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (cachePrevious) {
|
if (cachePrevious) {
|
||||||
this._previousRanges = [].concat(this.ranges);
|
this._previousRanges = [].concat(this.ranges);
|
||||||
this._previousParams = Object.assign({}, this._currentParams);
|
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.
|
* If the iterator is running, it will be stopped as soon as possible.
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
|
if (this._timer) {
|
||||||
|
clearTimeout(this._timer);
|
||||||
|
this._timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
this._catchingUp.clear();
|
this._catchingUp.clear();
|
||||||
this._currentParams = this._previousParams = null;
|
this._currentParams = this._previousParams = null;
|
||||||
this._previousRanges = [];
|
this._previousRanges = [];
|
||||||
|
@ -266,16 +297,19 @@ this.FinderIterator = {
|
||||||
/**
|
/**
|
||||||
* Internal; compare if two sets of iterator parameters are equivalent.
|
* Internal; compare if two sets of iterator parameters are equivalent.
|
||||||
*
|
*
|
||||||
* @param {Object} paramSet1 First set of params (left hand side)
|
* @param {Object} paramSet1 First set of params (left hand side)
|
||||||
* @param {Object} paramSet2 Second set of params (right 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}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
_areParamsEqual(paramSet1, paramSet2) {
|
_areParamsEqual(paramSet1, paramSet2, allowDistance = 0) {
|
||||||
return (!!paramSet1 && !!paramSet2 &&
|
return (!!paramSet1 && !!paramSet2 &&
|
||||||
paramSet1.caseSensitive === paramSet2.caseSensitive &&
|
paramSet1.caseSensitive === paramSet2.caseSensitive &&
|
||||||
paramSet1.entireWord === paramSet2.entireWord &&
|
paramSet1.entireWord === paramSet2.entireWord &&
|
||||||
paramSet1.linksOnly === paramSet2.linksOnly &&
|
paramSet1.linksOnly === paramSet2.linksOnly &&
|
||||||
paramSet1.word == paramSet2.word);
|
NLP.levenshtein(paramSet1.word, paramSet2.word) <= allowDistance);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -384,6 +418,17 @@ this.FinderIterator = {
|
||||||
* @yield {nsIDOMRange}
|
* @yield {nsIDOMRange}
|
||||||
*/
|
*/
|
||||||
_findAllRanges: Task.async(function* (finder, window, spawnId) {
|
_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);
|
this._notifyListeners("start", this.params);
|
||||||
|
|
||||||
// First we collect all frames we need to search through, whilst making sure
|
// First we collect all frames we need to search through, whilst making sure
|
||||||
|
|
|
@ -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',
|
'Locale.jsm',
|
||||||
'Log.jsm',
|
'Log.jsm',
|
||||||
'NewTabUtils.jsm',
|
'NewTabUtils.jsm',
|
||||||
|
'NLP.jsm',
|
||||||
'ObjectUtils.jsm',
|
'ObjectUtils.jsm',
|
||||||
'PageMenu.jsm',
|
'PageMenu.jsm',
|
||||||
'PageMetadata.jsm',
|
'PageMetadata.jsm',
|
||||||
|
|
|
@ -13,26 +13,29 @@ add_task(function* () {
|
||||||
let finder = tab.linkedBrowser.finder;
|
let finder = tab.linkedBrowser.finder;
|
||||||
let listener = {
|
let listener = {
|
||||||
onFindResult: function () {
|
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);
|
finder.addResultListener(listener);
|
||||||
|
|
||||||
function waitForFind() {
|
function waitForFind(which = "onFindResult") {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
listener.onFindResult = resolve;
|
listener[which] = resolve;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let promiseFind = waitForFind();
|
let promiseFind = waitForFind("onHighlightFinished");
|
||||||
finder.highlight(true, "content");
|
finder.highlight(true, "content");
|
||||||
let findResult = yield promiseFind;
|
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");
|
finder.highlight(true, "Bla");
|
||||||
findResult = yield promiseFind;
|
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.
|
// Search only for links and draw outlines.
|
||||||
promiseFind = waitForFind();
|
promiseFind = waitForFind();
|
||||||
|
|
|
@ -155,8 +155,8 @@ add_task(function* testModalResults() {
|
||||||
}],
|
}],
|
||||||
["o", {
|
["o", {
|
||||||
rectCount: 492,
|
rectCount: 492,
|
||||||
insertCalls: [1, 3],
|
insertCalls: [1, 4],
|
||||||
removeCalls: [1, 2]
|
removeCalls: [1, 3]
|
||||||
}]
|
}]
|
||||||
]);
|
]);
|
||||||
let url = kFixtureBaseURL + "file_FinderSample.html";
|
let url = kFixtureBaseURL + "file_FinderSample.html";
|
||||||
|
@ -294,7 +294,7 @@ add_task(function* testHighlightAllToggle() {
|
||||||
expectedResult = {
|
expectedResult = {
|
||||||
rectCount: 0,
|
rectCount: 0,
|
||||||
insertCalls: [0, 1],
|
insertCalls: [0, 1],
|
||||||
removeCalls: [1, 1]
|
removeCalls: [0, 1]
|
||||||
};
|
};
|
||||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||||
yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
|
yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
|
||||||
|
|
|
@ -141,7 +141,7 @@ add_task(function* test_stop() {
|
||||||
|
|
||||||
yield whenDone;
|
yield whenDone;
|
||||||
|
|
||||||
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
|
Assert.equal(count, 0, "Number of ranges should be 0");
|
||||||
|
|
||||||
FinderIterator.reset();
|
FinderIterator.reset();
|
||||||
});
|
});
|
||||||
|
@ -161,9 +161,8 @@ add_task(function* test_reset() {
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.ok(FinderIterator.running, "Yup, running we are");
|
Assert.ok(FinderIterator.running, "Yup, running we are");
|
||||||
Assert.equal(count, 100, "Number of ranges should match `kIterationSizeMax`");
|
Assert.equal(count, 0, "Number of ranges should match 0");
|
||||||
Assert.equal(FinderIterator.ranges.length, 100,
|
Assert.equal(FinderIterator.ranges.length, 0, "Number of ranges should match 0");
|
||||||
"Number of ranges should match `kIterationSizeMax`");
|
|
||||||
|
|
||||||
FinderIterator.reset();
|
FinderIterator.reset();
|
||||||
|
|
||||||
|
@ -173,7 +172,7 @@ add_task(function* test_reset() {
|
||||||
|
|
||||||
yield whenDone;
|
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() {
|
add_task(function* test_parallel_starts() {
|
||||||
|
@ -191,8 +190,7 @@ add_task(function* test_parallel_starts() {
|
||||||
word: findText
|
word: findText
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start again after a few milliseconds.
|
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 120));
|
||||||
yield new Promise(resolve => gMockWindow.setTimeout(resolve, 20));
|
|
||||||
Assert.ok(FinderIterator.running, "We ought to be running here");
|
Assert.ok(FinderIterator.running, "We ought to be running here");
|
||||||
|
|
||||||
let count2 = 0;
|
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.less(count2, rangeCount, "Not all ranges should've been found");
|
||||||
|
|
||||||
Assert.equal(count2, count, "The second start was later, but should have caught up");
|
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();
|
||||||
});
|
});
|
||||||
|
|
Загрузка…
Ссылка в новой задаче