mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
Bug 1221539 - Add search engine discovery to the page action menu. Part 2: Add the new action. r=Gijs
MozReview-Commit-ID: DEEZBwmV0JD --HG-- extra : rebase_source : 71b68621198d72c069f505d922e92ed357eefe8b
This commit is contained in:
parent
36b7f691ee
commit
e5e43736bc
@ -965,10 +965,29 @@ var BrowserPageActionFeedback = {
|
||||
return this.feedbackLabel = document.getElementById("pageActionFeedbackMessage");
|
||||
},
|
||||
|
||||
show(action, event, textContentOverride) {
|
||||
this.feedbackLabel.textContent = this.panelNode.getAttribute((textContentOverride || action.id) + "Feedback");
|
||||
/**
|
||||
* Shows the feedback popup for an action.
|
||||
*
|
||||
* @param action (PageActions.Action, required)
|
||||
* The action associated with the feedback.
|
||||
* @param opts (object, optional)
|
||||
* An object with the following optional properties:
|
||||
* - event (DOM event): The event that triggered the feedback.
|
||||
* - textAttributeOverride (string): Normally the feedback text is
|
||||
* taken from an attribute on the feedback panel. The attribute's
|
||||
* name is `${action.id}Feedback`. Use this to override the
|
||||
* action.id part of the name.
|
||||
* - text (string): The text string. If not given, an attribute on
|
||||
* panel is assumed to contain the text, as described above.
|
||||
*/
|
||||
show(action, opts = {}) {
|
||||
this.feedbackLabel.textContent =
|
||||
opts.text ||
|
||||
this.panelNode.getAttribute((opts.textAttributeOverride || action.id) +
|
||||
"Feedback");
|
||||
this.panelNode.hidden = false;
|
||||
|
||||
let event = opts.event || null;
|
||||
let anchor = BrowserPageActions.panelAnchorNodeForAction(action, event);
|
||||
PanelMultiView.openPopup(this.panelNode, anchor, {
|
||||
position: "bottomcenter topright",
|
||||
@ -1019,7 +1038,9 @@ BrowserPageActions.copyURL = {
|
||||
.getService(Ci.nsIClipboardHelper)
|
||||
.copyString(gURLBar.makeURIReadable(gBrowser.selectedBrowser.currentURI).displaySpec);
|
||||
let action = PageActions.actionForID("copyURL");
|
||||
BrowserPageActionFeedback.show(action, event);
|
||||
BrowserPageActionFeedback.show(action, {
|
||||
event,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@ -1096,8 +1117,11 @@ BrowserPageActions.sendToDevice = {
|
||||
// in", "Learn about Sync", etc. Device items will be .sendtab-target.
|
||||
if (event.target.classList.contains("sendtab-target")) {
|
||||
let action = PageActions.actionForID("sendToDevice");
|
||||
let textOverride = gSync.offline && "sendToDeviceOffline";
|
||||
BrowserPageActionFeedback.show(action, event, textOverride);
|
||||
let textAttributeOverride = gSync.offline && "sendToDeviceOffline";
|
||||
BrowserPageActionFeedback.show(action, {
|
||||
event,
|
||||
textAttributeOverride,
|
||||
});
|
||||
}
|
||||
});
|
||||
return item;
|
||||
@ -1120,3 +1144,118 @@ BrowserPageActions.sendToDevice = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// add search engine
|
||||
BrowserPageActions.addSearchEngine = {
|
||||
get action() {
|
||||
return PageActions.actionForID("addSearchEngine");
|
||||
},
|
||||
|
||||
get engines() {
|
||||
return gBrowser.selectedBrowser.engines || [];
|
||||
},
|
||||
|
||||
get strings() {
|
||||
delete this.strings;
|
||||
let uri = "chrome://browser/locale/search.properties";
|
||||
return this.strings = Services.strings.createBundle(uri);
|
||||
},
|
||||
|
||||
updateEngines() {
|
||||
// As a slight optimization, if the action isn't in the urlbar, don't do
|
||||
// anything here except disable it. The action's panel nodes are updated
|
||||
// when the panel is shown.
|
||||
this.action.setDisabled(!this.engines.length, window);
|
||||
if (this.action.shouldShowInUrlbar(window)) {
|
||||
this._updateTitleAndIcon();
|
||||
}
|
||||
},
|
||||
|
||||
_updateTitleAndIcon() {
|
||||
if (!this.engines.length) {
|
||||
return;
|
||||
}
|
||||
let title =
|
||||
this.engines.length == 1 ?
|
||||
this.strings.formatStringFromName("searchAddFoundEngine",
|
||||
[this.engines[0].title], 1) :
|
||||
this.strings.GetStringFromName("searchAddFoundEngineMenu");
|
||||
this.action.setTitle(title, window);
|
||||
this.action.setIconURL(this.engines[0].icon, window);
|
||||
},
|
||||
|
||||
onShowingInPanel() {
|
||||
this._updateTitleAndIcon();
|
||||
this.action.setWantsSubview(this.engines.length > 1, window);
|
||||
let button = BrowserPageActions.panelButtonNodeForActionID(this.action.id);
|
||||
button.classList.add("badged-button");
|
||||
button.setAttribute("image", this.engines[0].icon);
|
||||
button.setAttribute("uri", this.engines[0].uri);
|
||||
button.setAttribute("crop", "center");
|
||||
},
|
||||
|
||||
onSubviewShowing(panelViewNode) {
|
||||
let body = panelViewNode.querySelector(".panel-subview-body");
|
||||
while (body.firstChild) {
|
||||
body.firstChild.remove();
|
||||
}
|
||||
for (let engine of this.engines) {
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.classList.add("subviewbutton", "subviewbutton-iconic");
|
||||
button.setAttribute("label", engine.title);
|
||||
button.setAttribute("image", engine.icon);
|
||||
button.setAttribute("uri", engine.uri);
|
||||
button.addEventListener("command", event => {
|
||||
PanelMultiView.hidePopup(BrowserPageActions.panelNode);
|
||||
this._handleClickOnEngineButton(button);
|
||||
});
|
||||
body.appendChild(button);
|
||||
}
|
||||
},
|
||||
|
||||
onCommand(event, buttonNode) {
|
||||
if (!buttonNode.closest("panel")) {
|
||||
// The urlbar button was clicked. It should have a subview if there are
|
||||
// many engines.
|
||||
let manyEngines = this.engines.length > 1;
|
||||
this.action.setWantsSubview(manyEngines, window);
|
||||
if (manyEngines) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._handleClickOnEngineButton(buttonNode);
|
||||
},
|
||||
|
||||
_handleClickOnEngineButton(button) {
|
||||
this._installEngine(button.getAttribute("uri"),
|
||||
button.getAttribute("image"));
|
||||
},
|
||||
|
||||
_installEngine(uri, image) {
|
||||
Services.search.addEngine(uri, null, image, false, {
|
||||
onSuccess: engine => {
|
||||
BrowserPageActionFeedback.show(this.action, {
|
||||
text: this.strings.GetStringFromName("searchAddedFoundEngine"),
|
||||
});
|
||||
},
|
||||
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, 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);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -1346,6 +1346,7 @@ var gBrowserInit = {
|
||||
TabletModeUpdater.init();
|
||||
CombinedStopReload.ensureInitialized();
|
||||
gPrivateBrowsingUI.init();
|
||||
BrowserSearch.init();
|
||||
BrowserPageActions.init();
|
||||
gAccessibilityServiceIndicator.init();
|
||||
|
||||
@ -1876,6 +1877,8 @@ var gBrowserInit = {
|
||||
|
||||
LanguagePrompt.uninit();
|
||||
|
||||
BrowserSearch.uninit();
|
||||
|
||||
// Now either cancel delayedStartup, or clean up the services initialized from
|
||||
// it.
|
||||
if (this._boundDelayedStartup) {
|
||||
@ -3747,6 +3750,83 @@ const DOMEventHandler = {
|
||||
};
|
||||
|
||||
const BrowserSearch = {
|
||||
init() {
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified");
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
||||
},
|
||||
|
||||
observe(engine, topic, data) {
|
||||
// There are two kinds of search engine objects, nsISearchEngine objects and
|
||||
// plain { uri, title, icon } objects. `engine` in this method is the
|
||||
// former. The browser.engines and browser.hiddenEngines arrays are the
|
||||
// latter, and they're the engines offered by the the page in the browser.
|
||||
//
|
||||
// The two types of engines are currently related by their titles/names,
|
||||
// although that may change; see bug 335102.
|
||||
let engineName = engine.wrappedJSObject.name;
|
||||
switch (data) {
|
||||
case "engine-removed":
|
||||
// An engine was removed from the search service. If a page is offering
|
||||
// the engine, then the engine needs to be added back to the corresponding
|
||||
// browser's offered engines.
|
||||
this._addMaybeOfferedEngine(engineName);
|
||||
break;
|
||||
case "engine-added":
|
||||
// An engine was added to the search service. If a page is offering the
|
||||
// engine, then the engine needs to be removed from the corresponding
|
||||
// browser's offered engines.
|
||||
this._removeMaybeOfferedEngine(engineName);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_addMaybeOfferedEngine(engineName) {
|
||||
let selectedBrowserOffersEngine = false;
|
||||
for (let browser of gBrowser.browsers) {
|
||||
for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
|
||||
if (browser.hiddenEngines[i].title == engineName) {
|
||||
if (!browser.engines) {
|
||||
browser.engines = [];
|
||||
}
|
||||
browser.engines.push(browser.hiddenEngines[i]);
|
||||
browser.hiddenEngines.splice(i, 1);
|
||||
if (browser == gBrowser.selectedBrowser) {
|
||||
selectedBrowserOffersEngine = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedBrowserOffersEngine) {
|
||||
this.updateOpenSearchBadge();
|
||||
}
|
||||
},
|
||||
|
||||
_removeMaybeOfferedEngine(engineName) {
|
||||
let selectedBrowserOffersEngine = false;
|
||||
for (let browser of gBrowser.browsers) {
|
||||
for (let i = 0; i < (browser.engines || []).length; i++) {
|
||||
if (browser.engines[i].title == engineName) {
|
||||
if (!browser.hiddenEngines) {
|
||||
browser.hiddenEngines = [];
|
||||
}
|
||||
browser.hiddenEngines.push(browser.engines[i]);
|
||||
browser.engines.splice(i, 1);
|
||||
if (browser == gBrowser.selectedBrowser) {
|
||||
selectedBrowserOffersEngine = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedBrowserOffersEngine) {
|
||||
this.updateOpenSearchBadge();
|
||||
}
|
||||
},
|
||||
|
||||
addEngine(browser, engine, uri) {
|
||||
// Check to see whether we've already added an engine with this title
|
||||
if (browser.engines) {
|
||||
@ -3784,6 +3864,8 @@ const BrowserSearch = {
|
||||
* has search engines.
|
||||
*/
|
||||
updateOpenSearchBadge() {
|
||||
BrowserPageActions.addSearchEngine.updateEngines();
|
||||
|
||||
var searchBar = this.searchBar;
|
||||
if (!searchBar)
|
||||
return;
|
||||
|
@ -42,6 +42,14 @@ skip-if = true # Bug 1315887
|
||||
[browser_moz_action_link.js]
|
||||
[browser_new_tab_urlbar_reset.js]
|
||||
[browser_page_action_menu.js]
|
||||
[browser_page_action_menu_add_search_engine.js]
|
||||
support-files =
|
||||
page_action_menu_add_search_engine_one.html
|
||||
page_action_menu_add_search_engine_many.html
|
||||
page_action_menu_add_search_engine_same_names.html
|
||||
page_action_menu_add_search_engine_0.xml
|
||||
page_action_menu_add_search_engine_1.xml
|
||||
page_action_menu_add_search_engine_2.xml
|
||||
[browser_page_action_menu_clipboard.js]
|
||||
subsuite = clipboard
|
||||
[browser_pasteAndGo.js]
|
||||
|
@ -0,0 +1,305 @@
|
||||
"use strict";
|
||||
|
||||
// Checks a page that doesn't offer any engines.
|
||||
add_task(async function none() {
|
||||
let url = "http://mochi.test:8888/";
|
||||
await BrowserTestUtils.withNewTab(url, async () => {
|
||||
// Open the panel.
|
||||
await promisePageActionPanelOpen();
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
|
||||
// The action should not be present.
|
||||
let actions = PageActions.actionsInPanel(window);
|
||||
Assert.ok(!actions.some(a => a.id == "addSearchEngine"),
|
||||
"Action should not be present in panel");
|
||||
let button =
|
||||
BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(!button, "Action button should not be in panel");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Checks a page that offers one engine.
|
||||
add_task(async function one() {
|
||||
let url = getRootDirectory(gTestPath) + "page_action_menu_add_search_engine_one.html";
|
||||
await BrowserTestUtils.withNewTab(url, async () => {
|
||||
// Open the panel.
|
||||
await promisePageActionPanelOpen();
|
||||
|
||||
// The action should be present.
|
||||
let actions = PageActions.actionsInPanel(window);
|
||||
let action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
let expectedTitle =
|
||||
"Add \u{201C}page_action_menu_add_search_engine_0\u{201D} to One-Click Search";
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
let button =
|
||||
BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Button should be in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), false,
|
||||
"Button should not expand into a subview");
|
||||
|
||||
// Click the action's button.
|
||||
let enginePromise =
|
||||
promiseEngine("engine-added", "page_action_menu_add_search_engine_0");
|
||||
let hiddenPromise = promisePageActionPanelHidden();
|
||||
let feedbackPromise = promiseFeedbackPanelHidden();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
await hiddenPromise;
|
||||
let engine = await enginePromise;
|
||||
let feedbackText = await feedbackPromise;
|
||||
Assert.equal(feedbackText, "Added to Search Dropdown");
|
||||
|
||||
// Open the panel again.
|
||||
await promisePageActionPanelOpen();
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
|
||||
// The action should be gone.
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(!action, "Action should not be present in panel");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(!button, "Action button should not be in panel");
|
||||
|
||||
// Remove the engine.
|
||||
enginePromise =
|
||||
promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
|
||||
Services.search.removeEngine(engine);
|
||||
await enginePromise;
|
||||
|
||||
// Open the panel again.
|
||||
await promisePageActionPanelOpen();
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
|
||||
// The action should be present again.
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Action button should be in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), false,
|
||||
"Button should not expand into a subview");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Checks a page that offers many engines.
|
||||
add_task(async function many() {
|
||||
let url = getRootDirectory(gTestPath) + "page_action_menu_add_search_engine_many.html";
|
||||
await BrowserTestUtils.withNewTab(url, async () => {
|
||||
// Open the panel.
|
||||
await promisePageActionPanelOpen();
|
||||
|
||||
// The action should be present.
|
||||
let actions = PageActions.actionsInPanel(window);
|
||||
let action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
let expectedTitle = "Add One-Click Search Engine";
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
let button =
|
||||
BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Action button should be in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), true,
|
||||
"Button should expand into a subview");
|
||||
|
||||
// Click the action's button. The subview should be shown.
|
||||
let viewPromise = promisePageActionViewShown();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
let view = await viewPromise;
|
||||
let viewID =
|
||||
BrowserPageActions._panelViewNodeIDForActionID("addSearchEngine", false);
|
||||
Assert.equal(view.id, viewID, "View ID");
|
||||
let bodyID = viewID + "-body";
|
||||
let body = document.getElementById(bodyID);
|
||||
Assert.deepEqual(
|
||||
Array.map(body.childNodes, n => n.label),
|
||||
[
|
||||
"page_action_menu_add_search_engine_0",
|
||||
"page_action_menu_add_search_engine_1",
|
||||
"page_action_menu_add_search_engine_2",
|
||||
],
|
||||
"Subview children"
|
||||
);
|
||||
|
||||
// Click the first engine to install it.
|
||||
let enginePromise =
|
||||
promiseEngine("engine-added", "page_action_menu_add_search_engine_0");
|
||||
let hiddenPromise = promisePageActionPanelHidden();
|
||||
let feedbackPromise = promiseFeedbackPanelHidden();
|
||||
EventUtils.synthesizeMouseAtCenter(body.childNodes[0], {});
|
||||
await hiddenPromise;
|
||||
let engines = [];
|
||||
let engine = await enginePromise;
|
||||
engines.push(engine);
|
||||
let feedbackText = await feedbackPromise;
|
||||
Assert.equal(feedbackText, "Added to Search Dropdown", "Feedback text");
|
||||
|
||||
// Open the panel and show the subview again. The installed engine should
|
||||
// be gone.
|
||||
await promisePageActionPanelOpen();
|
||||
viewPromise = promisePageActionViewShown();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
await viewPromise;
|
||||
Assert.deepEqual(
|
||||
Array.map(body.childNodes, n => n.label),
|
||||
[
|
||||
"page_action_menu_add_search_engine_1",
|
||||
"page_action_menu_add_search_engine_2",
|
||||
],
|
||||
"Subview children"
|
||||
);
|
||||
|
||||
// Click the next engine to install it.
|
||||
enginePromise =
|
||||
promiseEngine("engine-added", "page_action_menu_add_search_engine_1");
|
||||
hiddenPromise = promisePageActionPanelHidden();
|
||||
feedbackPromise = promiseFeedbackPanelHidden();
|
||||
EventUtils.synthesizeMouseAtCenter(body.childNodes[0], {});
|
||||
await hiddenPromise;
|
||||
engine = await enginePromise;
|
||||
engines.push(engine);
|
||||
feedbackText = await feedbackPromise;
|
||||
Assert.equal(feedbackText, "Added to Search Dropdown", "Feedback text");
|
||||
|
||||
// Open the panel again. This time the action button should show the one
|
||||
// remaining engine.
|
||||
await promisePageActionPanelOpen();
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
expectedTitle =
|
||||
"Add \u{201C}page_action_menu_add_search_engine_2\u{201D} to One-Click Search";
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Button should be present in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), false,
|
||||
"Button should not expand into a subview");
|
||||
|
||||
// Click the button.
|
||||
enginePromise =
|
||||
promiseEngine("engine-added", "page_action_menu_add_search_engine_2");
|
||||
hiddenPromise = promisePageActionPanelHidden();
|
||||
feedbackPromise = promiseFeedbackPanelHidden();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
await hiddenPromise;
|
||||
engine = await enginePromise;
|
||||
engines.push(engine);
|
||||
feedbackText = await feedbackPromise;
|
||||
Assert.equal(feedbackText, "Added to Search Dropdown", "Feedback text");
|
||||
|
||||
// All engines are installed at this point. Open the panel and make sure
|
||||
// the action is gone.
|
||||
await promisePageActionPanelOpen();
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(!action, "Action should be gone");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(!button, "Button should not be in panel");
|
||||
|
||||
// Remove the first engine.
|
||||
enginePromise =
|
||||
promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
|
||||
Services.search.removeEngine(engines.shift());
|
||||
await enginePromise;
|
||||
|
||||
// Open the panel again. The action should be present and showing the first
|
||||
// engine.
|
||||
await promisePageActionPanelOpen();
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
expectedTitle =
|
||||
"Add \u{201C}page_action_menu_add_search_engine_0\u{201D} to One-Click Search";
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Button should be present in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), false,
|
||||
"Button should not expand into a subview");
|
||||
|
||||
// Remove the second engine.
|
||||
enginePromise =
|
||||
promiseEngine("engine-removed", "page_action_menu_add_search_engine_1");
|
||||
Services.search.removeEngine(engines.shift());
|
||||
await enginePromise;
|
||||
|
||||
// Open the panel again and check the subview. The subview should be
|
||||
// present now that there are two offerred engines again.
|
||||
await promisePageActionPanelOpen();
|
||||
actions = PageActions.actionsInPanel(window);
|
||||
action = actions.find(a => a.id == "addSearchEngine");
|
||||
Assert.ok(action, "Action should be present in panel");
|
||||
expectedTitle = "Add One-Click Search Engine";
|
||||
Assert.equal(action.getTitle(window), expectedTitle, "Action title");
|
||||
button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
|
||||
Assert.ok(button, "Button should be in panel");
|
||||
Assert.equal(button.label, expectedTitle, "Button label");
|
||||
Assert.equal(button.classList.contains("subviewbutton-nav"), true,
|
||||
"Button should expand into a subview");
|
||||
viewPromise = promisePageActionViewShown();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
await viewPromise;
|
||||
body = document.getElementById(bodyID);
|
||||
Assert.deepEqual(
|
||||
Array.map(body.childNodes, n => n.label),
|
||||
[
|
||||
"page_action_menu_add_search_engine_0",
|
||||
"page_action_menu_add_search_engine_1",
|
||||
],
|
||||
"Subview children"
|
||||
);
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
|
||||
// Remove the third engine.
|
||||
enginePromise =
|
||||
promiseEngine("engine-removed", "page_action_menu_add_search_engine_2");
|
||||
Services.search.removeEngine(engines.shift());
|
||||
await enginePromise;
|
||||
|
||||
// Open the panel again and check the subview.
|
||||
await promisePageActionPanelOpen();
|
||||
viewPromise = promisePageActionViewShown();
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
await viewPromise;
|
||||
Assert.deepEqual(
|
||||
Array.map(body.childNodes, n => n.label),
|
||||
[
|
||||
"page_action_menu_add_search_engine_0",
|
||||
"page_action_menu_add_search_engine_1",
|
||||
"page_action_menu_add_search_engine_2",
|
||||
],
|
||||
"Subview children"
|
||||
);
|
||||
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
|
||||
await promisePageActionPanelHidden();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function promiseEngine(expectedData, expectedEngineName) {
|
||||
return TestUtils.topicObserved("browser-search-engine-modified", (engine, data) => {
|
||||
return expectedData == data &&
|
||||
expectedEngineName == engine.wrappedJSObject.name;
|
||||
}).then(([engine, data]) => engine);
|
||||
}
|
||||
|
||||
function promiseFeedbackPanelHidden() {
|
||||
return new Promise(resolve => {
|
||||
BrowserPageActionFeedback.panelNode.addEventListener("popuphidden", event => {
|
||||
resolve(BrowserPageActionFeedback.feedbackLabel.textContent);
|
||||
}, {once: true});
|
||||
});
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>page_action_menu_add_search_engine_0</ShortName>
|
||||
<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
|
||||
<Param name="terms" value="{searchTerms}"/>
|
||||
</Url>
|
||||
</SearchPlugin>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>page_action_menu_add_search_engine_1</ShortName>
|
||||
<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
|
||||
<Param name="terms" value="{searchTerms}"/>
|
||||
</Url>
|
||||
</SearchPlugin>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>page_action_menu_add_search_engine_2</ShortName>
|
||||
<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
|
||||
<Param name="terms" value="{searchTerms}"/>
|
||||
</Url>
|
||||
</SearchPlugin>
|
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_0" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_0.xml">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_1" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_1.xml">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_2" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_2.xml">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_0" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_0.xml">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_0" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_0.xml">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="page_action_menu_add_search_engine_1" href="http://mochi.test:8888/browser/browser/base/content/test/urlbar/page_action_menu_add_search_engine_0.xml">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -188,18 +188,6 @@
|
||||
<parameter name="aVerb"/>
|
||||
<body><![CDATA[
|
||||
if (aTopic == "browser-search-engine-modified") {
|
||||
switch (aVerb) {
|
||||
case "engine-removed":
|
||||
this.offerNewEngine(aEngine);
|
||||
break;
|
||||
case "engine-added":
|
||||
this.hideNewEngine(aEngine);
|
||||
break;
|
||||
case "engine-changed":
|
||||
// An engine was removed (or hidden) or added, or an icon was
|
||||
// changed. Do nothing special.
|
||||
}
|
||||
|
||||
// Make sure the engine list is refetched next time it's needed
|
||||
this._engines = null;
|
||||
|
||||
@ -210,75 +198,6 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- There are two seaprate lists of search engines, whose uses intersect
|
||||
in this file. The search service (nsIBrowserSearchService and
|
||||
nsSearchService.js) maintains a list of Engine objects which is used to
|
||||
populate the searchbox list of available engines and to perform queries.
|
||||
That list is accessed here via this.SearchService, and it's that sort of
|
||||
Engine that is passed to this binding's observer as aEngine.
|
||||
|
||||
In addition, browser.js fills two lists of autodetected search engines
|
||||
(browser.engines and browser.hiddenEngines) as properties of
|
||||
selectedBrowser. Those lists contain unnamed JS objects of the form
|
||||
{ uri:, title:, icon: }, and that's what the searchbar uses to determine
|
||||
whether to show any "Add <EngineName>" menu items in the drop-down.
|
||||
|
||||
The two types of engines are currently related by their identifying
|
||||
titles (the Engine object's 'name'), although that may change; see bug
|
||||
335102. -->
|
||||
|
||||
<!-- If the engine that was just removed from the searchbox list was
|
||||
autodetected on this page, move it to each browser's active list so it
|
||||
will be offered to be added again. -->
|
||||
<method name="offerNewEngine">
|
||||
<parameter name="aEngine"/>
|
||||
<body><![CDATA[
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (browser.hiddenEngines) {
|
||||
// XXX This will need to be changed when engines are identified by
|
||||
// URL rather than title; see bug 335102.
|
||||
var removeTitle = aEngine.wrappedJSObject.name;
|
||||
for (var i = 0; i < browser.hiddenEngines.length; i++) {
|
||||
if (browser.hiddenEngines[i].title == removeTitle) {
|
||||
if (!browser.engines)
|
||||
browser.engines = [];
|
||||
browser.engines.push(browser.hiddenEngines[i]);
|
||||
browser.hiddenEngines.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BrowserSearch.updateOpenSearchBadge();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- If the engine that was just added to the searchbox list was
|
||||
autodetected on this page, move it to each browser's hidden list so it is
|
||||
no longer offered to be added. -->
|
||||
<method name="hideNewEngine">
|
||||
<parameter name="aEngine"/>
|
||||
<body><![CDATA[
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (browser.engines) {
|
||||
// XXX This will need to be changed when engines are identified by
|
||||
// URL rather than title; see bug 335102.
|
||||
var removeTitle = aEngine.wrappedJSObject.name;
|
||||
for (var i = 0; i < browser.engines.length; i++) {
|
||||
if (browser.engines[i].title == removeTitle) {
|
||||
if (!browser.hiddenEngines)
|
||||
browser.hiddenEngines = [];
|
||||
browser.hiddenEngines.push(browser.engines[i]);
|
||||
browser.engines.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BrowserSearch.updateOpenSearchBadge();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="setIcon">
|
||||
<parameter name="element"/>
|
||||
<parameter name="uri"/>
|
||||
|
@ -33,6 +33,10 @@ cmd_addFoundEngine=Add “%S”
|
||||
# grouped in a submenu using cmd_addFoundEngineMenu as a label.
|
||||
cmd_addFoundEngineMenu=Add search engine
|
||||
|
||||
searchAddFoundEngine=Add “%S” to One-Click Search
|
||||
searchAddFoundEngineMenu=Add One-Click Search Engine
|
||||
searchAddedFoundEngine=Added to Search Dropdown
|
||||
|
||||
# LOCALIZATION NOTE (searchForSomethingWith2):
|
||||
# This string is used to build the header above the list of one-click
|
||||
# search providers: "Search for <user-typed string> with:"
|
||||
|
@ -1132,6 +1132,25 @@ var gBuiltInActions = [
|
||||
browserPageActions(buttonNode).emailLink.onCommand(event, buttonNode);
|
||||
},
|
||||
},
|
||||
|
||||
// add search engine
|
||||
{
|
||||
id: "addSearchEngine",
|
||||
// The title is set in browser-pageActions.js.
|
||||
title: "",
|
||||
_transient: true,
|
||||
onShowingInPanel(buttonNode) {
|
||||
browserPageActions(buttonNode).addSearchEngine.onShowingInPanel();
|
||||
},
|
||||
onCommand(event, buttonNode) {
|
||||
browserPageActions(buttonNode).addSearchEngine
|
||||
.onCommand(event, buttonNode);
|
||||
},
|
||||
onSubviewShowing(panelViewNode) {
|
||||
browserPageActions(panelViewNode).addSearchEngine
|
||||
.onSubviewShowing(panelViewNode);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
|
||||
|
@ -164,9 +164,9 @@ add_task(async function simple() {
|
||||
initialActionsInUrlbar,
|
||||
"Actions in urlbar after adding the action");
|
||||
|
||||
// Check the list of all actions.
|
||||
Assert.deepEqual(PageActions.actions,
|
||||
initialActions.concat([action]),
|
||||
// Check the set of all actions.
|
||||
Assert.deepEqual(new Set(PageActions.actions),
|
||||
new Set(initialActions.concat([action])),
|
||||
"All actions after adding the action");
|
||||
|
||||
Assert.deepEqual(PageActions.actionForID(action.id), action,
|
||||
@ -817,15 +817,15 @@ add_task(async function nonBuiltFirst() {
|
||||
|
||||
// Check the actions.
|
||||
Assert.deepEqual(
|
||||
PageActions.actions.map(a => a.id),
|
||||
initialActions.map(a => a.id).concat(
|
||||
new Set(PageActions.actions.map(a => a.id)),
|
||||
new Set(initialActions.map(a => a.id).concat(
|
||||
[action.id]
|
||||
),
|
||||
)),
|
||||
"All actions should be in PageActions.actions"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
PageActions._builtInActions.map(a => a.id),
|
||||
initialActions.map(a => a.id),
|
||||
initialActions.filter(a => !a.__transient).map(a => a.id),
|
||||
"PageActions._builtInActions should be initial actions"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
@ -866,7 +866,7 @@ add_task(async function nonBuiltFirst() {
|
||||
);
|
||||
Assert.deepEqual(
|
||||
PageActions._builtInActions.map(a => a.id),
|
||||
initialActions.map(a => a.id),
|
||||
initialActions.filter(a => !a.__transient).map(a => a.id),
|
||||
"PageActions._builtInActions should be initial actions"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
|
@ -163,6 +163,23 @@
|
||||
list-style-image: url("chrome://browser/skin/sync.svg");
|
||||
}
|
||||
|
||||
#pageAction-panel-addSearchEngine > .toolbarbutton-badge-stack > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
#pageAction-panel-addSearchEngine > .toolbarbutton-badge-stack > .toolbarbutton-badge {
|
||||
display: -moz-box;
|
||||
background: url(chrome://browser/skin/search-indicator-badge-add.svg) no-repeat center;
|
||||
box-shadow: none;
|
||||
/* "!important" is necessary to override the rule in toolbarbutton.css */
|
||||
margin: -4px 0 0 !important;
|
||||
margin-inline-end: -4px !important;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
min-width: 11px;
|
||||
min-height: 11px;
|
||||
}
|
||||
|
||||
/* URL bar and page action buttons */
|
||||
|
||||
#page-action-buttons {
|
||||
|
@ -6429,7 +6429,7 @@
|
||||
"expires_in_version": "64",
|
||||
"kind": "categorical",
|
||||
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
|
||||
"sendToDevice", "other"],
|
||||
"sendToDevice", "other", "addSearchEngine"],
|
||||
"description": "Count how many times people add items to the url bar"
|
||||
},
|
||||
"FX_PAGE_ACTION_REMOVED": {
|
||||
@ -6439,7 +6439,7 @@
|
||||
"expires_in_version": "64",
|
||||
"kind": "categorical",
|
||||
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
|
||||
"sendToDevice", "other"],
|
||||
"sendToDevice", "other", "addSearchEngine"],
|
||||
"description": "Count how many times people remove items from the url bar"
|
||||
},
|
||||
"FX_PAGE_ACTION_MANAGED": {
|
||||
@ -6449,7 +6449,7 @@
|
||||
"expires_in_version": "64",
|
||||
"kind": "categorical",
|
||||
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
|
||||
"sendToDevice", "other"],
|
||||
"sendToDevice", "other", "addSearchEngine"],
|
||||
"description": "Count how many times people manage extensions via their actions in the url bar"
|
||||
},
|
||||
"FX_PAGE_ACTION_URLBAR_USED": {
|
||||
@ -6459,7 +6459,7 @@
|
||||
"expires_in_version": "64",
|
||||
"kind": "categorical",
|
||||
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
|
||||
"sendToDevice", "other"],
|
||||
"sendToDevice", "other", "addSearchEngine"],
|
||||
"description": "Count how many times people use items in the url bar"
|
||||
},
|
||||
"FX_PAGE_ACTION_PANEL_USED": {
|
||||
@ -6469,7 +6469,7 @@
|
||||
"expires_in_version": "64",
|
||||
"kind": "categorical",
|
||||
"labels": ["bookmark", "pocket", "screenshots", "webcompat", "copyURL", "emailLink",
|
||||
"sendToDevice", "other"],
|
||||
"sendToDevice", "other", "addSearchEngine"],
|
||||
"description": "Count how many times people use items from the main page action button"
|
||||
},
|
||||
"INPUT_EVENT_RESPONSE_MS": {
|
||||
|
Loading…
Reference in New Issue
Block a user