mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 14:25:52 +00:00
Bug 1245571: Allow AMO to be able to query details about an add-on. r=rhelmer
This adds a bunch of structure supporting a promise-based API on the AddonManager object that is exposed to webpages and adds the first example, getAddonByID. MozReview-Commit-ID: CCEFl4R1o81 --HG-- extra : rebase_source : 6206982c687a8e1733ef323488fc2710a4967688
This commit is contained in:
parent
4acb23615f
commit
9885c5b8b1
@ -404,6 +404,7 @@
|
||||
@RESPATH@/components/addonManager.js
|
||||
@RESPATH@/components/amContentHandler.js
|
||||
@RESPATH@/components/amInstallTrigger.js
|
||||
@RESPATH@/components/amWebAPI.js
|
||||
@RESPATH@/components/amWebInstallListener.js
|
||||
@RESPATH@/components/nsBlocklistService.js
|
||||
@RESPATH@/components/nsBlocklistServiceContent.js
|
||||
|
@ -307,6 +307,27 @@ function getLocale() {
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
function webAPIForAddon(addon) {
|
||||
if (!addon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = {};
|
||||
|
||||
// By default just pass through any plain property, the webidl will control
|
||||
// access.
|
||||
for (let prop in addon) {
|
||||
if (typeof(addon[prop]) != "function") {
|
||||
result[prop] = addon[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// A few properties are computed for a nicer API
|
||||
result.isEnabled = !addon.userDisabled;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class to repeatedly call a listener with each object in an array
|
||||
* optionally checking whether the object has a method in it.
|
||||
@ -2761,6 +2782,16 @@ var AddonManagerInternal = {
|
||||
get hotfixID() {
|
||||
return gHotfixID;
|
||||
},
|
||||
|
||||
webAPI: {
|
||||
getAddonByID(id) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getAddonByID(id, (addon) => {
|
||||
resolve(webAPIForAddon(addon));
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3342,6 +3373,10 @@ this.AddonManager = {
|
||||
return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow);
|
||||
},
|
||||
|
||||
get webAPI() {
|
||||
return AddonManagerInternal.webAPI;
|
||||
},
|
||||
|
||||
get shutdown() {
|
||||
return gShutdownBarrier.client;
|
||||
},
|
||||
|
@ -24,6 +24,9 @@ const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
|
||||
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
|
||||
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
|
||||
|
||||
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
|
||||
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
|
||||
|
||||
const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -38,14 +41,13 @@ function amManager() {
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
/*globals AddonManagerPrivate*/
|
||||
|
||||
let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
let globalMM = Services.mm;
|
||||
globalMM.loadFrameScript(CHILD_SCRIPT, true);
|
||||
globalMM.addMessageListener(MSG_INSTALL_ADDONS, this);
|
||||
|
||||
gParentMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
gParentMM = Services.ppmm;
|
||||
gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
|
||||
gParentMM.addMessageListener(MSG_PROMISE_REQUEST, this);
|
||||
|
||||
// Needed so receiveMessage can be called directly by JS callers
|
||||
this.wrappedJSObject = this;
|
||||
@ -174,6 +176,30 @@ amManager.prototype = {
|
||||
aMessage.target, payload.triggeringPrincipal, payload.uris,
|
||||
payload.hashes, payload.names, payload.icons, callback);
|
||||
}
|
||||
|
||||
case MSG_PROMISE_REQUEST: {
|
||||
let resolve = (value) => {
|
||||
aMessage.target.sendAsyncMessage(MSG_PROMISE_RESULT, {
|
||||
callbackID: payload.callbackID,
|
||||
resolve: value
|
||||
});
|
||||
}
|
||||
let reject = (value) => {
|
||||
aMessage.target.sendAsyncMessage(MSG_PROMISE_RESULT, {
|
||||
callbackID: payload.callbackID,
|
||||
reject: value
|
||||
});
|
||||
}
|
||||
|
||||
let API = AddonManager.webAPI;
|
||||
if (payload.type in API) {
|
||||
API[payload.type](...payload.args).then(resolve, reject);
|
||||
}
|
||||
else {
|
||||
reject("Unknown Add-on API request.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
@ -10,6 +10,83 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
|
||||
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
|
||||
|
||||
const APIBroker = {
|
||||
_nextID: 0,
|
||||
|
||||
init() {
|
||||
this._promises = new Map();
|
||||
|
||||
Services.cpmm.addMessageListener(MSG_PROMISE_RESULT, this);
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
let payload = message.data;
|
||||
|
||||
switch (message.name) {
|
||||
case MSG_PROMISE_RESULT: {
|
||||
if (!this._promises.has(payload.callbackID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { resolve, reject } = this._promises.get(payload.callbackID);
|
||||
this._promises.delete(payload.callbackID);
|
||||
|
||||
if ("resolve" in payload)
|
||||
resolve(payload.resolve);
|
||||
else
|
||||
reject(payload.reject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sendRequest: function(type, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let callbackID = this._nextID++;
|
||||
|
||||
this._promises.set(callbackID, { resolve, reject });
|
||||
Services.cpmm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
APIBroker.init();
|
||||
|
||||
function Addon(properties) {
|
||||
// We trust the webidl binding to broker access to our properties.
|
||||
for (let key of Object.keys(properties)) {
|
||||
this[key] = properties[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API methods should return promises from the page, this is a simple wrapper
|
||||
* to make sure of that. It also automatically wraps objects when necessary.
|
||||
*/
|
||||
function WebAPITask(generator) {
|
||||
let task = Task.async(generator);
|
||||
|
||||
return function(...args) {
|
||||
let win = this.window;
|
||||
|
||||
let wrapForContent = (obj) => {
|
||||
if (obj instanceof Addon) {
|
||||
return win.Addon._create(win, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
return new win.Promise((resolve, reject) => {
|
||||
task(...args).then(wrapForContent)
|
||||
.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function WebAPI() {
|
||||
}
|
||||
|
||||
@ -18,9 +95,10 @@ WebAPI.prototype = {
|
||||
this.window = window;
|
||||
},
|
||||
|
||||
getAddonByID(id) {
|
||||
return this.window.Promise.reject("Not yet implemented");
|
||||
},
|
||||
getAddonByID: WebAPITask(function*(id) {
|
||||
let addonInfo = yield APIBroker.sendRequest("getAddonByID", id);
|
||||
return addonInfo ? new Addon(addonInfo) : null;
|
||||
}),
|
||||
|
||||
classID: Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"),
|
||||
contractID: "@mozilla.org/addon-web-api/manager;1",
|
||||
|
@ -64,6 +64,7 @@ skip-if = require_signing
|
||||
[browser_task_next_test.js]
|
||||
[browser_discovery_install.js]
|
||||
[browser_update.js]
|
||||
[browser_webapi.js]
|
||||
[browser_webapi_access.js]
|
||||
|
||||
[include:browser-common.ini]
|
||||
|
92
toolkit/mozapps/extensions/test/browser/browser_webapi.js
Normal file
92
toolkit/mozapps/extensions/test/browser/browser_webapi.js
Normal file
@ -0,0 +1,92 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
|
||||
|
||||
Services.prefs.setBoolPref("extensions.webapi.testing", true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("extensions.webapi.testing");
|
||||
});
|
||||
|
||||
function testWithAPI(task) {
|
||||
return function*() {
|
||||
yield BrowserTestUtils.withNewTab(TESTPAGE, task);
|
||||
}
|
||||
}
|
||||
|
||||
let gProvider = new MockProvider();
|
||||
|
||||
gProvider.createAddons([{
|
||||
id: "addon1@tests.mozilla.org",
|
||||
name: "Test add-on 1",
|
||||
version: "2.1",
|
||||
description: "Short description",
|
||||
type: "extension",
|
||||
userDisabled: false,
|
||||
isActive: true,
|
||||
}, {
|
||||
id: "addon2@tests.mozilla.org",
|
||||
name: "Test add-on 2",
|
||||
version: "5.3.7ab",
|
||||
description: null,
|
||||
type: "theme",
|
||||
userDisabled: false,
|
||||
isActive: false,
|
||||
}, {
|
||||
id: "addon3@tests.mozilla.org",
|
||||
name: "Test add-on 3",
|
||||
version: "1",
|
||||
description: "Longer description",
|
||||
type: "extension",
|
||||
userDisabled: true,
|
||||
isActive: false,
|
||||
}]);
|
||||
|
||||
function API_getAddonByID(browser, id) {
|
||||
return ContentTask.spawn(browser, id, function*(id) {
|
||||
let addon = yield content.navigator.mozAddonManager.getAddonByID(id);
|
||||
|
||||
// We can't send native objects back so clone its properties.
|
||||
let result = {};
|
||||
for (let prop in addon) {
|
||||
result[prop] = addon[prop];
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(testWithAPI(function*(browser) {
|
||||
function compareObjects(web, real) {
|
||||
for (let prop of Object.keys(web)) {
|
||||
let webVal = web[prop];
|
||||
let realVal = real[prop];
|
||||
|
||||
switch (prop) {
|
||||
case "isEnabled":
|
||||
realVal = !real.userDisabled;
|
||||
break;
|
||||
}
|
||||
|
||||
// null and undefined don't compare well so stringify them first
|
||||
if (realVal === null || realVal === undefined) {
|
||||
realVal = `${realVal}`;
|
||||
webVal = `${webVal}`;
|
||||
}
|
||||
|
||||
is(webVal, realVal, `Property ${prop} should have the right value in add-on ${real.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
let [a1, a2, a3] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
|
||||
"addon2@tests.mozilla.org",
|
||||
"addon3@tests.mozilla.org"]);
|
||||
let w1 = yield API_getAddonByID(browser, "addon1@tests.mozilla.org");
|
||||
let w2 = yield API_getAddonByID(browser, "addon2@tests.mozilla.org");
|
||||
let w3 = yield API_getAddonByID(browser, "addon3@tests.mozilla.org");
|
||||
|
||||
compareObjects(w1, a1);
|
||||
compareObjects(w2, a2);
|
||||
compareObjects(w3, a3);
|
||||
}));
|
Loading…
Reference in New Issue
Block a user