author | Cosmin Sabou <csabou@mozilla.com> |
Thu, 15 Nov 2018 10:53:29 +0200 | |
changeset 446550 | d76f007bee63b3730068f1a5549bd4d6513593c7 |
parent 446549 | eff95fc19f19f108a7554f74e456f9fd7664541a |
child 446551 | ba1aae6c2949a2c1b9cbb0e4aad403c5abe2bb69 |
push id | 35043 |
push user | ebalazs@mozilla.com |
push date | Thu, 15 Nov 2018 16:12:36 +0000 |
treeherder | mozilla-central@59026ada59bd [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1506261 |
milestone | 65.0a1 |
backs out | 10a5af6d30df698b0f03cd62c0948d26089cf2af |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -84,17 +84,16 @@ <script type="application/javascript" #ifdef BROWSER_XHTML xmlns="http://www.w3.org/1999/xhtml" #endif > Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", this); Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser.js", this); - Services.scriptloader.loadSubScript("chrome://browser/content/search/search-one-offs.js", this); window.onload = gBrowserInit.onLoad.bind(gBrowserInit); window.onunload = gBrowserInit.onUnload.bind(gBrowserInit); window.onclose = WindowIsClosing; #ifdef BROWSER_XHTML window.addEventListener("readystatechange", () => { // We initially hide the window to prevent layouts during parse. This lets us @@ -256,24 +255,24 @@ xmlns="http://www.w3.org/1999/xhtml" flip="none" level="parent"> <html:div class="urlbarView-body-outer"> <html:div class="urlbarView-body-inner"> <!-- TODO: add search suggestions notification --> <html:div class="urlbarView-results"/> </html:div> </html:div> - <hbox class="search-one-offs" - compact="true" - includecurrentengine="true" - disabletab="true"/> + <search-one-offs class="search-one-offs" + compact="true" + includecurrentengine="true" + disabletab="true"/> </panel> - <!-- for date/time picker. consumeoutsideclicks is set to never, so that - clicks on the anchored input box are never consumed. --> + <!-- for date/time picker. consumeoutsideclicks is set to never, so that + clicks on the anchored input box are never consumed. --> <panel id="DateTimePickerPanel" type="arrow" hidden="true" orient="vertical" noautofocus="true" norolluponanchor="true" consumeoutsideclicks="never" level="parent"
--- a/browser/base/content/global-scripts.inc +++ b/browser/base/content/global-scripts.inc @@ -12,16 +12,17 @@ xmlns="http://www.w3.org/1999/xhtml" #endif > Components.utils.import("resource://gre/modules/Services.jsm"); for (let script of [ "chrome://browser/content/browser.js", "chrome://browser/content/search/searchbar.js", + "chrome://browser/content/search/search-one-offs.js", "chrome://browser/content/browser-captivePortal.js", "chrome://browser/content/browser-compacttheme.js", "chrome://browser/content/browser-contentblocking.js", "chrome://browser/content/browser-media.js", "chrome://browser/content/browser-pageActions.js", "chrome://browser/content/browser-places.js", "chrome://browser/content/browser-plugins.js",
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js +++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js @@ -255,27 +255,27 @@ add_task(async function collapsedOneOffs // The one-offs should be hidden when searching with an "@engine" search engine // alias. add_task(async function hiddenWhenUsingSearchAlias() { let typedValue = "@example"; await promiseAutocompleteResultPopup(typedValue, window, true); await waitForAutocompleteResultAt(0); Assert.equal(gURLBar.popup.oneOffSearchesEnabled, false); Assert.equal( - window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display, + window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display, "none" ); await hidePopup(); typedValue = "not an engine alias"; await promiseAutocompleteResultPopup(typedValue, window, true); await waitForAutocompleteResultAt(0); Assert.equal(gURLBar.popup.oneOffSearchesEnabled, true); Assert.equal( - window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display, + window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display, "-moz-box" ); await hidePopup(); }); function assertState(result, oneOff, textValue = undefined) { Assert.equal(gURLBar.popup.selectedIndex, result,
--- a/browser/base/content/test/urlbar/browser_urlbarSearchFunction.js +++ b/browser/base/content/test/urlbar/browser_urlbarSearchFunction.js @@ -122,17 +122,17 @@ function assertSearchSuggestionsNotifica * Asserts that the one-off search buttons are or aren't visible. * * @param visible * True if they should be visible, false if not. */ function assertOneOffButtonsVisible(visible) { Assert.equal(gURLBar.popup.oneOffSearchesEnabled, visible); Assert.equal( - window.getComputedStyle(gURLBar.popup.oneOffSearchButtons.container).display, + window.getComputedStyle(gURLBar.popup.oneOffSearchButtons).display, visible ? "-moz-box" : "none" ); } /** * Asserts that the urlbar's input value is the given value. Also asserts that * the first (heuristic) result in the popup is a search suggestion whose search * query is the given value.
--- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -1868,22 +1868,22 @@ file, You can obtain one at http://mozil onclick="openPreferences('paneSearch', {origin: 'searchChangeSettings'});" control="search-suggestions-change-settings"/> </xul:hbox> </xul:deck> <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/> <xul:hbox anonid="footer"> <children/> - <xul:hbox anonid="one-off-search-buttons" - class="search-one-offs" - compact="true" - includecurrentengine="true" - disabletab="true" - flex="1"/> + <xul:search-one-offs anonid="one-off-search-buttons" + class="search-one-offs" + compact="true" + includecurrentengine="true" + disabletab="true" + flex="1"/> </xul:hbox> </content> <implementation> <!-- For performance reasons we want to limit the size of the text runs we build and show to the user. --> @@ -1907,19 +1907,18 @@ file, You can obtain one at http://mozil document.getAnonymousElementByAttribute(this, "anonid", "footer"); </field> <field name="shrinkDelay" readonly="true"> 250 </field> <field name="oneOffSearchButtons" readonly="true"> - new window.SearchOneOffs( - document.getAnonymousElementByAttribute(this, "anonid", - "one-off-search-buttons")); + document.getAnonymousElementByAttribute(this, "anonid", + "one-off-search-buttons"); </field> <field name="_overrideValue">null</field> <property name="overrideValue" onget="return this._overrideValue;" onset="this._overrideValue = val; return val;"/> <method name="onPopupClick">
--- a/browser/components/search/content/search-one-offs.js +++ b/browser/components/search/content/search-one-offs.js @@ -1,46 +1,230 @@ /* 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/. */ -/* eslint-env mozilla/browser-window */ +"use strict"; -"use strict"; +/* eslint-env mozilla/browser-window */ { -class SearchOneOffs { - constructor(container) { - this.container = container; +let sharedFragment; +function getFragment() { + if (!sharedFragment) { + sharedFragment = MozXULElement.parseXULToFragment(` + <deck class="search-panel-one-offs-header search-panel-header search-panel-current-input"> + <label class="searchbar-oneoffheader-search" value="&searchWithHeader.label;"></label> + <hbox class="search-panel-searchforwith search-panel-current-input"> + <label value="&searchFor.label;"></label> + <label class="searchbar-oneoffheader-searchtext search-panel-input-value" flex="1" crop="end"></label> + <label flex="10000" value="&searchWith.label;"></label> + </hbox> + <hbox class="search-panel-searchonengine search-panel-current-input"> + <label value="&search.label;"></label> + <label class="searchbar-oneoffheader-engine search-panel-input-value" flex="1" crop="end"></label> + <label flex="10000" value="&searchAfter.label;"></label> + </hbox> + </deck> + <description role="group" class="search-panel-one-offs"> + <button oncommand="showSettings();" class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"></button> + </description> + <vbox class="search-add-engines"></vbox> + <button oncommand="showSettings();" class="search-setting-button search-panel-header" label="&changeSearchSettings.button;"></button> + <menupopup class="search-one-offs-context-menu"> + <menuitem class="search-one-offs-context-open-in-new-tab" label="&searchInNewTab.label;" accesskey="&searchInNewTab.accesskey;"></menuitem> + <menuitem class="search-one-offs-context-set-default" label="&searchSetAsDefault.label;" accesskey="&searchSetAsDefault.accesskey;"></menuitem> + </menupopup> + `, ["chrome://browser/locale/browser.dtd"]); + } + + return document.importNode(sharedFragment, true); +} + +class MozSearchOneOffs extends MozXULElement { + constructor() { + super(); + + this.addEventListener("mousedown", event => { + let target = event.originalTarget; + if (target.classList.contains("addengine-menu-button")) { + return; + } + // Required to receive click events from the buttons on Linux. + event.preventDefault(); + }); + + this.addEventListener("mousemove", event => { + let target = event.originalTarget; + + // Handle mouseover on the add-engine menu button and its popup items. + if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) || + target.classList.contains("addengine-menu-button")) { + let menuButton = this.querySelector(".addengine-menu-button"); + this._updateStateForButton(menuButton); + this._addEngineMenuShouldBeOpen = true; + this._resetAddEngineMenuTimeout(); + return; + } + + if (target.localName != "button") { + return; + } + + // Ignore mouse events when the context menu is open. + if (this._ignoreMouseEvents) { + return; + } + + let isOneOff = + target.classList.contains("searchbar-engine-one-off-item") && + !target.classList.contains("dummy"); + if (isOneOff || + target.classList.contains("addengine-item") || + target.classList.contains("search-setting-button")) { + this._updateStateForButton(target); + } + }); + + this.addEventListener("mouseout", event => { + let target = event.originalTarget; + + // Handle mouseout on the add-engine menu button and its popup items. + if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) || + target.classList.contains("addengine-menu-button")) { + this._updateStateForButton(null); + this._addEngineMenuShouldBeOpen = false; + this._resetAddEngineMenuTimeout(); + return; + } + + if (target.localName != "button") { + return; + } + + // Don't update the mouseover state if the context menu is open. + if (this._ignoreMouseEvents) { + return; + } + + this._updateStateForButton(null); + }); + + this.addEventListener("click", event => { + if (event.button == 2) { + return; // ignore right clicks. + } - this.container.appendChild(MozXULElement.parseXULToFragment(` - <deck class="search-panel-one-offs-header search-panel-header search-panel-current-input"> - <label class="searchbar-oneoffheader-search" value="&searchWithHeader.label;"/> - <hbox class="search-panel-searchforwith search-panel-current-input"> - <label value="&searchFor.label;"/> - <label class="searchbar-oneoffheader-searchtext search-panel-input-value" flex="1" crop="end"/> - <label flex="10000" value="&searchWith.label;"/> - </hbox> - <hbox class="search-panel-searchonengine search-panel-current-input"> - <label value="&search.label;"/> - <label class="searchbar-oneoffheader-engine search-panel-input-value" flex="1" crop="end"/> - <label flex="10000" value="&searchAfter.label;"/> - </hbox> - </deck> - <description role="group" class="search-panel-one-offs"> - <button class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"/> - </description> - <vbox class="search-add-engines"/> - <button class="search-setting-button search-panel-header" label="&changeSearchSettings.button;"/> - <menupopup class="search-one-offs-context-menu"> - <menuitem class="search-one-offs-context-open-in-new-tab" label="&searchInNewTab.label;" accesskey="&searchInNewTab.accesskey;"/> - <menuitem class="search-one-offs-context-set-default" label="&searchSetAsDefault.label;" accesskey="&searchSetAsDefault.accesskey;"/> - </menupopup> - `, ["chrome://browser/locale/browser.dtd"])); + let button = event.originalTarget; + let engine = button.engine; + + if (!engine) { + return; + } + + // Select the clicked button so that consumers can easily tell which + // button was acted on. + this.selectedButton = button; + this.handleSearchCommand(event, engine); + }); + + this.addEventListener("command", event => { + let target = event.originalTarget; + if (target.classList.contains("addengine-item")) { + // On success, hide the panel and tell event listeners to reshow it to + // show the new engine. + let installCallback = { + onSuccess: engine => { + this._rebuild(); + }, + onError(errorCode) { + if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) { + // Download error is shown by the search service + return; + } + const kSearchBundleURI = + "chrome://global/locale/search/search.properties"; + let searchBundle = Services.strings.createBundle(kSearchBundleURI); + let brandBundle = document.getElementById("bundle_brand"); + let brandName = brandBundle.getString("brandShortName"); + let title = searchBundle.GetStringFromName( + "error_invalid_engine_title" + ); + let text = searchBundle.formatStringFromName( + "error_duplicate_engine_msg", + [brandName, target.getAttribute("uri")], + 2 + ); + Services.prompt.QueryInterface(Ci.nsIPromptFactory); + let prompt = Services.prompt.getPrompt( + gBrowser.contentWindow, + Ci.nsIPrompt + ); + prompt.QueryInterface(Ci.nsIWritablePropertyBag2); + prompt.setPropertyAsBool("allowTabModal", true); + prompt.alert(title, text); + }, + }; + Services.search.addEngine(target.getAttribute("uri"), + target.getAttribute("image"), false, + installCallback); + } + if (target.classList.contains("search-one-offs-context-open-in-new-tab")) { + // Select the context-clicked button so that consumers can easily + // tell which button was acted on. + this.selectedButton = this._buttonForEngine(this._contextEngine); + this.handleSearchCommand(event, this._contextEngine, true); + } + if (target.classList.contains("search-one-offs-context-set-default")) { + let currentEngine = Services.search.defaultEngine; + + if (!this.getAttribute("includecurrentengine")) { + // Make the target button of the context menu reflect the current + // search engine first. Doing this as opposed to rebuilding all the + // one-off buttons avoids flicker. + let button = this._buttonForEngine(this._contextEngine); + button.id = this._buttonIDForEngine(currentEngine); + let uri = "chrome://browser/skin/search-engine-placeholder.png"; + if (currentEngine.iconURI) { + uri = currentEngine.iconURI.spec; + } + button.setAttribute("image", uri); + button.setAttribute("tooltiptext", currentEngine.name); + button.engine = currentEngine; + } + + Services.search.defaultEngine = this._contextEngine; + } + }); + + this.addEventListener("contextmenu", event => { + let target = event.originalTarget; + // Prevent the context menu from appearing except on the one off buttons. + if (!target.classList.contains("searchbar-engine-one-off-item") || + target.classList.contains("dummy")) { + event.preventDefault(); + return; + } + this.querySelector(".search-one-offs-context-set-default") + .setAttribute("disabled", target.engine == Services.search.defaultEngine); + + this.contextMenuPopup.openPopupAtScreen(event.screenX, event.screenY, true); + event.preventDefault(); + + this._contextEngine = target.engine; + }); + } + + connectedCallback() { + if (this.delayConnectedCallback()) { + return; + } + + this.appendChild(getFragment()); this._popup = null; this._textbox = null; this._textboxWidth = 0; /** @@ -88,83 +272,39 @@ class SearchOneOffs { * many engines offered by the current site. */ this._addEngineMenuTimeoutMs = 200; this._addEngineMenuTimeout = null; this._addEngineMenuShouldBeOpen = false; - this.addEventListener("mousedown", this); - this.addEventListener("mousemove", this); - this.addEventListener("mouseout", this); - this.addEventListener("click", this); - this.addEventListener("command", this); - this.addEventListener("contextmenu", this); - this.settingsButton.addEventListener("command", this); - this.settingsButtonCompact.addEventListener("command", this); - // Prevent popup events from the context menu from reaching the autocomplete // binding (or other listeners). let listener = aEvent => aEvent.stopPropagation(); this.contextMenuPopup.addEventListener("popupshowing", listener); this.contextMenuPopup.addEventListener("popuphiding", listener); this.contextMenuPopup.addEventListener("popupshown", aEvent => { this._ignoreMouseEvents = true; aEvent.stopPropagation(); }); this.contextMenuPopup.addEventListener("popuphidden", aEvent => { this._ignoreMouseEvents = false; aEvent.stopPropagation(); }); - // Invalidate our cached list of engines. - Services.prefs.addObserver("browser.search.hiddenOneOffs", this); - Services.obs.addObserver(this, "browser-search-engine-modified"); - Services.obs.addObserver(this, "browser-search-service"); + // Add weak referenced observers to invalidate our cached list of engines. + Services.prefs.addObserver("browser.search.hiddenOneOffs", this, true); + Services.obs.addObserver(this, "browser-search-engine-modified", true); + Services.obs.addObserver(this, "browser-search-service", true); // Rebuild the buttons when the theme changes. See bug 1357800 for // details. Summary: On Linux, switching between themes can cause a row // of buttons to disappear. - Services.obs.addObserver(this, "lightweight-theme-changed"); - - window.addEventListener("unload", this); - } - - addEventListener(...args) { - this.container.addEventListener(...args); - } - - removeEventListener(...args) { - this.container.removeEventListener(...args); - } - - dispatchEvent(...args) { - this.container.dispatchEvent(...args); - } - - getAttribute(...args) { - return this.container.getAttribute(...args); - } - - setAttribute(...args) { - this.container.setAttribute(...args); - } - - querySelector(...args) { - return this.container.querySelector(...args); - } - - handleEvent(event) { - let methodName = "_on_" + event.type; - if (methodName in this) { - this[methodName](event); - } else { - throw "Unrecognized search-one-offs event: " + event.type; - } + Services.obs.addObserver(this, "lightweight-theme-changed", true); } /** * Width in pixels of the one-off buttons. 49px is the min-width of * each search engine button, adapt this const when changing the css. * It's actually 48px + 1px of right border. */ get buttonWidth() { @@ -193,48 +333,41 @@ class SearchOneOffs { this._popup = val; // If the popup is already open, rebuild the one-offs now. The // popup may be opening, so check that the state is not closed // instead of checking popupOpen. if (val && val.state != "closed") { this._rebuild(); } - return val; } get popup() { return this._popup; } - /** * The textbox associated with the one-offs. Set this to a textbox to * automatically keep the related one-offs UI up to date. Otherwise you * can leave it null/undefined, and in that case you should update the * query property manually. */ set textbox(val) { if (this._textbox) { this._textbox.removeEventListener("input", this); } if (val) { val.addEventListener("input", this); } return this._textbox = val; } - get style() { - return this.container.style; - } - get textbox() { return this._textbox; } - /** * The query string currently shown in the one-offs. If the textbox * property is non-null, then this is automatically updated on * input. */ set query(val) { this._query = val; if (this.popup && this.popup.popupOpen) { @@ -328,21 +461,54 @@ class SearchOneOffs { return (!currentEngineNameToIgnore || name != currentEngineNameToIgnore) && !hiddenList.includes(name); }); return this._engines; } + /** + * This handles events outside the one-off buttons, like on the popup + * and textbox. + */ + handleEvent(event) { + switch (event.type) { + case "input": + // Allow the consumer's input to override its value property with + // a oneOffSearchQuery property. That way if the value is not + // actually what the user typed (e.g., it's autofilled, or it's a + // mozaction URI), the consumer has some way of providing it. + this.query = event.target.oneOffSearchQuery || event.target.value; + break; + case "popupshowing": + this._rebuild(); + break; + case "popuphidden": + Services.tm.dispatchToMainThread(() => { + this.selectedButton = null; + this._contextEngine = null; + }); + break; + } + } + observe(aEngine, aTopic, aData) { // Make sure the engine list is refetched next time it's needed. this._engines = null; } + showSettings() { + openPreferences("paneSearch", { origin: "contentSearch" }); + + // If the preference tab was already selected, the panel doesn't + // close itself automatically. + this.popup.hidePopup(); + } + /** * Updates the parts of the UI that show the query string. */ _updateAfterQueryChanged() { let headerSearchText = this.querySelector(".searchbar-oneoffheader-searchtext"); headerSearchText.setAttribute("value", this.query); let groupText; let isOneOffSelected = @@ -1026,224 +1192,14 @@ class SearchOneOffs { clearTimeout(this._addEngineMenuTimeout); } this._addEngineMenuTimeout = setTimeout(() => { delete this._addEngineMenuTimeout; let button = this.querySelector(".addengine-menu-button"); button.open = this._addEngineMenuShouldBeOpen; }, this._addEngineMenuTimeoutMs); } - - // Event handlers below. - - _on_mousedown(event) { - let target = event.originalTarget; - if (target.classList.contains("addengine-menu-button")) { - return; - } - // Required to receive click events from the buttons on Linux. - event.preventDefault(); - } - - _on_mousemove(event) { - let target = event.originalTarget; - - // Handle mouseover on the add-engine menu button and its popup items. - if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) || - target.classList.contains("addengine-menu-button")) { - let menuButton = this.querySelector(".addengine-menu-button"); - this._updateStateForButton(menuButton); - this._addEngineMenuShouldBeOpen = true; - this._resetAddEngineMenuTimeout(); - return; - } - - if (target.localName != "button") { - return; - } - - // Ignore mouse events when the context menu is open. - if (this._ignoreMouseEvents) { - return; - } - - let isOneOff = - target.classList.contains("searchbar-engine-one-off-item") && - !target.classList.contains("dummy"); - if (isOneOff || - target.classList.contains("addengine-item") || - target.classList.contains("search-setting-button")) { - this._updateStateForButton(target); - } - } - - _on_mouseout(event) { - let target = event.originalTarget; - - // Handle mouseout on the add-engine menu button and its popup items. - if ((target.localName == "menuitem" && target.classList.contains("addengine-item")) || - target.classList.contains("addengine-menu-button")) { - this._updateStateForButton(null); - this._addEngineMenuShouldBeOpen = false; - this._resetAddEngineMenuTimeout(); - return; - } - - if (target.localName != "button") { - return; - } - - // Don't update the mouseover state if the context menu is open. - if (this._ignoreMouseEvents) { - return; - } - - this._updateStateForButton(null); - } - - _on_click(event) { - if (event.button == 2) { - return; // ignore right clicks. - } - - let button = event.originalTarget; - let engine = button.engine; - - if (!engine) { - return; - } - - // Select the clicked button so that consumers can easily tell which - // button was acted on. - this.selectedButton = button; - this.handleSearchCommand(event, engine); - } - - _on_command(event) { - let target = event.target; - - if (target == this.settingsButton || - target == this.settingsButtonCompact) { - openPreferences("paneSearch", { origin: "contentSearch" }); - - // If the preference tab was already selected, the panel doesn't - // close itself automatically. - this.popup.hidePopup(); - return; - } - - if (target.classList.contains("addengine-item")) { - // On success, hide the panel and tell event listeners to reshow it to - // show the new engine. - let installCallback = { - onSuccess: engine => { - this._rebuild(); - }, - onError(errorCode) { - if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) { - // Download error is shown by the search service - return; - } - const kSearchBundleURI = - "chrome://global/locale/search/search.properties"; - let searchBundle = Services.strings.createBundle(kSearchBundleURI); - let brandBundle = document.getElementById("bundle_brand"); - let brandName = brandBundle.getString("brandShortName"); - let title = searchBundle.GetStringFromName( - "error_invalid_engine_title" - ); - let text = searchBundle.formatStringFromName( - "error_duplicate_engine_msg", - [brandName, target.getAttribute("uri")], - 2 - ); - Services.prompt.QueryInterface(Ci.nsIPromptFactory); - let prompt = Services.prompt.getPrompt( - gBrowser.contentWindow, - Ci.nsIPrompt - ); - prompt.QueryInterface(Ci.nsIWritablePropertyBag2); - prompt.setPropertyAsBool("allowTabModal", true); - prompt.alert(title, text); - }, - }; - Services.search.addEngine(target.getAttribute("uri"), - target.getAttribute("image"), false, - installCallback); - } - - if (target.classList.contains("search-one-offs-context-open-in-new-tab")) { - // Select the context-clicked button so that consumers can easily - // tell which button was acted on. - this.selectedButton = this._buttonForEngine(this._contextEngine); - this.handleSearchCommand(event, this._contextEngine, true); - } - - if (target.classList.contains("search-one-offs-context-set-default")) { - let currentEngine = Services.search.defaultEngine; - - if (!this.getAttribute("includecurrentengine")) { - // Make the target button of the context menu reflect the current - // search engine first. Doing this as opposed to rebuilding all the - // one-off buttons avoids flicker. - let button = this._buttonForEngine(this._contextEngine); - button.id = this._buttonIDForEngine(currentEngine); - let uri = "chrome://browser/skin/search-engine-placeholder.png"; - if (currentEngine.iconURI) { - uri = currentEngine.iconURI.spec; - } - button.setAttribute("image", uri); - button.setAttribute("tooltiptext", currentEngine.name); - button.engine = currentEngine; - } - - Services.search.defaultEngine = this._contextEngine; - } - } - - _on_contextmenu(event) { - let target = event.originalTarget; - // Prevent the context menu from appearing except on the one off buttons. - if (!target.classList.contains("searchbar-engine-one-off-item") || - target.classList.contains("dummy")) { - event.preventDefault(); - return; - } - this.querySelector(".search-one-offs-context-set-default") - .setAttribute("disabled", target.engine == Services.search.defaultEngine); - - this.contextMenuPopup.openPopupAtScreen(event.screenX, event.screenY, true); - event.preventDefault(); - - this._contextEngine = target.engine; - } - - _on_input(event) { - // Allow the consumer's input to override its value property with - // a oneOffSearchQuery property. That way if the value is not - // actually what the user typed (e.g., it's autofilled, or it's a - // mozaction URI), the consumer has some way of providing it. - this.query = event.target.oneOffSearchQuery || event.target.value; - } - - _on_popupshowing() { - this._rebuild(); - } - - _on_popuphidden() { - Services.tm.dispatchToMainThread(() => { - this.selectedButton = null; - this._contextEngine = null; - }); - } - - _on_unload() { - Services.prefs.removeObserver("browser.search.hiddenOneOffs", this); - Services.obs.removeObserver(this, "browser-search-engine-modified"); - Services.obs.removeObserver(this, "browser-search-service"); - Services.obs.removeObserver(this, "lightweight-theme-changed"); - } } -window.SearchOneOffs = SearchOneOffs; +MozXULElement.implementCustomInterface(MozSearchOneOffs, [Ci.nsIObserver, Ci.nsIWeakReference]); +customElements.define("search-one-offs", MozSearchOneOffs); } -
--- a/browser/components/search/content/search.xml +++ b/browser/components/search/content/search.xml @@ -372,17 +372,17 @@ <content ignorekeys="true" level="top" consumeoutsideclicks="never"> <xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings" class="search-panel-header search-panel-current-engine"> <xul:image class="searchbar-engine-image" xbl:inherits="src"/> <xul:label anonid="searchbar-engine-name" flex="1" crop="end" role="presentation"/> </xul:hbox> <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox search-panel-tree" flex="1"/> - <xul:hbox anonid="search-one-off-buttons" class="search-one-offs"/> + <xul:search-one-offs anonid="search-one-off-buttons" class="search-one-offs"/> </content> <implementation> <method name="openAutocompletePopup"> <parameter name="aInput"/> <parameter name="aElement"/> <body><![CDATA[ // initially the panel is hidden // to avoid impacting startup / new window performance @@ -468,19 +468,18 @@ this._bundle = Services.strings.createBundle(kBundleURI); } return this._bundle; ]]> </getter> </property> <field name="oneOffButtons" readonly="true"> - new window.SearchOneOffs( - document.getAnonymousElementByAttribute(this, "anonid", - "search-one-off-buttons")); + document.getAnonymousElementByAttribute(this, "anonid", + "search-one-off-buttons"); </field> <method name="updateHeader"> <body><![CDATA[ let currentEngine = Services.search.defaultEngine; let uri = currentEngine.iconURI; if (uri) { this.setAttribute("src", uri.spec);
--- a/browser/components/search/test/browser_oneOffContextMenu.js +++ b/browser/components/search/test/browser_oneOffContextMenu.js @@ -1,18 +1,20 @@ "use strict"; const TEST_ENGINE_NAME = "Foo"; const TEST_ENGINE_BASENAME = "testEngine.xml"; const searchPopup = document.getElementById("PopupSearchAutoComplete"); -const oneOffInstance = searchPopup.oneOffButtons; -const contextMenu = oneOffInstance.querySelector(".search-one-offs-context-menu"); -const oneOffButtons = oneOffInstance.buttons; -const searchInNewTabMenuItem = oneOffInstance.querySelector(".search-one-offs-context-open-in-new-tab"); +const oneOffElement = document.getAnonymousElementByAttribute( + searchPopup, "anonid", "search-one-off-buttons" +); +const contextMenu = oneOffElement.querySelector(".search-one-offs-context-menu"); +const oneOffButtons = oneOffElement.buttons; +const searchInNewTabMenuItem = oneOffElement.querySelector(".search-one-offs-context-open-in-new-tab"); let searchbar; let searchIcon; add_task(async function init() { searchbar = await gCUITestUtils.addSearchBar(); registerCleanupFunction(() => { gCUITestUtils.removeSearchBar();
--- a/browser/components/search/test/browser_oneOffContextMenu_setDefault.js +++ b/browser/components/search/test/browser_oneOffContextMenu_setDefault.js @@ -4,18 +4,22 @@ const TEST_ENGINE_NAME = "Foo"; const TEST_ENGINE_BASENAME = "testEngine.xml"; const SEARCHBAR_BASE_ID = "searchbar-engine-one-off-item-"; const URLBAR_BASE_ID = "urlbar-engine-one-off-item-"; const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches"; const urlbar = document.getElementById("urlbar"); const searchPopup = document.getElementById("PopupSearchAutoComplete"); const urlbarPopup = document.getElementById("PopupAutoCompleteRichResult"); -const searchOneOff = searchPopup.oneOffButtons; -const urlBarOneOff = urlbarPopup.oneOffSearchButtons; +const searchOneOffElement = document.getAnonymousElementByAttribute( + searchPopup, "anonid", "search-one-off-buttons" +); +const urlBarOneOffElement = document.getAnonymousElementByAttribute( + urlbarPopup, "anonid", "one-off-search-buttons" +); let originalEngine = Services.search.defaultEngine; function resetEngine() { Services.search.defaultEngine = originalEngine; } registerCleanupFunction(resetEngine); @@ -31,20 +35,20 @@ add_task(async function init() { await promiseNewEngine(TEST_ENGINE_BASENAME, { setAsCurrent: false, }); }); add_task(async function test_searchBarChangeEngine() { let oneOffButton = await openPopupAndGetEngineButton(true, searchPopup, - searchOneOff, + searchOneOffElement, SEARCHBAR_BASE_ID); - const setDefaultEngineMenuItem = searchOneOff.querySelector( + const setDefaultEngineMenuItem = searchOneOffElement.querySelector( ".search-one-offs-context-set-default" ); // Click the set default engine menu item. let promise = promiseCurrentEngineChanged(); EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {}); // This also checks the engine correctly changed. @@ -65,20 +69,20 @@ add_task(async function test_urlBarChang registerCleanupFunction(function() { Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF); }); // Ensure the engine is reset. resetEngine(); let oneOffButton = await openPopupAndGetEngineButton(false, urlbarPopup, - urlBarOneOff, + urlBarOneOffElement, URLBAR_BASE_ID); - const setDefaultEngineMenuItem = urlBarOneOff.querySelector( + const setDefaultEngineMenuItem = urlBarOneOffElement.querySelector( ".search-one-offs-context-set-default" ); // Click the set default engine menu item. let promise = promiseCurrentEngineChanged(); EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {}); // This also checks the engine correctly changed. @@ -119,40 +123,40 @@ function promiseCurrentEngineChanged() { /** * Opens the specified urlbar/search popup and gets the test engine from the * one-off buttons. * * @param {Boolean} isSearch true if the search popup should be opened; false * for the urlbar popup. * @param {Object} popup The expected popup. - * @param {Object} oneOffInstance The expected one-off instance for the popup. + * @param {Object} oneOffElement The expected one-off-element for the popup. * @param {String} baseId The expected string for the id of the current * engine button, without the engine name. * @return {Object} Returns an object that represents the one off button for the * test engine. */ -async function openPopupAndGetEngineButton(isSearch, popup, oneOffInstance, baseId) { +async function openPopupAndGetEngineButton(isSearch, popup, oneOffElement, baseId) { // Open the popup. let promise = promiseEvent(popup, "popupshown"); info("Opening panel"); // We have to open the popups in differnt ways. if (isSearch) { // Use the search icon to avoid hitting the network. EventUtils.synthesizeMouseAtCenter(searchIcon, {}); } else { // There's no history at this stage, so we need to press a key. urlbar.focus(); EventUtils.sendString("a"); } await promise; - const contextMenu = oneOffInstance.contextMenuPopup; - const oneOffButtons = oneOffInstance.buttons; + const contextMenu = oneOffElement.contextMenuPopup; + const oneOffButtons = oneOffElement.buttons; // Get the one-off button for the test engine. let oneOffButton; for (let node of oneOffButtons.children) { if (node.engine && node.engine.name == TEST_ENGINE_NAME) { oneOffButton = node; break; }
--- a/browser/components/urlbar/UrlbarView.jsm +++ b/browser/components/urlbar/UrlbarView.jsm @@ -40,22 +40,16 @@ class UrlbarView { if (event.target.classList.contains("urlbarView-row-inner")) { event.target.toggleAttribute("overflow", false); } }); this.controller.addQueryListener(this); } - get oneOffSearchButtons() { - return this._oneOffSearchButtons || - (this._oneOffSearchButtons = - new this.window.SearchOneOffs(this.panel.querySelector(".search-one-offs"))); - } - /** * Opens the autocomplete results popup. */ open() { this.panel.removeAttribute("hidden"); let panelDirection = this.panel.style.direction; if (!panelDirection) { @@ -67,20 +61,16 @@ class UrlbarView { let documentRect = this._getBoundsWithoutFlushing(this.document.documentElement); let width = documentRect.right - documentRect.left; this.panel.setAttribute("width", width); // Subtract two pixels for left and right borders on the panel. this._mainContainer.style.maxWidth = (width - 2) + "px"; - // TODO: Search one off buttons are a stub right now. - // We'll need to set them up properly. - this.oneOffSearchButtons; - this.panel.openPopup(this.urlbar.textbox.closest("toolbar"), "after_end", 0, -1); this._rows.firstElementChild.toggleAttribute("selected", true); } /** * Closes the autocomplete results popup. */