--- a/toolkit/components/processsingleton/MainProcessSingleton.js
+++ b/toolkit/components/processsingleton/MainProcessSingleton.js
@@ -79,26 +79,89 @@ MainProcessSingleton.prototype = {
case "document-element-inserted":
// Set up Custom Elements for XUL windows before anything else happens
// in the document. Anything loaded here should be considered part of
// core XUL functionality. Any window-specific elements can be registered
// via <script> tags at the top of individual documents.
const doc = subject;
if (doc.nodePrincipal.isSystemPrincipal &&
doc.contentType == "application/vnd.mozilla.xul+xml") {
+ const win = doc.ownerGlobal;
for (let script of [
"chrome://global/content/elements/stringbundle.js",
- "chrome://global/content/elements/findbar.js",
]) {
- Services.scriptloader.loadSubScript(script, doc.ownerGlobal);
+ Services.scriptloader.loadSubScript(script, win);
}
+
+ lazyCustomElementDefine({
+ window: win,
+ tagName: "findbar",
+ script: "chrome://global/content/elements/findbar.js",
+ impl: "MozFindbar"
+ });
}
break;
case "xpcom-shutdown":
Services.mm.removeMessageListener("Search:AddEngine", this.addSearchEngine);
Services.obs.removeObserver(this, "document-element-inserted");
break;
}
},
};
+/**
+ * Define a Custom Element where the implementation lives in another
+ * script file. This is useful when an element isn't typically opened,
+ * to avoid parsing the script until it's actually created.
+ */
+function lazyCustomElementDefine({ window, tagName, script, impl }) {
+ // This is an assign function that copies full descriptors
+ function completeAssign(target, source) {
+ let descriptors = Object.keys(source).reduce((descriptors, key) => {
+ descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
+ return descriptors;
+ }, {});
+ // by default, Object.assign copies enumerable Symbols too
+ Object.getOwnPropertySymbols(source).forEach(sym => {
+ let descriptor = Object.getOwnPropertyDescriptor(source, sym);
+ if (descriptor.enumerable) {
+ descriptors[sym] = descriptor;
+ }
+ });
+ Object.defineProperties(target, descriptors);
+ return target;
+ }
+
+ window.customElements.define(tagName, class extends window.XULElement {
+ constructor() {
+ super();
+ // If this is the first time the Custom Element is seen, then load the
+ // script containing its implementation.
+ if (!window.customElements[impl]) {
+ Services.scriptloader.loadSubScript(script, window);
+ if (!window.customElements[impl]) {
+ throw new Error(`{impl} not found in window after loading ${script}`);
+ }
+ }
+
+ // Assign properties from the implementation onto the instance:
+ completeAssign(this, window.customElements[impl]);
+ }
+ connectedCallback() {
+ if (window.customElements[impl].connectedCallback) {
+ window.customElements[impl].connectedCallback.apply(this, arguments);
+ }
+ }
+ disconnectedCallback() {
+ if (window.customElements[impl].disconnectedCallback) {
+ window.customElements[impl].disconnectedCallback.apply(this, arguments);
+ }
+ }
+ attributeChangedCallback() {
+ if (window.customElements[impl].attributeChangedCallback) {
+ window.customElements[impl].attributeChangedCallback.apply(this, arguments);
+ }
+ }
+ });
+}
+
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MainProcessSingleton]);
--- a/toolkit/content/widgets/findbar.js
+++ b/toolkit/content/widgets/findbar.js
@@ -25,19 +25,18 @@ function parseDOM(str) {
const doc = d.parseFromString(
`<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">${str}</box>`,
"application/xml");
const range = doc.createRange();
range.selectNodeContents(doc.firstChild);
return range.extractContents();
}
-class MozFindbar extends XULElement {
+customElements.MozFindbar = {
connectedCallback() {
-
this.appendChild(parseDOM(`
<hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
<hbox anonid="findbar-textbox-wrapper" align="stretch">
<textbox 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-all" oncommand="toggleHighlight(this.checked);" type="checkbox" />
@@ -291,52 +290,52 @@ class MozFindbar extends XULElement {
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 findMode() {
return this._findMode;
- }
+ },
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) {
if (this._browser) {
if (this._browser.messageManager) {
this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
}
let finder = this._browser.finder;
@@ -350,49 +349,49 @@ class MozFindbar extends XULElement {
this._updateBrowserWithState();
this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
this._browser.messageManager.addMessageListener("Findbar:Mouseup", 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 _prefsvc() {
return Services.prefs;
- }
+ },
get pluralForm() {
if (!this._pluralForm) {
this._pluralForm = ChromeUtils.import(
"resource://gre/modules/PluralForm.jsm", {}).PluralForm;
}
return this._pluralForm;
- }
+ },
get strBundle() {
if (!this._strBundle) {
this._strBundle = Services.strings.createBundle(
"chrome://global/locale/findbar.properties");
}
return this._strBundle;
- }
+ },
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)
@@ -413,32 +412,32 @@ class MozFindbar extends XULElement {
prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
this._observer);
prefsvc.removeObserver("findbar.entireword", this._observer);
prefsvc.removeObserver("findbar.highlightAll", this._observer);
prefsvc.removeObserver("findbar.modalHighlight", this._observer);
// 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) {
@@ -448,30 +447,30 @@ class MozFindbar extends XULElement {
}
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.
* - @param aRes
* - the result of the find operation
*/
_updateMatchesCount() {
if (!this._dispatchFindEvent("matchescount"))
return;
this.browser.finder.requestMatchesCount(this._findField.value,
this._findMode == this.FIND_LINKS);
- }
+ },
/**
* - Turns highlight on or off.
* - @param aHighlight (boolean)
* - Whether to turn the highlight on or off
* - @param aFromPrefObserver (boolean)
* - Whether the callee is the pref observer, which means we should
* - not set the same pref again.
@@ -494,17 +493,17 @@ class MozFindbar extends XULElement {
if (aHighlight && !word)
return;
this.browser.finder.highlight(aHighlight, word,
this._findMode == this.FIND_LINKS);
// Update the matches count
this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
- }
+ },
/**
* - Updates the highlight-all mode of the findbar and its UI.
* - @param aHighlight (boolean)
* - Whether to turn the highlight on or off.
* - @param aFromPrefObserver (boolean)
* - Whether the callee is the pref observer, which means we should
* - not set the same pref again.
@@ -514,17 +513,17 @@ class MozFindbar extends XULElement {
aHighlight = this._highlightAll;
}
if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
}
this._highlightAll = aHighlight;
let checkbox = this.getElement("highlight");
checkbox.checked = this._highlightAll;
- }
+ },
/**
* - Updates the case-sensitivity mode of the findbar and its UI.
* - @param [optional] aString
* - 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.
@@ -543,34 +542,34 @@ class MozFindbar extends XULElement {
// 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 aCaseSensitivity (int)
* - 0 - case insensitive
* - 1 - case sensitive
* - 2 - auto = case sensitive iff match string contains upper case letters
* - @see _shouldBeCaseSensitive
*/
_setCaseSensitivity(aCaseSensitivity) {
this._typeAheadCaseSensitive = aCaseSensitivity;
this._updateCaseSensitivity();
this._findFailedString = null;
this._find();
this._dispatchFindEvent("casesensitivitychange");
- }
+ },
/**
* - 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");
@@ -580,33 +579,33 @@ class MozFindbar extends XULElement {
// 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 aEntireWord (boolean)
* - Whether or not entire-word mode should be turned on.
*/
toggleEntireWord(aEntireWord, aFromPrefObserver) {
if (!aFromPrefObserver) {
// Just set the pref; our observer will change the find bar behavior.
this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
return;
}
this._findFailedString = null;
this._find();
- }
+ },
/**
* - Opens and displays the find bar.
* -
* - @param aMode
* - the find mode to be used, which is either FIND_NORMAL,
* - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
* - find mode if any or FIND_NORMAL.
@@ -648,17 +647,17 @@ class MozFindbar extends XULElement {
event.initEvent("findbaropen", true, false);
this.dispatchEvent(event);
this.browser.finder.onFindbarOpen();
return true;
}
return false;
- }
+ },
/**
* - Closes the findbar.
*/
close(aNoAnim) {
if (this.hidden)
return;
@@ -670,36 +669,36 @@ class MozFindbar extends XULElement {
// 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();
this._findField.reset();
this.toggleHighlight(false);
this._updateStatusUI();
this._enableFindButtons(false);
- }
+ },
_dispatchKeypressEvent(aTarget, fakeEvent) {
if (!aTarget)
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 aTarget.ownerGlobal.KeyboardEvent(fakeEvent.type, fakeEvent);
aTarget.dispatchEvent(event);
- }
+ },
_updateStatusUIBar(aFoundURL) {
if (!this._xulBrowserWindow) {
try {
this._xulBrowserWindow =
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
@@ -711,38 +710,38 @@ class MozFindbar extends XULElement {
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(aFoundURL || "", null);
return true;
- }
+ },
_finishFAYT(aKeypressEvent) {
this.browser.finder.focusContent();
if (aKeypressEvent)
aKeypressEvent.preventDefault();
this.browser.finder.keyPress(aKeypressEvent);
this.close();
return true;
- }
+ },
_shouldBeCaseSensitive(aString) {
if (this._typeAheadCaseSensitive == 0)
return false;
if (this._typeAheadCaseSensitive == 1)
return true;
return aString != aString.toLowerCase();
- }
+ },
/**
* 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.
*/
_onBrowserKeypress(aFakeEvent) {
const FAYT_LINKS_KEY = "'";
const FAYT_TEXT_KEY = "/";
@@ -777,17 +776,17 @@ class MozFindbar extends XULElement {
this._findField.select();
this._findField.focus();
if (autostartFAYT)
this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
else
this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
}
- }
+ },
/**
* See MessageListener
*/
receiveMessage(aMessage) {
if (aMessage.target != this._browser) {
return undefined;
}
@@ -796,32 +795,32 @@ class MozFindbar extends XULElement {
if (!this.hidden && this._findMode != this.FIND_NORMAL)
this.close();
break;
case "Findbar:Keypress":
this._onBrowserKeypress(aMessage.data);
break;
}
return undefined;
- }
+ },
_updateBrowserWithState() {
if (this._browser && this._browser.messageManager) {
this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
findMode: this._findMode,
isOpenAndFocused: !this.hidden && document.activeElement == this._findField.inputField,
hasQuickFindTimeout: !!this._quickFindTimeout,
});
}
- }
+ },
_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;
@@ -846,17 +845,17 @@ class MozFindbar extends XULElement {
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(aValue) {
if (!this._dispatchFindEvent(""))
return;
let val = aValue || this._findField.value;
// We have to carry around an explicit version of this,
@@ -896,39 +895,39 @@ class MozFindbar extends XULElement {
// 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(aFindPrevious) {
this.browser.finder.findAgain(aFindPrevious,
this._findMode == this.FIND_LINKS,
this._findMode != this.FIND_NORMAL);
- }
+ },
_updateStatusUI(res, aFindPrevious) {
switch (res) {
case this.nsITypeAheadFind.FIND_WRAPPED:
this._findStatusIcon.setAttribute("status", "wrapped");
this._findStatusDesc.textContent =
aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
this._findField.removeAttribute("status");
@@ -945,35 +944,35 @@ class MozFindbar extends XULElement {
break;
case this.nsITypeAheadFind.FIND_FOUND:
default:
this._findStatusIcon.removeAttribute("status");
this._findStatusDesc.textContent = "";
this._findField.removeAttribute("status");
break;
}
- }
+ },
updateControlState(aResult, aFindPrevious) {
this._updateStatusUI(aResult, aFindPrevious);
this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND &&
!!this._findField.value);
- }
+ },
_dispatchFindEvent(aType, aFindPrevious) {
let event = document.createEvent("CustomEvent");
event.initCustomEvent("find" + aType, true, true, {
query: this._findField.value,
caseSensitive: !!this._typeAheadCaseSensitive,
entireWord: this._entireWord,
highlightAll: this._highlightAll,
findPrevious: aFindPrevious
});
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 aMode
* - the find mode to be used, which is either FIND_NORMAL,
* - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
* - find mode if any or FIND_NORMAL.
@@ -1014,27 +1013,27 @@ class MozFindbar extends XULElement {
}
// 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 aFindPrevious
* - true for find-previous, false otherwise.
*/
onFindAgainCommand(aFindPrevious) {
let findString = this._browser.finder.searchString || this._findField.value;
@@ -1058,43 +1057,43 @@ class MozFindbar extends XULElement {
this._findAgain(aFindPrevious);
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() {
let searchString = this.browser.finder.setSearchStringToSelection();
if (searchString)
this._findField.value = searchString;
- }
+ },
_onFindFieldFocus() {
let prefsvc = this._prefsvc;
const kPref = "accessibility.typeaheadfind.prefillwithselection";
if (this.prefillWithSelection && prefsvc.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 aResult
* - One of the nsITypeAheadFind.FIND_* constants
* - indicating the result of a search operation.
* - @param aFindBackwards
@@ -1117,17 +1116,17 @@ class MozFindbar extends XULElement {
this._findFailedString = null;
}
this._updateStatusUI(aData.result, aData.findBackwards);
this._updateStatusUIBar(aData.linkURL);
if (this._findMode != this.FIND_NORMAL)
this._setFindCloseTimeout();
- }
+ },
/**
* - This handles all the result changes for matches counts.
* - @param aResult
* - Result Object, containing the total amount of matches and a vector
* - of the current result.
*/
onMatchesCountResult(aResult) {
@@ -1144,21 +1143,21 @@ class MozFindbar extends XULElement {
).replace("#1", aResult.current)
.replace("#2", aResult.total);
}
this._foundMatches.hidden = false;
} else {
this._foundMatches.hidden = true;
this._foundMatches.value = "";
}
- }
+ },
onHighlightFinished(result) {
// Noop.
- }
+ },
onCurrentSelection(aSelectionString, aIsInitialSelection) {
// Ignore the prefill if the user has already typed in the findbar,
// it would have been overwritten anyway. See bug 1198465.
if (aIsInitialSelection && !this._startFindDeferred)
return;
if (/Mac/.test(window.navigator.platform) && aIsInitialSelection && !aSelectionString) {
@@ -1173,17 +1172,17 @@ class MozFindbar extends XULElement {
if (aIsInitialSelection) {
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)
@@ -1193,18 +1192,16 @@ class MozFindbar extends XULElement {
if (!focusedElement)
return false;
let bindingParent = document.getBindingParent(focusedElement);
if (bindingParent != this && bindingParent != this._findField)
return false;
return true;
- }
+ },
disconnectedCallback() {
this.destroy();
- }
-}
-
-customElements.define("findbar", MozFindbar);
+ },
+};
}