Bug 1338713 Extension install telemetry r=bsmedberg,rhelmer

MozReview-Commit-ID: KFd7k7zaDL6

--HG--
extra : rebase_source : 3aabdbafb1c91d49b76813c2400a2e48a3909fff
extra : source : a4b9c0de633a13ce350500f5b618efbc45acf89c
This commit is contained in:
Andrew Swan 2017-02-28 09:08:49 -08:00
Родитель b91c8d76ac
Коммит 97e0cc9666
11 изменённых файлов: 106 добавлений и 29 удалений

Просмотреть файл

@ -172,6 +172,8 @@ add_task(function* () {
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
});
hookExtensionsTelemetry();
let changePromise = new Promise(resolve => {
ExtensionsUI.on("change", function listener() {
ExtensionsUI.off("change", listener);
@ -327,6 +329,9 @@ add_task(function* () {
addon4 = yield AddonManager.getAddonByID(ID4);
is(addon4.userDisabled, false, "Addon 4 should be enabled");
// We should have recorded 1 cancelled followed by 3 accepted sideloads.
expectTelemetry(["sideloadRejected", "sideloadAccepted", "sideloadAccepted", "sideloadAccepted"]);
isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);

Просмотреть файл

@ -52,6 +52,8 @@ add_task(function* setup() {
});
});
hookExtensionsTelemetry();
// Helper function to test background updates.
function* backgroundUpdateTest(url, id, checkIconFn) {
yield SpecialPowers.pushPrefEnv({set: [
@ -163,6 +165,9 @@ function* backgroundUpdateTest(url, id, checkIconFn) {
is(getBadgeStatus(), "", "Addon alert badge should be gone");
// Should have recorded 1 canceled followed by 1 accepted update.
expectTelemetry(["updateRejected", "updateAccepted"]);
addon.uninstall();
yield SpecialPowers.popPrefEnv();
}

Просмотреть файл

@ -39,4 +39,4 @@ async function installSearch(filename) {
EventUtils.synthesizeMouseAtCenter(button, {}, win);
}
add_task(() => testInstallMethod(installSearch));
add_task(() => testInstallMethod(installSearch, "installAmo"));

Просмотреть файл

@ -11,4 +11,4 @@ async function installTrigger(filename) {
});
}
add_task(() => testInstallMethod(installTrigger));
add_task(() => testInstallMethod(installTrigger, "installAmo"));

Просмотреть файл

@ -20,4 +20,4 @@ async function installFile(filename) {
contentWin.gViewController.doCommand("cmd_installFromFile");
}
add_task(() => testInstallMethod(installFile));
add_task(() => testInstallMethod(installFile, "installLocal"));

Просмотреть файл

@ -11,4 +11,4 @@ async function installMozAM(filename) {
});
}
add_task(() => testInstallMethod(installMozAM));
add_task(() => testInstallMethod(installMozAM, "installAmo"));

Просмотреть файл

@ -2,6 +2,8 @@
const BASE = getRootDirectory(gTestPath)
.replace("chrome://mochitests/content/", "https://example.com/");
Cu.import("resource:///modules/ExtensionsUI.jsm");
/**
* Wait for the given PopupNotification to display
*
@ -201,10 +203,13 @@ function checkNotification(panel, checkIcon, permissions) {
* Callable that takes the name of an xpi file to install and
* starts to install it. Should return a Promise that resolves
* when the install is finished or rejects if the install is canceled.
* @param {string} telemetryBase
* If supplied, the base type for telemetry events that should be
* recorded for this install method.
*
* @returns {Promise}
*/
async function testInstallMethod(installFn) {
async function testInstallMethod(installFn, telemetryBase) {
const PERMS_XPI = "browser_webext_permissions.xpi";
const NO_PERMS_XPI = "browser_webext_nopermissions.xpi";
const ID = "permissions@test.mozilla.org";
@ -217,6 +222,10 @@ async function testInstallMethod(installFn) {
["extensions.webextPermissionPrompts", true],
]});
if (telemetryBase !== undefined) {
hookExtensionsTelemetry();
}
let testURI = makeURI("https://example.com/");
Services.perms.add(testURI, "install", Services.perms.ALLOW_ACTION);
registerCleanupFunction(() => Services.perms.remove(testURI, "install"));
@ -316,6 +325,12 @@ async function testInstallMethod(installFn) {
// the extension to clean up.)
await runOnce(PERMS_XPI, false);
if (telemetryBase !== undefined) {
// Should see 2 canceled installs followed by 1 successful install
// for this method.
expectTelemetry([`${telemetryBase}Rejected`, `${telemetryBase}Rejected`, `${telemetryBase}Accepted`]);
}
await SpecialPowers.popPrefEnv();
}
@ -347,3 +362,21 @@ add_task(async function() {
}
});
});
let collectedTelemetry = [];
function hookExtensionsTelemetry() {
let originalHistogram = ExtensionsUI.histogram;
ExtensionsUI.histogram = {
add(value) { collectedTelemetry.push(value); },
};
registerCleanupFunction(() => {
is(collectedTelemetry.length, 0, "No unexamined telemetry after test is finished");
ExtensionsUI.histogram = originalHistogram;
});
}
function expectTelemetry(values) {
Assert.deepEqual(values, collectedTelemetry);
collectedTelemetry = [];
}

Просмотреть файл

@ -33,8 +33,11 @@ this.ExtensionsUI = {
sideloaded: new Set(),
updates: new Set(),
sideloadListener: null,
histogram: null,
init() {
this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT");
Services.obs.addObserver(this, "webextension-permission-prompt", false);
Services.obs.addObserver(this, "webextension-update-permissions", false);
Services.obs.addObserver(this, "webextension-install-notify", false);
@ -88,13 +91,13 @@ this.ExtensionsUI = {
});
},
showAddonsManager(browser, strings, icon) {
showAddonsManager(browser, strings, icon, histkey) {
let global = browser.selectedBrowser.ownerGlobal;
return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.chromeEventHandler;
return this.showPermissionsPrompt(aomBrowser, strings, icon);
return this.showPermissionsPrompt(aomBrowser, strings, icon, histkey);
});
},
@ -108,13 +111,14 @@ this.ExtensionsUI = {
permissions: addon.userPermissions,
type: "sideload",
});
this.showAddonsManager(browser, strings, addon.iconURL).then(answer => {
addon.userDisabled = !answer;
});
this.showAddonsManager(browser, strings, addon.iconURL, "sideload")
.then(answer => {
addon.userDisabled = !answer;
});
},
showUpdate(browser, info) {
this.showAddonsManager(browser, info.strings, info.addon.iconURL)
this.showAddonsManager(browser, info.strings, info.addon.iconURL, "update")
.then(answer => {
if (answer) {
info.resolve();
@ -147,13 +151,27 @@ this.ExtensionsUI = {
return;
}
this.showPermissionsPrompt(target, strings, info.icon).then(answer => {
if (answer) {
info.resolve();
} else {
info.reject();
}
});
let histkey;
if (info.type == "sideload") {
histkey = "sideload";
} else if (info.type == "update") {
histkey = "update";
} else if (info.source == "AMO") {
histkey = "installAmo";
} else if (info.source == "local") {
histkey = "installLocal";
} else {
histkey = "installWeb";
}
this.showPermissionsPrompt(target, strings, info.icon, histkey)
.then(answer => {
if (answer) {
info.resolve();
} else {
info.reject();
}
});
} else if (topic == "webextension-update-permissions") {
let info = subject.wrappedJSObject;
info.type = "update";
@ -307,7 +325,7 @@ this.ExtensionsUI = {
return result;
},
showPermissionsPrompt(browser, strings, icon) {
showPermissionsPrompt(browser, strings, icon, histkey) {
function eventCallback(topic) {
if (topic == "showing") {
let doc = this.browser.ownerDocument;
@ -349,13 +367,19 @@ this.ExtensionsUI = {
let action = {
label: strings.acceptText,
accessKey: strings.acceptKey,
callback: () => resolve(true),
callback: () => {
this.histogram.add(histkey + "Accepted");
resolve(true);
},
};
let secondaryActions = [
{
label: strings.cancelText,
accessKey: strings.cancelKey,
callback: () => resolve(false),
callback: () => {
this.histogram.add(histkey + "Rejected");
resolve(false);
},
},
];

Просмотреть файл

@ -11077,5 +11077,14 @@
"bug_numbers": [1307689],
"description": "Records a value each time a tab that is showing the loading throbber is interrupted. Desktop only.",
"labels": ["stop", "back", "forward", "historyNavigation", "reload", "tabClosed", "newURI"]
},
"EXTENSION_INSTALL_PROMPT_RESULT": {
"alert_emails": ["aswan@mozilla.com", "andym@mozilla.com"],
"bug_numbers": [1338713],
"expires_in_version": "60",
"kind": "categorical",
"labels": ["installAmoAccepted", "installAmoRejected", "installLocalAccepted", "installLocalRejected", "installWebAccepted", "installWebRejected", "sideloadAccepted", "sideloadRejected", "updateAccepted", "updateRejected"],
"description": "Results of displaying add-on installation notifications.",
"releaseChannelCollection": "opt-out"
}
}

Просмотреть файл

@ -2191,17 +2191,17 @@ var AddonManagerInternal = {
// install in that case.
new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
AddonManagerInternal.setupPromptHandler(aBrowser, aInstallingPrincipal.URI, aInstall, true);
let startInstall = (source) => {
AddonManagerInternal.setupPromptHandler(aBrowser, aInstallingPrincipal.URI, aInstall, true, source);
let startInstall = () => {
AddonManagerInternal.startInstall(aBrowser, aInstallingPrincipal.URI, aInstall);
};
if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
this.installNotifyObservers("addon-install-blocked", topBrowser,
aInstallingPrincipal.URI, aInstall,
startInstall);
() => startInstall("other"));
} else {
startInstall();
startInstall("AMO");
}
} catch (e) {
// In the event that the weblistener throws during instantiation or when
@ -2228,7 +2228,7 @@ var AddonManagerInternal = {
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
AddonManagerInternal.setupPromptHandler(browser, uri, install, true);
AddonManagerInternal.setupPromptHandler(browser, uri, install, true, "local");
AddonManagerInternal.startInstall(browser, uri, install);
},
@ -2825,7 +2825,7 @@ var AddonManagerInternal = {
return gHotfixID;
},
setupPromptHandler(browser, url, install, requireConfirm) {
setupPromptHandler(browser, url, install, requireConfirm, source) {
install.promptHandler = info => new Promise((resolve, _reject) => {
let reject = () => {
this.installNotifyObservers("addon-install-cancelled",
@ -2852,7 +2852,7 @@ var AddonManagerInternal = {
let subject = {
wrappedJSObject: {
target: browser,
info: Object.assign({resolve, reject}, info),
info: Object.assign({resolve, reject, source}, info),
}
};
subject.wrappedJSObject.info.permissions = info.addon.userPermissions;
@ -3041,7 +3041,7 @@ var AddonManagerInternal = {
return AddonManagerInternal.getInstallForURL(options.url, "application/x-xpinstall", options.hash)
.then(install => {
AddonManagerInternal.setupPromptHandler(target, null, install, false);
AddonManagerInternal.setupPromptHandler(target, null, install, false, "AMO");
let id = this.nextInstall++;
let {listener, installPromise} = this.makeListener(id, target.messageManager);

Просмотреть файл

@ -665,6 +665,7 @@
.getInterface(Ci.nsIDocShell).chromeEventHandler,
info: {
addon: info.addon,
source: "AMO",
icon: info.addon.iconURL,
permissions: info.addon.userPermissions,
resolve,