зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1578584 - Quantumbar WebExt API: Add onResultPicked event. r=harry,mixedpuppy
Adds a new event listener to `browser.urlbar` called `onResultPicked`. This event is fired for tip results when they don't specify a URL. Hypothetically it could be fired for any type of result that didn't specify a URL, but that's only tips for now. The listener is passed two arguments: the payload of the result that was picked, and a "details" object whose properties depend on the type of result. For tips, details is `{ helpPicked }`, where `helpPicked` is true if the help button was picked and false if the main button was picked. Differential Revision: https://phabricator.services.mozilla.com/D46254 --HG-- extra : source : febf4480bc0bce19a0c8883e0c9296c40013e01e
This commit is contained in:
Родитель
10e6f9e594
Коммит
35081f529d
|
@ -192,6 +192,23 @@ this.urlbar = class extends ExtensionAPI {
|
|||
},
|
||||
}).api(),
|
||||
|
||||
onResultPicked: new EventManager({
|
||||
context,
|
||||
name: "urlbar.onResultPicked",
|
||||
register: (fire, providerName) => {
|
||||
let provider = UrlbarProviderExtension.getOrCreate(providerName);
|
||||
provider.setEventListener(
|
||||
"resultPicked",
|
||||
async (resultPayload, details) => {
|
||||
return fire.async(resultPayload, details).catch(error => {
|
||||
throw context.normalizeError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
return () => provider.setEventListener("resultPicked", null);
|
||||
},
|
||||
}).api(),
|
||||
|
||||
openViewOnFocus: getSettingsAPI(
|
||||
context.extension.id,
|
||||
"openViewOnFocus",
|
||||
|
|
|
@ -166,6 +166,31 @@
|
|||
},
|
||||
"description": "The results that the provider fetched for the query."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "onResultPicked",
|
||||
"type": "function",
|
||||
"description": "Typically, a provider includes a <code>url</code> property in its results' payloads. When the user picks a result with a URL, Firefox automatically loads the URL. URLs don't make sense for every result type, however. When the user picks a result without a URL, this event is fired. The provider should take an appropriate action in response. Currently the only applicable <code>ResultType</code> is <code>tip</code>.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"type": "object",
|
||||
"description": "The payload of the result that was picked."
|
||||
},
|
||||
{
|
||||
"name": "details",
|
||||
"type": "object",
|
||||
"description": "Details about the pick. The specific properties depend on the result type."
|
||||
}
|
||||
],
|
||||
"extraParameters": [
|
||||
{
|
||||
"name": "providerName",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]+$",
|
||||
"description": "The listener will be called for the results of the provider with this name."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -270,6 +270,7 @@ skip-if = os == 'mac' # Save as PDF not supported on Mac OS X
|
|||
[browser_ext_themes_validation.js]
|
||||
[browser_ext_topSites.js]
|
||||
[browser_ext_url_overrides_newtab.js]
|
||||
[browser_ext_urlbar.js]
|
||||
[browser_ext_urlbar_contextual_tip.js]
|
||||
[browser_ext_user_events.js]
|
||||
[browser_ext_webRequest.js]
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
|
||||
});
|
||||
|
||||
async function loadExtension(options = {}) {
|
||||
let ext = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["urlbar"],
|
||||
},
|
||||
isPrivileged: true,
|
||||
background() {
|
||||
browser.test.onMessage.addListener(options => {
|
||||
browser.urlbar.onBehaviorRequested.addListener(query => {
|
||||
return "restricting";
|
||||
}, "test");
|
||||
browser.urlbar.onResultsRequested.addListener(query => {
|
||||
return [
|
||||
{
|
||||
type: "tip",
|
||||
source: "local",
|
||||
heuristic: true,
|
||||
payload: {
|
||||
text: "Test",
|
||||
buttonText: "OK",
|
||||
data: "testData",
|
||||
buttonUrl: options.buttonUrl,
|
||||
helpUrl: options.helpUrl,
|
||||
},
|
||||
},
|
||||
];
|
||||
}, "test");
|
||||
browser.urlbar.onResultPicked.addListener((payload, details) => {
|
||||
browser.test.assertEq(payload.text, "Test", "payload.text");
|
||||
browser.test.assertEq(payload.buttonText, "OK", "payload.buttonText");
|
||||
browser.test.assertEq(payload.data, "testData", "payload.data");
|
||||
browser.test.sendMessage("onResultPicked received", details);
|
||||
}, "test");
|
||||
browser.test.sendMessage("ready");
|
||||
});
|
||||
},
|
||||
});
|
||||
await ext.startup();
|
||||
await Promise.all([ext.sendMessage(options), ext.awaitMessage("ready")]);
|
||||
return ext;
|
||||
}
|
||||
|
||||
// Loads an extension without a main button URL and presses enter on the main
|
||||
// button.
|
||||
add_task(async function testOnResultPicked_mainButton_noURL_enter() {
|
||||
let ext = await loadExtension();
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
let details = await ext.awaitMessage("onResultPicked received");
|
||||
Assert.deepEqual(details, { helpPicked: false });
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension without a main button URL and clicks the main button.
|
||||
add_task(async function testOnResultPicked_mainButton_noURL_mouse() {
|
||||
let ext = await loadExtension();
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let mainButton = document.querySelector(
|
||||
"#urlbarView-row-0 .urlbarView-tip-button"
|
||||
);
|
||||
Assert.ok(mainButton);
|
||||
EventUtils.synthesizeMouseAtCenter(mainButton, {});
|
||||
let details = await ext.awaitMessage("onResultPicked received");
|
||||
Assert.deepEqual(details, { helpPicked: false });
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension with a main button URL and presses enter on the main
|
||||
// button.
|
||||
add_task(async function testOnResultPicked_mainButton_url_enter() {
|
||||
let ext = await loadExtension({ buttonUrl: "http://example.com/" });
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
ext.onMessage("onResultPicked received", () => {
|
||||
Assert.ok(false, "onResultPicked should not be called");
|
||||
});
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await loadedPromise;
|
||||
Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
|
||||
});
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension with a main button URL and clicks the main button.
|
||||
add_task(async function testOnResultPicked_mainButton_url_mouse() {
|
||||
let ext = await loadExtension({ buttonUrl: "http://example.com/" });
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let mainButton = document.querySelector(
|
||||
"#urlbarView-row-0 .urlbarView-tip-button"
|
||||
);
|
||||
Assert.ok(mainButton);
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
ext.onMessage("onResultPicked received", () => {
|
||||
Assert.ok(false, "onResultPicked should not be called");
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(mainButton, {});
|
||||
await loadedPromise;
|
||||
Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
|
||||
});
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension without a help button URL and presses enter on the help
|
||||
// button.
|
||||
add_task(async function testOnResultPicked_helpButton_noURL_enter() {
|
||||
let ext = await loadExtension();
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: 2 });
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
let details = await ext.awaitMessage("onResultPicked received");
|
||||
Assert.deepEqual(details, { helpPicked: true });
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension without a help button URL and clicks the help button.
|
||||
add_task(async function testOnResultPicked_helpButton_noURL_mouse() {
|
||||
let ext = await loadExtension();
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let helpButton = document.querySelector(
|
||||
"#urlbarView-row-0 .urlbarView-tip-help"
|
||||
);
|
||||
Assert.ok(helpButton);
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
let details = await ext.awaitMessage("onResultPicked received");
|
||||
Assert.deepEqual(details, { helpPicked: true });
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension with a help button URL and presses enter on the help
|
||||
// button.
|
||||
add_task(async function testOnResultPicked_helpButton_url_enter() {
|
||||
let ext = await loadExtension({ helpUrl: "http://example.com/" });
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
ext.onMessage("onResultPicked received", () => {
|
||||
Assert.ok(false, "onResultPicked should not be called");
|
||||
});
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: 2 });
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await loadedPromise;
|
||||
Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
|
||||
});
|
||||
await ext.unload();
|
||||
});
|
||||
|
||||
// Loads an extension with a help button URL and clicks the help button.
|
||||
add_task(async function testOnResultPicked_helpButton_url_mouse() {
|
||||
let ext = await loadExtension({ helpUrl: "http://example.com/" });
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value: "test",
|
||||
});
|
||||
let helpButton = document.querySelector(
|
||||
"#urlbarView-row-0 .urlbarView-tip-help"
|
||||
);
|
||||
Assert.ok(helpButton);
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
ext.onMessage("onResultPicked received", () => {
|
||||
Assert.ok(false, "onResultPicked should not be called");
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
await loadedPromise;
|
||||
Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
|
||||
});
|
||||
await ext.unload();
|
||||
});
|
|
@ -20,6 +20,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
UrlbarController: "resource:///modules/UrlbarController.jsm",
|
||||
UrlbarEventBufferer: "resource:///modules/UrlbarEventBufferer.jsm",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
|
||||
UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
|
||||
UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
|
||||
UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
|
||||
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
|
||||
|
@ -653,10 +654,10 @@ class UrlbarInput {
|
|||
break;
|
||||
}
|
||||
case UrlbarUtils.RESULT_TYPE.TIP: {
|
||||
if (element.classList.contains("urlbarView-tip-help")) {
|
||||
let helpPicked = element.classList.contains("urlbarView-tip-help");
|
||||
if (helpPicked) {
|
||||
url = result.payload.helpUrl;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
this.handleRevert();
|
||||
this.controller.engagementEvent.record(event, {
|
||||
|
@ -664,11 +665,16 @@ class UrlbarInput {
|
|||
selIndex,
|
||||
selType: "tip",
|
||||
});
|
||||
|
||||
// TODO: Call out to UrlbarProvider.pickElement as part of bug 1578584.
|
||||
let provider = UrlbarProvidersManager.getProvider(
|
||||
result.providerName
|
||||
);
|
||||
if (!provider) {
|
||||
Cu.reportError(`Provider not found: ${result.providerName}`);
|
||||
return;
|
||||
}
|
||||
provider.pickResult(result, { helpPicked });
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UrlbarUtils.RESULT_TYPE.OMNIBOX: {
|
||||
|
|
|
@ -213,24 +213,38 @@ class UrlbarProviderExtension extends UrlbarProvider {
|
|||
this._notifyListener("queryCanceled", context);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a result from the provider without a URL is
|
||||
* picked, but currently only for tip results. The provider should handle the
|
||||
* pick.
|
||||
*
|
||||
* @param {UrlbarResult} result
|
||||
* The result that was picked.
|
||||
* @param {object} details
|
||||
* Details about the pick, depending on the result type.
|
||||
*/
|
||||
pickResult(result, details) {
|
||||
this._notifyListener("resultPicked", result.payload, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a listener function set by the extension API implementation, if any.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* The name of the listener to call (i.e., the name of the event to fire).
|
||||
* @param {UrlbarQueryContext} context
|
||||
* The query context relevant to the event.
|
||||
* @param {arguments} args
|
||||
* The arguments to pass to the listener.
|
||||
* @returns {*}
|
||||
* The value returned by the listener function, if any.
|
||||
*/
|
||||
async _notifyListener(eventName, context) {
|
||||
async _notifyListener(eventName, ...args) {
|
||||
let listener = this._eventListeners.get(eventName);
|
||||
if (!listener) {
|
||||
return undefined;
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = listener(context);
|
||||
result = listener(...args);
|
||||
} catch (error) {
|
||||
Cu.reportError(error);
|
||||
return undefined;
|
||||
|
|
|
@ -367,6 +367,7 @@ class Query {
|
|||
return;
|
||||
}
|
||||
|
||||
match.providerName = provider.name;
|
||||
this.context.results.push(match);
|
||||
|
||||
let notifyResults = () => {
|
||||
|
|
|
@ -607,6 +607,7 @@ class UrlbarMuxer {
|
|||
get name() {
|
||||
return "UrlbarMuxerBase";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts queryContext results in-place.
|
||||
* @param {UrlbarQueryContext} queryContext the context to sort results for.
|
||||
|
@ -630,6 +631,7 @@ class UrlbarProvider {
|
|||
get name() {
|
||||
return "UrlbarProviderBase";
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the provider, must be one of UrlbarUtils.PROVIDER_TYPE.
|
||||
* @abstract
|
||||
|
@ -637,6 +639,7 @@ class UrlbarProvider {
|
|||
get type() {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this provider should be invoked for the given context.
|
||||
* If this method returns false, the providers manager won't start a query
|
||||
|
@ -648,6 +651,7 @@ class UrlbarProvider {
|
|||
isActive(queryContext) {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this provider wants to restrict results to just itself.
|
||||
* Other providers won't be invoked, unless this provider doesn't
|
||||
|
@ -659,6 +663,7 @@ class UrlbarProvider {
|
|||
isRestricting(queryContext) {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts querying.
|
||||
* @param {UrlbarQueryContext} queryContext The query context object
|
||||
|
@ -671,6 +676,7 @@ class UrlbarProvider {
|
|||
startQuery(queryContext, addCallback) {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a running query,
|
||||
* @param {UrlbarQueryContext} queryContext the query context object to cancel
|
||||
|
@ -680,6 +686,19 @@ class UrlbarProvider {
|
|||
cancelQuery(queryContext) {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a result from the provider without a URL is picked, but
|
||||
* currently only for tip results. The provider should handle the pick.
|
||||
* @param {UrlbarResult} result
|
||||
* The result that was picked.
|
||||
* @param {object} details
|
||||
* Details about the pick, depending on the result type.
|
||||
* @abstract
|
||||
*/
|
||||
pickResult(result, details) {
|
||||
throw new Error("Trying to access the base class, must be overridden");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче