/** * 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/. */ /* global MozXULElement */ "use strict"; // The autocomplete CE is defined lazily. Create one now to get // autocomplete-input defined, allowing us to inherit from it. if (!customElements.get("autocomplete-input")) { delete document.createXULElement("input", { is: "autocomplete-input" }); } customElements.whenDefined("autocomplete-input").then(() => { const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { GlodaIMSearcher: "resource:///modules/GlodaIMSearcher.sys.mjs", }); ChromeUtils.defineModuleGetter( lazy, "Gloda", "resource:///modules/gloda/GlodaPublic.jsm" ); ChromeUtils.defineModuleGetter( lazy, "GlodaMsgSearcher", "resource:///modules/gloda/GlodaMsgSearcher.jsm" ); ChromeUtils.defineModuleGetter( lazy, "GlodaConstants", "resource:///modules/gloda/GlodaConstants.jsm" ); XPCOMUtils.defineLazyGetter( lazy, "glodaCompleter", () => Cc["@mozilla.org/autocomplete/search;1?name=gloda"].getService( Ci.nsIAutoCompleteSearch ).wrappedJSObject ); /** * The MozGlodaAutocompleteInput widget is used to display the autocomplete search bar. * * @augments {AutocompleteInput} */ class MozGlodaAutocompleteInput extends customElements.get( "autocomplete-input" ) { constructor() { super(); this.addEventListener( "drop", event => { this.searchInputDNDObserver.onDrop(event); }, true ); this.addEventListener("keypress", event => { if (event.keyCode == KeyEvent.DOM_VK_RETURN) { // Trigger the click event if a popup result is currently selected. if (this.popup.richlistbox.selectedIndex != -1) { this.popup.onPopupClick(event); } else { this.doSearch(); } event.preventDefault(); event.stopPropagation(); } if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) { this.clearSearch(); event.preventDefault(); event.stopPropagation(); } }); } connectedCallback() { if (this.hasConnected) { return; } this.hasConnected = true; super.connectedCallback(); this.setAttribute("is", "gloda-autocomplete-input"); // @implements {nsIObserver} this.searchInputDNDObserver = { onDrop: event => { if (event.dataTransfer.types.includes("text/x-moz-address")) { this.focus(); this.value = event.dataTransfer.getData("text/plain"); // XXX for some reason the input field is _cleared_ even though // the search works. this.doSearch(); } event.stopPropagation(); }, }; // @implements {nsIObserver} this.textObserver = { observe: (subject, topic, data) => { try { // Some autocomplete controllers throw NS_ERROR_NOT_IMPLEMENTED. subject.popupElement; } catch (ex) { return; } if ( topic == "autocomplete-did-enter-text" && document.activeElement == this ) { let selectedIndex = this.popup.selectedIndex; let curResult = lazy.glodaCompleter.curResult; if (!curResult) { // autocomplete didn't even finish. return; } let row = curResult.getObjectAt(selectedIndex); if (row == null) { return; } if (row.fullText) { // The autocomplete-did-enter-text notification is synchronously // generated by nsAutoCompleteController which will attempt to // call ClosePopup after we return and then tell the searchbox // about the text entered. Since doSearch may close the current // tab (and thus destroy the XUL document that owns the popup and // the input field), the search box may no longer have its // binding attached when we return and telling it about the // entered text could fail. // To avoid this, we defer the doSearch call to the next turn of // the event loop by using setTimeout. setTimeout(this.doSearch.bind(this), 0); } else if (row.nounDef) { let theQuery = lazy.Gloda.newQuery( lazy.GlodaConstants.NOUN_MESSAGE ); if (row.nounDef.name == "tag") { theQuery = theQuery.tags(row.item); } else if (row.nounDef.name == "identity") { theQuery = theQuery.involves(row.item); } theQuery.orderBy("-date"); document.getElementById("tabmail").openTab("glodaFacet", { query: theQuery, }); } } }, }; let keyLabel = AppConstants.platform == "macosx" ? "keyLabelMac" : "keyLabelNonMac"; let placeholder = this.getAttribute("emptytextbase").replace( "#1", this.getAttribute(keyLabel) ); this.setAttribute("placeholder", placeholder); Services.obs.addObserver( this.textObserver, "autocomplete-did-enter-text" ); // make sure we set our emptytext here from the get-go if (this.hasAttribute("placeholder")) { this.placeholder = this.getAttribute("placeholder"); } } set state(val) { this.value = val.string; } get state() { return { string: this.value }; } doSearch() { if (this.value) { let tabmail = document.getElementById("tabmail"); // If the current tab is a gloda search tab, reset the value // to the initial search value. Otherwise, clear it. This // is the value that is going to be saved with the current // tab when we switch back to it next. let searchString = this.value; if (tabmail.currentTabInfo.mode.name == "glodaFacet") { // We'd rather reuse the existing tab (and somehow do something // smart with any preexisting facet choices, but that's a // bit hard right now, so doing the cheap thing and closing // this tab and starting over. tabmail.closeTab(); } this.value = ""; // clear our value, to avoid persistence let args = { searcher: new lazy.GlodaMsgSearcher(null, searchString), }; if (Services.prefs.getBoolPref("mail.chat.enabled")) { args.IMSearcher = new lazy.GlodaIMSearcher(null, searchString); } tabmail.openTab("glodaFacet", args); } } clearSearch() { this.value = ""; } disconnectedCallback() { Services.obs.removeObserver( this.textObserver, "autocomplete-did-enter-text" ); this.hasConnected = false; } } MozXULElement.implementCustomInterface(MozGlodaAutocompleteInput, [ Ci.nsIObserver, ]); customElements.define("gloda-autocomplete-input", MozGlodaAutocompleteInput, { extends: "input", }); });