зеркало из https://github.com/mozilla/gecko-dev.git
1387 строки
45 KiB
JavaScript
1387 строки
45 KiB
JavaScript
/* 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 is loaded into chrome windows with the subscript loader. Wrap in
|
|
// a block to prevent accidentally leaking globals onto `window`.
|
|
{
|
|
const { Services } = ChromeUtils.import(
|
|
"resource://gre/modules/Services.jsm"
|
|
);
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
let LazyConstants = {};
|
|
ChromeUtils.defineModuleGetter(
|
|
LazyConstants,
|
|
"PluralForm",
|
|
"resource://gre/modules/PluralForm.jsm"
|
|
);
|
|
|
|
const PREFS_TO_OBSERVE_BOOL = new Map([
|
|
["findAsYouType", "accessibility.typeaheadfind"],
|
|
["manualFAYT", "accessibility.typeaheadfind.manual"],
|
|
["typeAheadLinksOnly", "accessibility.typeaheadfind.linksonly"],
|
|
["entireWord", "findbar.entireword"],
|
|
["highlightAll", "findbar.highlightAll"],
|
|
["useModalHighlight", "findbar.modalHighlight"],
|
|
]);
|
|
const PREFS_TO_OBSERVE_INT = new Map([
|
|
["typeAheadCaseSensitive", "accessibility.typeaheadfind.casesensitive"],
|
|
["matchDiacritics", "findbar.matchdiacritics"],
|
|
]);
|
|
const PREFS_TO_OBSERVE_ALL = new Map([
|
|
...PREFS_TO_OBSERVE_BOOL,
|
|
...PREFS_TO_OBSERVE_INT,
|
|
]);
|
|
const TOPIC_MAC_APP_ACTIVATE = "mac_app_activate";
|
|
|
|
class MozFindbar extends XULElement {
|
|
constructor() {
|
|
super();
|
|
MozXULElement.insertFTLIfNeeded("toolkit/main-window/findbar.ftl");
|
|
this.destroy = this.destroy.bind(this);
|
|
|
|
// We have to guard against `this.close` being |null| due to an unknown
|
|
// issue, which is tracked in bug 957999.
|
|
this.addEventListener(
|
|
"keypress",
|
|
event => {
|
|
if (event.keyCode == event.DOM_VK_ESCAPE) {
|
|
if (this.close) {
|
|
this.close();
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
},
|
|
true
|
|
);
|
|
|
|
this.content = MozXULElement.parseXULToFragment(`
|
|
<hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
|
|
<hbox anonid="findbar-textbox-wrapper" align="stretch">
|
|
<html:input anonid="findbar-textbox" class="findbar-textbox findbar-find-fast" />
|
|
<toolbarbutton anonid="find-previous" class="findbar-find-previous tabbable"
|
|
data-l10n-attrs="tooltiptext" data-l10n-id="findbar-previous"
|
|
oncommand="onFindAgainCommand(true);" disabled="true" />
|
|
<toolbarbutton anonid="find-next" class="findbar-find-next tabbable"
|
|
data-l10n-id="findbar-next" oncommand="onFindAgainCommand(false);" disabled="true" />
|
|
</hbox>
|
|
<toolbarbutton anonid="highlight" class="findbar-highlight findbar-button tabbable"
|
|
data-l10n-id="findbar-highlight-all2" oncommand="toggleHighlight(this.checked);" type="checkbox" />
|
|
<toolbarbutton anonid="find-case-sensitive" class="findbar-case-sensitive findbar-button tabbable"
|
|
data-l10n-id="findbar-case-sensitive" oncommand="_setCaseSensitivity(this.checked ? 1 : 0);" type="checkbox" />
|
|
<toolbarbutton anonid="find-match-diacritics" class="findbar-match-diacritics findbar-button tabbable"
|
|
data-l10n-id="findbar-match-diacritics" oncommand="_setDiacriticMatching(this.checked ? 1 : 0);" type="checkbox" />
|
|
<toolbarbutton anonid="find-entire-word" class="findbar-entire-word findbar-button tabbable"
|
|
data-l10n-id="findbar-entire-word" oncommand="toggleEntireWord(this.checked);" type="checkbox" />
|
|
<label anonid="match-case-status" class="findbar-find-fast" />
|
|
<label anonid="match-diacritics-status" class="findbar-find-fast" />
|
|
<label anonid="entire-word-status" class="findbar-find-fast" />
|
|
<label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true" />
|
|
<image anonid="find-status-icon" class="findbar-find-fast find-status-icon" />
|
|
<description anonid="find-status" control="findbar-textbox" class="findbar-find-fast findbar-find-status" />
|
|
</hbox>
|
|
<toolbarbutton anonid="find-closebutton" class="findbar-closebutton close-icon"
|
|
data-l10n-id="findbar-find-button-close" oncommand="close();" />
|
|
`);
|
|
}
|
|
|
|
connectedCallback() {
|
|
// Hide the findbar immediately without animation. This prevents a flicker in the case where
|
|
// we'll never be shown (i.e. adopting a tab that has a previously-opened-but-now-closed
|
|
// findbar into a new window).
|
|
this.setAttribute("noanim", "true");
|
|
this.hidden = true;
|
|
this.appendChild(document.importNode(this.content, true));
|
|
|
|
/**
|
|
* Please keep in sync with toolkit/modules/FindBarContent.jsm
|
|
*/
|
|
this.FIND_NORMAL = 0;
|
|
|
|
this.FIND_TYPEAHEAD = 1;
|
|
|
|
this.FIND_LINKS = 2;
|
|
|
|
this._findMode = 0;
|
|
|
|
this._flashFindBar = 0;
|
|
|
|
this._initialFlashFindBarCount = 6;
|
|
|
|
/**
|
|
* For tests that need to know when the find bar is finished
|
|
* initializing, we store a promise to notify on.
|
|
*/
|
|
this._startFindDeferred = null;
|
|
|
|
this._browser = null;
|
|
|
|
this._destroyed = false;
|
|
|
|
this._strBundle = null;
|
|
|
|
this._xulBrowserWindow = null;
|
|
|
|
// These elements are accessed frequently and are therefore cached.
|
|
this._findField = this.getElement("findbar-textbox");
|
|
this._foundMatches = this.getElement("found-matches");
|
|
this._findStatusIcon = this.getElement("find-status-icon");
|
|
this._findStatusDesc = this.getElement("find-status");
|
|
|
|
this._foundURL = null;
|
|
|
|
let prefsvc = Services.prefs;
|
|
|
|
this.quickFindTimeoutLength = prefsvc.getIntPref(
|
|
"accessibility.typeaheadfind.timeout"
|
|
);
|
|
this._flashFindBar = prefsvc.getIntPref(
|
|
"accessibility.typeaheadfind.flashBar"
|
|
);
|
|
|
|
let observe = (this._observe = this.observe.bind(this));
|
|
for (let [propName, prefName] of PREFS_TO_OBSERVE_ALL) {
|
|
prefsvc.addObserver(prefName, observe);
|
|
let prefGetter = PREFS_TO_OBSERVE_BOOL.has(propName) ? "Bool" : "Int";
|
|
this["_" + propName] = prefsvc[`get${prefGetter}Pref`](prefName);
|
|
}
|
|
Services.obs.addObserver(observe, TOPIC_MAC_APP_ACTIVATE);
|
|
|
|
this._findResetTimeout = -1;
|
|
|
|
// Make sure the FAYT keypress listener is attached by initializing the
|
|
// browser property.
|
|
if (this.getAttribute("browserid")) {
|
|
setTimeout(() => {
|
|
// eslint-disable-next-line no-self-assign
|
|
this.browser = this.browser;
|
|
}, 0);
|
|
}
|
|
|
|
window.addEventListener("unload", this.destroy);
|
|
|
|
this._findField.addEventListener("input", event => {
|
|
// We should do nothing during composition. E.g., composing string
|
|
// before converting may matches a forward word of expected word.
|
|
// After that, even if user converts the composition string to the
|
|
// expected word, it may find second or later searching word in the
|
|
// document.
|
|
if (this._isIMEComposing) {
|
|
return;
|
|
}
|
|
|
|
const value = this._findField.value;
|
|
if (this._hadValue && !value) {
|
|
this._willfullyDeleted = true;
|
|
this._hadValue = false;
|
|
} else if (value.trim()) {
|
|
this._hadValue = true;
|
|
this._willfullyDeleted = false;
|
|
}
|
|
this._find(value);
|
|
});
|
|
|
|
this._findField.addEventListener("keypress", event => {
|
|
switch (event.keyCode) {
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
if (this.findMode == this.FIND_NORMAL) {
|
|
let findString = this._findField;
|
|
if (!findString.value) {
|
|
return;
|
|
}
|
|
if (event.getModifierState("Accel")) {
|
|
this.getElement("highlight").click();
|
|
return;
|
|
}
|
|
|
|
this.onFindAgainCommand(event.shiftKey);
|
|
} else {
|
|
this._finishFAYT(event);
|
|
}
|
|
break;
|
|
case KeyEvent.DOM_VK_TAB:
|
|
let shouldHandle =
|
|
!event.altKey && !event.ctrlKey && !event.metaKey;
|
|
if (shouldHandle && this.findMode != this.FIND_NORMAL) {
|
|
this._finishFAYT(event);
|
|
}
|
|
break;
|
|
case KeyEvent.DOM_VK_PAGE_UP:
|
|
case KeyEvent.DOM_VK_PAGE_DOWN:
|
|
if (
|
|
!event.altKey &&
|
|
!event.ctrlKey &&
|
|
!event.metaKey &&
|
|
!event.shiftKey
|
|
) {
|
|
this.browser.finder.keyPress(event);
|
|
event.preventDefault();
|
|
}
|
|
break;
|
|
case KeyEvent.DOM_VK_UP:
|
|
case KeyEvent.DOM_VK_DOWN:
|
|
this.browser.finder.keyPress(event);
|
|
event.preventDefault();
|
|
break;
|
|
}
|
|
});
|
|
|
|
this._findField.addEventListener("blur", event => {
|
|
// Note: This code used to remove the selection
|
|
// if it matched an editable.
|
|
this.browser.finder.enableSelection();
|
|
});
|
|
|
|
this._findField.addEventListener("focus", event => {
|
|
this._updateBrowserWithState();
|
|
});
|
|
|
|
this._findField.addEventListener("compositionstart", event => {
|
|
// Don't close the find toolbar while IME is composing.
|
|
let findbar = this;
|
|
findbar._isIMEComposing = true;
|
|
if (findbar._quickFindTimeout) {
|
|
clearTimeout(findbar._quickFindTimeout);
|
|
findbar._quickFindTimeout = null;
|
|
findbar._updateBrowserWithState();
|
|
}
|
|
});
|
|
|
|
this._findField.addEventListener("compositionend", event => {
|
|
this._isIMEComposing = false;
|
|
if (this.findMode != this.FIND_NORMAL) {
|
|
this._setFindCloseTimeout();
|
|
}
|
|
});
|
|
|
|
this._findField.addEventListener("dragover", event => {
|
|
if (event.dataTransfer.types.includes("text/plain")) {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
this._findField.addEventListener("drop", event => {
|
|
let value = event.dataTransfer.getData("text/plain");
|
|
this._findField.value = value;
|
|
this._find(value);
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
|
|
set findMode(val) {
|
|
this._findMode = val;
|
|
this._updateBrowserWithState();
|
|
return val;
|
|
}
|
|
|
|
get findMode() {
|
|
return this._findMode;
|
|
}
|
|
|
|
set prefillWithSelection(val) {
|
|
this.setAttribute("prefillwithselection", val);
|
|
return val;
|
|
}
|
|
|
|
get prefillWithSelection() {
|
|
return this.getAttribute("prefillwithselection") != "false";
|
|
}
|
|
|
|
get hasTransactions() {
|
|
if (this._findField.value) {
|
|
return true;
|
|
}
|
|
|
|
// Watch out for lazy editor init
|
|
if (this._findField.editor) {
|
|
let tm = this._findField.editor.transactionManager;
|
|
return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
set browser(val) {
|
|
function setFindbarInActor(browser, findbar) {
|
|
if (!browser.frameLoader) {
|
|
return;
|
|
}
|
|
|
|
let windowGlobal = browser.browsingContext.currentWindowGlobal;
|
|
if (windowGlobal) {
|
|
let findbarParent = windowGlobal.getActor("FindBar");
|
|
if (findbarParent) {
|
|
findbarParent.setFindbar(browser, findbar);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._browser) {
|
|
setFindbarInActor(this._browser, null);
|
|
|
|
let finder = this._browser.finder;
|
|
if (finder) {
|
|
finder.removeResultListener(this);
|
|
}
|
|
}
|
|
|
|
this._browser = val;
|
|
if (this._browser) {
|
|
// Need to do this to ensure the correct initial state.
|
|
this._updateBrowserWithState();
|
|
|
|
setFindbarInActor(this._browser, this);
|
|
|
|
this._browser.finder.addResultListener(this);
|
|
|
|
this._findField.value = this._browser._lastSearchString;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
get browser() {
|
|
if (!this._browser) {
|
|
this._browser = document.getElementById(this.getAttribute("browserid"));
|
|
}
|
|
return this._browser;
|
|
}
|
|
|
|
get strBundle() {
|
|
if (!this._strBundle) {
|
|
this._strBundle = Services.strings.createBundle(
|
|
"chrome://global/locale/findbar.properties"
|
|
);
|
|
}
|
|
return this._strBundle;
|
|
}
|
|
|
|
observe(subject, topic, prefName) {
|
|
if (topic == TOPIC_MAC_APP_ACTIVATE) {
|
|
this._onAppActivateMac();
|
|
return;
|
|
}
|
|
|
|
if (topic != "nsPref:changed") {
|
|
return;
|
|
}
|
|
|
|
let prefsvc = Services.prefs;
|
|
|
|
switch (prefName) {
|
|
case "accessibility.typeaheadfind":
|
|
this._findAsYouType = prefsvc.getBoolPref(prefName);
|
|
break;
|
|
case "accessibility.typeaheadfind.manual":
|
|
this._manualFAYT = prefsvc.getBoolPref(prefName);
|
|
break;
|
|
case "accessibility.typeaheadfind.timeout":
|
|
this.quickFindTimeoutLength = prefsvc.getIntPref(prefName);
|
|
break;
|
|
case "accessibility.typeaheadfind.linksonly":
|
|
this._typeAheadLinksOnly = prefsvc.getBoolPref(prefName);
|
|
break;
|
|
case "accessibility.typeaheadfind.casesensitive":
|
|
this._setCaseSensitivity(prefsvc.getIntPref(prefName));
|
|
break;
|
|
case "findbar.entireword":
|
|
this._entireWord = prefsvc.getBoolPref(prefName);
|
|
this.toggleEntireWord(this._entireWord, true);
|
|
break;
|
|
case "findbar.highlightAll":
|
|
this.toggleHighlight(prefsvc.getBoolPref(prefName), true);
|
|
break;
|
|
case "findbar.matchdiacritics":
|
|
this._setDiacriticMatching(prefsvc.getIntPref(prefName));
|
|
break;
|
|
case "findbar.modalHighlight":
|
|
this._useModalHighlight = prefsvc.getBoolPref(prefName);
|
|
if (this.browser.finder) {
|
|
this.browser.finder.onModalHighlightChange(this._useModalHighlight);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
getElement(aAnonymousID) {
|
|
return this.querySelector(`[anonid=${aAnonymousID}]`);
|
|
}
|
|
|
|
/**
|
|
* This is necessary because the destructor isn't called when we are removed
|
|
* from a document that is not destroyed. This needs to be explicitly called
|
|
* in this case.
|
|
*/
|
|
destroy() {
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
window.removeEventListener("unload", this.destroy);
|
|
this._destroyed = true;
|
|
|
|
if (this.browser && this.browser.finder) {
|
|
this.browser.finder.destroy();
|
|
}
|
|
|
|
// Invoking this setter also removes the message listeners.
|
|
this.browser = null;
|
|
|
|
let prefsvc = Services.prefs;
|
|
let observe = this._observe;
|
|
for (let [, prefName] of PREFS_TO_OBSERVE_ALL) {
|
|
prefsvc.removeObserver(prefName, observe);
|
|
}
|
|
|
|
Services.obs.removeObserver(observe, TOPIC_MAC_APP_ACTIVATE);
|
|
|
|
// Clear all timers that might still be running.
|
|
this._cancelTimers();
|
|
}
|
|
|
|
_cancelTimers() {
|
|
if (this._flashFindBarTimeout) {
|
|
clearInterval(this._flashFindBarTimeout);
|
|
this._flashFindBarTimeout = null;
|
|
}
|
|
if (this._quickFindTimeout) {
|
|
clearTimeout(this._quickFindTimeout);
|
|
this._quickFindTimeout = null;
|
|
}
|
|
if (this._findResetTimeout) {
|
|
clearTimeout(this._findResetTimeout);
|
|
this._findResetTimeout = null;
|
|
}
|
|
}
|
|
|
|
_setFindCloseTimeout() {
|
|
if (this._quickFindTimeout) {
|
|
clearTimeout(this._quickFindTimeout);
|
|
}
|
|
|
|
// Don't close the find toolbar while IME is composing OR when the
|
|
// findbar is already hidden.
|
|
if (this._isIMEComposing || this.hidden) {
|
|
this._quickFindTimeout = null;
|
|
this._updateBrowserWithState();
|
|
return;
|
|
}
|
|
|
|
if (this.quickFindTimeoutLength < 1) {
|
|
this._quickFindTimeout = null;
|
|
} else {
|
|
this._quickFindTimeout = setTimeout(() => {
|
|
if (this.findMode != this.FIND_NORMAL) {
|
|
this.close();
|
|
}
|
|
this._quickFindTimeout = null;
|
|
}, this.quickFindTimeoutLength);
|
|
}
|
|
this._updateBrowserWithState();
|
|
}
|
|
|
|
/**
|
|
* Updates the search match count after each find operation on a new string.
|
|
*/
|
|
_updateMatchesCount() {
|
|
if (!this._dispatchFindEvent("matchescount")) {
|
|
return;
|
|
}
|
|
|
|
this.browser.finder.requestMatchesCount(
|
|
this._findField.value,
|
|
this.findMode == this.FIND_LINKS
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Turns highlighting of all occurrences on or off.
|
|
*
|
|
* @param {Boolean} highlight Whether to turn the highlight on or off.
|
|
* @param {Boolean} fromPrefObserver Whether the callee is the pref
|
|
* observer, which means we should not set
|
|
* the same pref again.
|
|
*/
|
|
toggleHighlight(highlight, fromPrefObserver) {
|
|
if (highlight === this._highlightAll) {
|
|
return;
|
|
}
|
|
|
|
this.browser.finder.onHighlightAllChange(highlight);
|
|
|
|
this._setHighlightAll(highlight, fromPrefObserver);
|
|
|
|
if (!this._dispatchFindEvent("highlightallchange")) {
|
|
return;
|
|
}
|
|
|
|
let word = this._findField.value;
|
|
// Bug 429723. Don't attempt to highlight ""
|
|
if (highlight && !word) {
|
|
return;
|
|
}
|
|
|
|
this.browser.finder.highlight(
|
|
highlight,
|
|
word,
|
|
this.findMode == this.FIND_LINKS
|
|
);
|
|
|
|
// Update the matches count
|
|
this._updateMatchesCount(Ci.nsITypeAheadFind.FIND_FOUND);
|
|
}
|
|
|
|
/**
|
|
* Updates the highlight-all mode of the findbar and its UI.
|
|
*
|
|
* @param {Boolean} highlight Whether to turn the highlight on or off.
|
|
* @param {Boolean} fromPrefObserver Whether the callee is the pref
|
|
* observer, which means we should not set
|
|
* the same pref again.
|
|
*/
|
|
_setHighlightAll(highlight, fromPrefObserver) {
|
|
if (typeof highlight != "boolean") {
|
|
highlight = this._highlightAll;
|
|
}
|
|
if (highlight !== this._highlightAll) {
|
|
this._highlightAll = highlight;
|
|
if (!fromPrefObserver) {
|
|
Services.prefs.setBoolPref("findbar.highlightAll", highlight);
|
|
}
|
|
}
|
|
let checkbox = this.getElement("highlight");
|
|
checkbox.checked = this._highlightAll;
|
|
}
|
|
|
|
/**
|
|
* Updates the case-sensitivity mode of the findbar and its UI.
|
|
*
|
|
* @param {String} [str] The string for which case sensitivity might be
|
|
* turned on. This only used when case-sensitivity is
|
|
* in auto mode, see `_shouldBeCaseSensitive`. The
|
|
* default value for this parameter is the find-field
|
|
* value.
|
|
* @see _shouldBeCaseSensitive
|
|
*/
|
|
_updateCaseSensitivity(str) {
|
|
let val = str || this._findField.value;
|
|
|
|
let caseSensitive = this._shouldBeCaseSensitive(val);
|
|
let checkbox = this.getElement("find-case-sensitive");
|
|
let statusLabel = this.getElement("match-case-status");
|
|
checkbox.checked = caseSensitive;
|
|
|
|
statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";
|
|
|
|
// Show the checkbox on the full Find bar in non-auto mode.
|
|
// Show the label in all other cases.
|
|
let hideCheckbox =
|
|
this.findMode != this.FIND_NORMAL ||
|
|
(this._typeAheadCaseSensitive != 0 &&
|
|
this._typeAheadCaseSensitive != 1);
|
|
checkbox.hidden = hideCheckbox;
|
|
statusLabel.hidden = !hideCheckbox;
|
|
|
|
this.browser.finder.caseSensitive = caseSensitive;
|
|
}
|
|
|
|
/**
|
|
* Sets the findbar case-sensitivity mode.
|
|
*
|
|
* @param {Number} caseSensitivity 0 - case insensitive,
|
|
* 1 - case sensitive,
|
|
* 2 - auto = case sensitive if the matching
|
|
* string contains upper case letters.
|
|
* @see _shouldBeCaseSensitive
|
|
*/
|
|
_setCaseSensitivity(caseSensitivity) {
|
|
this._typeAheadCaseSensitive = caseSensitivity;
|
|
this._updateCaseSensitivity();
|
|
this._findFailedString = null;
|
|
this._find();
|
|
|
|
this._dispatchFindEvent("casesensitivitychange");
|
|
}
|
|
|
|
/**
|
|
* Updates the diacritic-matching mode of the findbar and its UI.
|
|
*
|
|
* @param {String} [str] The string for which diacritic matching might be
|
|
* turned on. This is only used when diacritic
|
|
* matching is in auto mode, see
|
|
* `_shouldMatchDiacritics`. The default value for
|
|
* this parameter is the find-field value.
|
|
* @see _shouldMatchDiacritics.
|
|
*/
|
|
_updateDiacriticMatching(str) {
|
|
let val = str || this._findField.value;
|
|
|
|
let matchDiacritics = this._shouldMatchDiacritics(val);
|
|
let checkbox = this.getElement("find-match-diacritics");
|
|
let statusLabel = this.getElement("match-diacritics-status");
|
|
checkbox.checked = matchDiacritics;
|
|
|
|
statusLabel.value = matchDiacritics ? this._matchDiacriticsStr : "";
|
|
|
|
// Show the checkbox on the full Find bar in non-auto mode.
|
|
// Show the label in all other cases.
|
|
let hideCheckbox =
|
|
this.findMode != this.FIND_NORMAL ||
|
|
(this._matchDiacritics != 0 && this._matchDiacritics != 1);
|
|
checkbox.hidden = hideCheckbox;
|
|
statusLabel.hidden = !hideCheckbox;
|
|
|
|
this.browser.finder.matchDiacritics = matchDiacritics;
|
|
}
|
|
|
|
/**
|
|
* Sets the findbar diacritic-matching mode
|
|
* @param {Number} diacriticMatching 0 - ignore diacritics,
|
|
* 1 - match diacritics,
|
|
* 2 - auto = match diacritics if the
|
|
* matching string contains
|
|
* diacritics.
|
|
* @see _shouldMatchDiacritics
|
|
*/
|
|
_setDiacriticMatching(diacriticMatching) {
|
|
this._matchDiacritics = diacriticMatching;
|
|
this._updateDiacriticMatching();
|
|
this._findFailedString = null;
|
|
this._find();
|
|
|
|
this._dispatchFindEvent("diacriticmatchingchange");
|
|
}
|
|
|
|
/**
|
|
* Updates the entire-word mode of the findbar and its UI.
|
|
*/
|
|
_setEntireWord() {
|
|
let entireWord = this._entireWord;
|
|
let checkbox = this.getElement("find-entire-word");
|
|
let statusLabel = this.getElement("entire-word-status");
|
|
checkbox.checked = entireWord;
|
|
|
|
statusLabel.value = entireWord ? this._entireWordStr : "";
|
|
|
|
// Show the checkbox on the full Find bar in non-auto mode.
|
|
// Show the label in all other cases.
|
|
let hideCheckbox = this.findMode != this.FIND_NORMAL;
|
|
checkbox.hidden = hideCheckbox;
|
|
statusLabel.hidden = !hideCheckbox;
|
|
|
|
this.browser.finder.entireWord = entireWord;
|
|
}
|
|
|
|
/**
|
|
* Sets the findbar entire-word mode.
|
|
*
|
|
* @param {Boolean} entireWord Whether or not entire-word mode should be
|
|
* turned on.
|
|
*/
|
|
toggleEntireWord(entireWord, fromPrefObserver) {
|
|
if (!fromPrefObserver) {
|
|
// Just set the pref; our observer will change the find bar behavior.
|
|
Services.prefs.setBoolPref("findbar.entireword", entireWord);
|
|
return;
|
|
}
|
|
|
|
this._findFailedString = null;
|
|
this._find();
|
|
}
|
|
|
|
/**
|
|
* Opens and displays the find bar.
|
|
*
|
|
* @param {Number} mode The find mode to be used, which is either
|
|
* FIND_NORMAL, FIND_TYPEAHEAD or FIND_LINKS. If not
|
|
* passed, we revert to the last find mode if any or
|
|
* FIND_NORMAL.
|
|
* @return {Boolean} `true` if the find bar wasn't previously open, `false`
|
|
* otherwise.
|
|
*/
|
|
open(mode) {
|
|
if (mode != undefined) {
|
|
this.findMode = mode;
|
|
}
|
|
|
|
if (!this._notFoundStr) {
|
|
var bundle = this.strBundle;
|
|
this._notFoundStr = bundle.GetStringFromName("NotFound");
|
|
this._wrappedToTopStr = bundle.GetStringFromName("WrappedToTop");
|
|
this._wrappedToBottomStr = bundle.GetStringFromName("WrappedToBottom");
|
|
this._normalFindStr = bundle.GetStringFromName("NormalFind");
|
|
this._fastFindStr = bundle.GetStringFromName("FastFind");
|
|
this._fastFindLinksStr = bundle.GetStringFromName("FastFindLinks");
|
|
this._caseSensitiveStr = bundle.GetStringFromName("CaseSensitive");
|
|
this._matchDiacriticsStr = bundle.GetStringFromName("MatchDiacritics");
|
|
this._entireWordStr = bundle.GetStringFromName("EntireWord");
|
|
}
|
|
|
|
this._findFailedString = null;
|
|
|
|
this._updateFindUI();
|
|
if (this.hidden) {
|
|
this.removeAttribute("noanim");
|
|
this.hidden = false;
|
|
|
|
this._updateStatusUI(Ci.nsITypeAheadFind.FIND_FOUND);
|
|
|
|
let event = document.createEvent("Events");
|
|
event.initEvent("findbaropen", true, false);
|
|
this.dispatchEvent(event);
|
|
|
|
this.browser.finder.onFindbarOpen();
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Closes the findbar.
|
|
*
|
|
* @param {Boolean} [noAnim] Whether to disable to closing animation. Used
|
|
* to close instantly and synchronously, when
|
|
* other operations depend on this state.
|
|
*/
|
|
close(noAnim) {
|
|
if (this.hidden) {
|
|
return;
|
|
}
|
|
|
|
if (noAnim) {
|
|
this.setAttribute("noanim", true);
|
|
}
|
|
this.hidden = true;
|
|
|
|
let event = document.createEvent("Events");
|
|
event.initEvent("findbarclose", true, false);
|
|
this.dispatchEvent(event);
|
|
|
|
// 'focusContent()' iterates over all listeners in the chrome
|
|
// process, so we need to call it from here.
|
|
this.browser.finder.focusContent();
|
|
this.browser.finder.onFindbarClose();
|
|
|
|
this._cancelTimers();
|
|
this._updateBrowserWithState();
|
|
|
|
this._findFailedString = null;
|
|
}
|
|
|
|
clear() {
|
|
this.browser.finder.removeSelection();
|
|
// Clear value and undo/redo transactions
|
|
this._findField.value = "";
|
|
if (this._findField.editor) {
|
|
this._findField.editor.transactionManager.clear();
|
|
}
|
|
this.toggleHighlight(false);
|
|
this._updateStatusUI();
|
|
this._enableFindButtons(false);
|
|
}
|
|
|
|
_dispatchKeypressEvent(target, fakeEvent) {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
// The event information comes from the child process. If we need more
|
|
// properties/information here, change the list of sent properties in
|
|
// browser-content.js.
|
|
let event = new target.ownerGlobal.KeyboardEvent(
|
|
fakeEvent.type,
|
|
fakeEvent
|
|
);
|
|
target.dispatchEvent(event);
|
|
}
|
|
|
|
_updateStatusUIBar(foundURL) {
|
|
if (!this._xulBrowserWindow) {
|
|
try {
|
|
this._xulBrowserWindow = window.docShell.treeOwner
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIAppWindow).XULBrowserWindow;
|
|
} catch (ex) {}
|
|
if (!this._xulBrowserWindow) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Call this has the same effect like hovering over link,
|
|
// the browser shows the URL as a tooltip.
|
|
this._xulBrowserWindow.setOverLink(foundURL || "");
|
|
return true;
|
|
}
|
|
|
|
_finishFAYT(keypressEvent) {
|
|
this.browser.finder.focusContent();
|
|
|
|
if (keypressEvent) {
|
|
keypressEvent.preventDefault();
|
|
}
|
|
|
|
this.browser.finder.keyPress(keypressEvent);
|
|
|
|
this.close();
|
|
return true;
|
|
}
|
|
|
|
_shouldBeCaseSensitive(str) {
|
|
if (this._typeAheadCaseSensitive == 0) {
|
|
return false;
|
|
}
|
|
if (this._typeAheadCaseSensitive == 1) {
|
|
return true;
|
|
}
|
|
|
|
return str != str.toLowerCase();
|
|
}
|
|
|
|
_shouldMatchDiacritics(str) {
|
|
if (this._matchDiacritics == 0) {
|
|
return false;
|
|
}
|
|
if (this._matchDiacritics == 1) {
|
|
return true;
|
|
}
|
|
|
|
return str != str.normalize("NFD");
|
|
}
|
|
|
|
onMouseUp() {
|
|
if (!this.hidden && this.findMode != this.FIND_NORMAL) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We get a fake event object through an IPC message when FAYT is being used
|
|
* from within the browser. We then stuff that input in the find bar here.
|
|
*
|
|
* @param {Object} fakeEvent Event object that looks and quacks like a
|
|
* native DOM KeyPress event.
|
|
*/
|
|
_onBrowserKeypress(fakeEvent) {
|
|
const FAYT_LINKS_KEY = "'";
|
|
const FAYT_TEXT_KEY = "/";
|
|
|
|
if (!this.hidden && this._findField == document.activeElement) {
|
|
this._dispatchKeypressEvent(this._findField, fakeEvent);
|
|
return;
|
|
}
|
|
|
|
if (this.findMode != this.FIND_NORMAL && this._quickFindTimeout) {
|
|
this._findField.select();
|
|
this._findField.focus();
|
|
this._dispatchKeypressEvent(this._findField, fakeEvent);
|
|
return;
|
|
}
|
|
|
|
let key = fakeEvent.charCode
|
|
? String.fromCharCode(fakeEvent.charCode)
|
|
: null;
|
|
let manualstartFAYT =
|
|
(key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY) && this._manualFAYT;
|
|
let autostartFAYT =
|
|
!manualstartFAYT && this._findAsYouType && key && key != " ";
|
|
if (manualstartFAYT || autostartFAYT) {
|
|
let mode =
|
|
key == FAYT_LINKS_KEY || (autostartFAYT && this._typeAheadLinksOnly)
|
|
? this.FIND_LINKS
|
|
: this.FIND_TYPEAHEAD;
|
|
|
|
// Clear bar first, so that when openFindBar() calls setCaseSensitivity()
|
|
// it doesn't get confused by a lingering value
|
|
this._findField.value = "";
|
|
|
|
this.open(mode);
|
|
this._setFindCloseTimeout();
|
|
this._findField.select();
|
|
this._findField.focus();
|
|
|
|
if (autostartFAYT) {
|
|
this._dispatchKeypressEvent(this._findField, fakeEvent);
|
|
} else {
|
|
this._updateStatusUI(Ci.nsITypeAheadFind.FIND_FOUND);
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateBrowserWithState() {
|
|
if (this._browser) {
|
|
this._browser.sendMessageToActor(
|
|
"Findbar:UpdateState",
|
|
{
|
|
findMode: this.findMode,
|
|
isOpenAndFocused:
|
|
!this.hidden && document.activeElement == this._findField,
|
|
hasQuickFindTimeout: !!this._quickFindTimeout,
|
|
},
|
|
"FindBar",
|
|
"all"
|
|
);
|
|
}
|
|
}
|
|
|
|
_enableFindButtons(aEnable) {
|
|
this.getElement("find-next").disabled = this.getElement(
|
|
"find-previous"
|
|
).disabled = !aEnable;
|
|
}
|
|
|
|
/**
|
|
* Determines whether minimalist or general-purpose search UI is to be
|
|
* displayed when the find bar is activated.
|
|
*/
|
|
_updateFindUI() {
|
|
let showMinimalUI = this.findMode != this.FIND_NORMAL;
|
|
|
|
let nodes = this.getElement("findbar-container").children;
|
|
let wrapper = this.getElement("findbar-textbox-wrapper");
|
|
let foundMatches = this._foundMatches;
|
|
for (let node of nodes) {
|
|
if (node == wrapper || node == foundMatches) {
|
|
continue;
|
|
}
|
|
node.hidden = showMinimalUI;
|
|
}
|
|
this.getElement("find-next").hidden = this.getElement(
|
|
"find-previous"
|
|
).hidden = showMinimalUI;
|
|
foundMatches.hidden = showMinimalUI || !foundMatches.value;
|
|
this._updateCaseSensitivity();
|
|
this._updateDiacriticMatching();
|
|
this._setEntireWord();
|
|
this._setHighlightAll();
|
|
|
|
if (showMinimalUI) {
|
|
this._findField.classList.add("minimal");
|
|
} else {
|
|
this._findField.classList.remove("minimal");
|
|
}
|
|
|
|
if (this.findMode == this.FIND_TYPEAHEAD) {
|
|
this._findField.placeholder = this._fastFindStr;
|
|
} else if (this.findMode == this.FIND_LINKS) {
|
|
this._findField.placeholder = this._fastFindLinksStr;
|
|
} else {
|
|
this._findField.placeholder = this._normalFindStr;
|
|
}
|
|
}
|
|
|
|
_find(value) {
|
|
if (!this._dispatchFindEvent("")) {
|
|
return;
|
|
}
|
|
|
|
let val = value || this._findField.value;
|
|
|
|
// We have to carry around an explicit version of this, because
|
|
// finder.searchString doesn't update on failed searches.
|
|
this.browser._lastSearchString = val;
|
|
|
|
// Only search on input if we don't have a last-failed string,
|
|
// or if the current search string doesn't start with it.
|
|
// In entire-word mode we always attemp a find; since sequential matching
|
|
// is not guaranteed, the first character typed may not be a word (no
|
|
// match), but the with the second character it may well be a word,
|
|
// thus a match.
|
|
if (
|
|
!this._findFailedString ||
|
|
!val.startsWith(this._findFailedString) ||
|
|
this._entireWord
|
|
) {
|
|
// Getting here means the user commanded a find op. Make sure any
|
|
// initial prefilling is ignored if it hasn't happened yet.
|
|
if (this._startFindDeferred) {
|
|
this._startFindDeferred.resolve();
|
|
this._startFindDeferred = null;
|
|
}
|
|
|
|
this._enableFindButtons(val);
|
|
this._updateCaseSensitivity(val);
|
|
this._updateDiacriticMatching(val);
|
|
this._setEntireWord();
|
|
|
|
this.browser.finder.fastFind(
|
|
val,
|
|
this.findMode == this.FIND_LINKS,
|
|
this.findMode != this.FIND_NORMAL
|
|
);
|
|
}
|
|
|
|
if (this.findMode != this.FIND_NORMAL) {
|
|
this._setFindCloseTimeout();
|
|
}
|
|
|
|
if (this._findResetTimeout != -1) {
|
|
clearTimeout(this._findResetTimeout);
|
|
}
|
|
|
|
// allow a search to happen on input again after a second has expired
|
|
// since the previous input, to allow for dynamic content and/ or page
|
|
// loading.
|
|
this._findResetTimeout = setTimeout(() => {
|
|
this._findFailedString = null;
|
|
this._findResetTimeout = -1;
|
|
}, 1000);
|
|
}
|
|
|
|
_flash() {
|
|
if (this._flashFindBarCount === undefined) {
|
|
this._flashFindBarCount = this._initialFlashFindBarCount;
|
|
}
|
|
|
|
if (this._flashFindBarCount-- == 0) {
|
|
clearInterval(this._flashFindBarTimeout);
|
|
this._findField.removeAttribute("flash");
|
|
this._flashFindBarCount = 6;
|
|
return;
|
|
}
|
|
|
|
this._findField.setAttribute(
|
|
"flash",
|
|
this._flashFindBarCount % 2 == 0 ? "false" : "true"
|
|
);
|
|
}
|
|
|
|
_findAgain(findPrevious) {
|
|
this.browser.finder.findAgain(
|
|
this._findField.value,
|
|
findPrevious,
|
|
this.findMode == this.FIND_LINKS,
|
|
this.findMode != this.FIND_NORMAL
|
|
);
|
|
}
|
|
|
|
_updateStatusUI(res, findPrevious) {
|
|
switch (res) {
|
|
case Ci.nsITypeAheadFind.FIND_WRAPPED:
|
|
this._findStatusIcon.setAttribute("status", "wrapped");
|
|
this._findStatusDesc.textContent = findPrevious
|
|
? this._wrappedToBottomStr
|
|
: this._wrappedToTopStr;
|
|
this._findField.removeAttribute("status");
|
|
break;
|
|
case Ci.nsITypeAheadFind.FIND_NOTFOUND:
|
|
this._findStatusIcon.setAttribute("status", "notfound");
|
|
this._findStatusDesc.textContent = this._notFoundStr;
|
|
this._findField.setAttribute("status", "notfound");
|
|
break;
|
|
case Ci.nsITypeAheadFind.FIND_PENDING:
|
|
this._findStatusIcon.setAttribute("status", "pending");
|
|
this._findStatusDesc.textContent = "";
|
|
this._findField.removeAttribute("status");
|
|
break;
|
|
case Ci.nsITypeAheadFind.FIND_FOUND:
|
|
default:
|
|
this._findStatusIcon.removeAttribute("status");
|
|
this._findStatusDesc.textContent = "";
|
|
this._findField.removeAttribute("status");
|
|
break;
|
|
}
|
|
}
|
|
|
|
updateControlState(result, findPrevious) {
|
|
this._updateStatusUI(result, findPrevious);
|
|
this._enableFindButtons(
|
|
result !== Ci.nsITypeAheadFind.FIND_NOTFOUND && !!this._findField.value
|
|
);
|
|
}
|
|
|
|
_dispatchFindEvent(type, findPrevious) {
|
|
let event = document.createEvent("CustomEvent");
|
|
event.initCustomEvent("find" + type, true, true, {
|
|
query: this._findField.value,
|
|
caseSensitive: !!this._typeAheadCaseSensitive,
|
|
matchDiacritics: !!this._matchDiacritics,
|
|
entireWord: this._entireWord,
|
|
highlightAll: this._highlightAll,
|
|
findPrevious,
|
|
});
|
|
return this.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Opens the findbar, focuses the findfield and selects its contents.
|
|
* Also flashes the findbar the first time it's used.
|
|
*
|
|
* @param {Number} mode The find mode to be used, which is either
|
|
* FIND_NORMAL, FIND_TYPEAHEAD or FIND_LINKS. If not
|
|
* passed, we revert to the last find mode if any or
|
|
* FIND_NORMAL.
|
|
* @return {Promise} A promise that will be resolved when the findbar is
|
|
* fully opened.
|
|
*/
|
|
startFind(mode) {
|
|
let prefsvc = Services.prefs;
|
|
let userWantsPrefill = true;
|
|
this.open(mode);
|
|
|
|
if (this._flashFindBar) {
|
|
this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
|
|
prefsvc.setIntPref(
|
|
"accessibility.typeaheadfind.flashBar",
|
|
--this._flashFindBar
|
|
);
|
|
}
|
|
|
|
let { PromiseUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/PromiseUtils.jsm"
|
|
);
|
|
this._startFindDeferred = PromiseUtils.defer();
|
|
let startFindPromise = this._startFindDeferred.promise;
|
|
|
|
if (this.prefillWithSelection) {
|
|
userWantsPrefill = prefsvc.getBoolPref(
|
|
"accessibility.typeaheadfind.prefillwithselection"
|
|
);
|
|
}
|
|
|
|
if (this.prefillWithSelection && userWantsPrefill) {
|
|
// NB: We have to focus this._findField here so tests that send
|
|
// key events can open and close the find bar synchronously.
|
|
this._findField.focus();
|
|
|
|
// (e10s) since we focus lets also select it, otherwise that would
|
|
// only happen in this.onCurrentSelection and, because it is async,
|
|
// there's a chance keypresses could come inbetween, leading to
|
|
// jumbled up queries.
|
|
this._findField.select();
|
|
|
|
this.browser.finder.getInitialSelection();
|
|
return startFindPromise;
|
|
}
|
|
|
|
// If userWantsPrefill is false but prefillWithSelection is true,
|
|
// then we might need to check the selection clipboard. Call
|
|
// onCurrentSelection to do so.
|
|
// Note: this.onCurrentSelection clears this._startFindDeferred.
|
|
this.onCurrentSelection("", true);
|
|
return startFindPromise;
|
|
}
|
|
|
|
/**
|
|
* Convenient alias to startFind(gFindBar.FIND_NORMAL);
|
|
*
|
|
* You should generally map the window's find command to this method.
|
|
* e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
|
|
*/
|
|
onFindCommand() {
|
|
return this.startFind(this.FIND_NORMAL);
|
|
}
|
|
|
|
/**
|
|
* Stub for find-next and find-previous commands.
|
|
*
|
|
* @param {Boolean} findPrevious `true` for find-previous, `false`
|
|
* otherwise.
|
|
*/
|
|
onFindAgainCommand(findPrevious) {
|
|
let findString =
|
|
this._browser.finder.searchString || this._findField.value;
|
|
if (!findString) {
|
|
return this.startFind();
|
|
}
|
|
|
|
// We dispatch the findAgain event here instead of in _findAgain since
|
|
// if there is a find event handler that prevents the default then
|
|
// finder.searchString will never get updated which in turn means
|
|
// there would never be findAgain events because of the logic below.
|
|
if (!this._dispatchFindEvent("again", findPrevious)) {
|
|
return undefined;
|
|
}
|
|
|
|
// user explicitly requested another search, so do it even if we think it'll fail
|
|
this._findFailedString = null;
|
|
|
|
// Ensure the stored SearchString is in sync with what we want to find
|
|
if (this._findField.value != this._browser.finder.searchString) {
|
|
this._find(this._findField.value);
|
|
} else {
|
|
this._findAgain(findPrevious);
|
|
if (this._useModalHighlight) {
|
|
this.open();
|
|
this._findField.focus();
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Fetches the currently selected text and sets that as the text to search
|
|
* next. This is a MacOS specific feature.
|
|
*/
|
|
onFindSelectionCommand() {
|
|
this.browser.finder.setSearchStringToSelection().then(searchInfo => {
|
|
if (searchInfo.selectedText) {
|
|
this._findField.value = searchInfo.selectedText;
|
|
}
|
|
});
|
|
}
|
|
|
|
_onAppActivateMac() {
|
|
const kPref = "accessibility.typeaheadfind.prefillwithselection";
|
|
if (this.prefillWithSelection && Services.prefs.getBoolPref(kPref)) {
|
|
return;
|
|
}
|
|
|
|
let clipboardSearchString = this._browser.finder.clipboardSearchString;
|
|
if (
|
|
clipboardSearchString &&
|
|
this._findField.value != clipboardSearchString &&
|
|
!this._findField._willfullyDeleted
|
|
) {
|
|
this._findField.value = clipboardSearchString;
|
|
this._findField._hadValue = true;
|
|
// Changing the search string makes the previous status invalid, so
|
|
// we better clear it here.
|
|
this._updateStatusUI();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This handles all the result changes for both type-ahead-find and
|
|
* highlighting.
|
|
*
|
|
* @param {Object} data A dictionary that holds the following properties:
|
|
* - {Number} result One of the FIND_* constants
|
|
* indicating the result of a search
|
|
* operation.
|
|
* - {Boolean} findBackwards If the search was done
|
|
* from the bottom to the
|
|
* top. This is used for
|
|
* status messages when
|
|
* reaching "the end of the
|
|
* page".
|
|
* - {String} linkURL When a link matched, then its
|
|
* URL. Always null when not in
|
|
* FIND_LINKS mode.
|
|
*/
|
|
onFindResult(data) {
|
|
if (data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
|
// If an explicit Find Again command fails, re-open the toolbar.
|
|
if (data.storeResult && this.open()) {
|
|
this._findField.select();
|
|
this._findField.focus();
|
|
}
|
|
this._findFailedString = data.searchString;
|
|
} else {
|
|
this._findFailedString = null;
|
|
}
|
|
|
|
this._updateStatusUI(data.result, data.findBackwards);
|
|
this._updateStatusUIBar(data.linkURL);
|
|
|
|
if (this.findMode != this.FIND_NORMAL) {
|
|
this._setFindCloseTimeout();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This handles all the result changes for matches counts.
|
|
*
|
|
* @param {Object} result Result Object, containing the total amount of
|
|
* matches and a vector of the current result.
|
|
* - {Number} total Total count number of matches found.
|
|
* - {Number} limit Current setting of the number of matches
|
|
* to hit to hit the limit.
|
|
* - {Number} current Vector of the current result.
|
|
*/
|
|
onMatchesCountResult(result) {
|
|
if (result.total !== 0) {
|
|
if (result.total == -1) {
|
|
this._foundMatches.value = LazyConstants.PluralForm.get(
|
|
result.limit,
|
|
this.strBundle.GetStringFromName("FoundMatchesCountLimit")
|
|
).replace("#1", result.limit);
|
|
} else {
|
|
this._foundMatches.value = LazyConstants.PluralForm.get(
|
|
result.total,
|
|
this.strBundle.GetStringFromName("FoundMatches")
|
|
)
|
|
.replace("#1", result.current)
|
|
.replace("#2", result.total);
|
|
}
|
|
this._foundMatches.hidden = false;
|
|
} else {
|
|
this._foundMatches.hidden = true;
|
|
this._foundMatches.value = "";
|
|
}
|
|
}
|
|
|
|
onHighlightFinished(result) {
|
|
// Noop.
|
|
}
|
|
|
|
onCurrentSelection(selectionString, isInitialSelection) {
|
|
// Ignore the prefill if the user has already typed in the findbar,
|
|
// it would have been overwritten anyway. See bug 1198465.
|
|
if (isInitialSelection && !this._startFindDeferred) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
AppConstants.platform == "macosx" &&
|
|
isInitialSelection &&
|
|
!selectionString
|
|
) {
|
|
let clipboardSearchString = this.browser.finder.clipboardSearchString;
|
|
if (clipboardSearchString) {
|
|
selectionString = clipboardSearchString;
|
|
}
|
|
}
|
|
|
|
if (selectionString) {
|
|
this._findField.value = selectionString;
|
|
}
|
|
|
|
if (isInitialSelection) {
|
|
this._enableFindButtons(!!this._findField.value);
|
|
this._findField.select();
|
|
this._findField.focus();
|
|
|
|
this._startFindDeferred.resolve();
|
|
this._startFindDeferred = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This handler may cancel a request to focus content by returning |false|
|
|
* explicitly.
|
|
*/
|
|
shouldFocusContent() {
|
|
const fm = Services.focus;
|
|
if (fm.focusedWindow != window) {
|
|
return false;
|
|
}
|
|
|
|
let focusedElement = fm.focusedElement;
|
|
if (!focusedElement) {
|
|
return false;
|
|
}
|
|
|
|
let focusedParent = focusedElement.closest("findbar");
|
|
if (focusedParent != this && focusedParent != this._findField) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
// Empty the DOM. We will rebuild if reconnected.
|
|
while (this.lastChild) {
|
|
this.removeChild(this.lastChild);
|
|
}
|
|
this.destroy();
|
|
}
|
|
}
|
|
|
|
customElements.define("findbar", MozFindbar);
|
|
}
|