forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1399 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1399 lines
		
	
	
	
		
			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 { 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 MozXULElement {
 | |
|     static get markup() {
 | |
|       return `
 | |
|       <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" />
 | |
|           <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>
 | |
|         <checkbox anonid="highlight" class="findbar-highlight tabbable"
 | |
|           data-l10n-id="findbar-highlight-all2" oncommand="toggleHighlight(this.checked);"/>
 | |
|         <checkbox anonid="find-case-sensitive" class="findbar-case-sensitive tabbable"
 | |
|           data-l10n-id="findbar-case-sensitive" oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"/>
 | |
|         <checkbox anonid="find-match-diacritics" class="findbar-match-diacritics tabbable"
 | |
|           data-l10n-id="findbar-match-diacritics" oncommand="_setDiacriticMatching(this.checked ? 1 : 0);"/>
 | |
|         <checkbox anonid="find-entire-word" class="findbar-entire-word tabbable"
 | |
|           data-l10n-id="findbar-entire-word" oncommand="toggleEntireWord(this.checked);"/>
 | |
|         <label anonid="match-case-status" class="findbar-label" />
 | |
|         <label anonid="match-diacritics-status" class="findbar-label" />
 | |
|         <label anonid="entire-word-status" class="findbar-label" />
 | |
|         <label anonid="found-matches" class="findbar-label found-matches" hidden="true" />
 | |
|         <image anonid="find-status-icon" class="find-status-icon" />
 | |
|         <description anonid="find-status" control="findbar-textbox" class="findbar-label findbar-find-status" />
 | |
|       </hbox>
 | |
|       <toolbarbutton anonid="find-closebutton" class="findbar-closebutton tabbable close-icon"
 | |
|         data-l10n-id="findbar-find-button-close" oncommand="close();"/>
 | |
|       `;
 | |
|     }
 | |
| 
 | |
|     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
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     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(this.constructor.fragment);
 | |
|       if (AppConstants.platform == "macosx") {
 | |
|         this.insertBefore(
 | |
|           this.getElement("find-closebutton"),
 | |
|           this.getElement("findbar-container")
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       /**
 | |
|        * 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();
 | |
|     }
 | |
| 
 | |
|     get findMode() {
 | |
|       return this._findMode;
 | |
|     }
 | |
| 
 | |
|     set prefillWithSelection(val) {
 | |
|       this.setAttribute("prefillwithselection", 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);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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;
 | |
| 
 | |
|       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.telemetry.scalarAdd("findbar.highlight_all", 1);
 | |
|           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");
 | |
|       Services.telemetry.scalarAdd("findbar.match_case", 1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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");
 | |
| 
 | |
|       Services.telemetry.scalarAdd("findbar.match_diacritics", 1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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);
 | |
| 
 | |
|         Services.telemetry.scalarAdd("findbar.whole_words", 1);
 | |
|         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) {
 | |
|         Services.telemetry.scalarAdd("findbar.shown", 1);
 | |
|         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.
 | |
|       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._findStatusDesc.setAttribute("status", "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");
 | |
|           this._findStatusDesc.removeAttribute("status");
 | |
|           break;
 | |
|         case Ci.nsITypeAheadFind.FIND_FOUND:
 | |
|         default:
 | |
|           this._findStatusIcon.removeAttribute("status");
 | |
|           this._findStatusDesc.textContent = "";
 | |
|           this._findField.removeAttribute("status");
 | |
|           this._findStatusDesc.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) {
 | |
|         this.browser.finder.getInitialSelection();
 | |
| 
 | |
|         // 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();
 | |
| 
 | |
|         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) {
 | |
|       if (findPrevious) {
 | |
|         Services.telemetry.scalarAdd("findbar.find_prev", 1);
 | |
|       } else {
 | |
|         Services.telemetry.scalarAdd("findbar.find_next", 1);
 | |
|       }
 | |
| 
 | |
|       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);
 | |
| }
 | 
