357 lines
10 KiB
JavaScript
357 lines
10 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { AddonTestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/AddonTestUtils.sys.mjs"
|
|
);
|
|
|
|
const { PermissionTestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/PermissionTestUtils.sys.mjs"
|
|
);
|
|
|
|
const XPI_URL = `${SECURE_TESTROOT}../xpinstall/amosigned.xpi`;
|
|
const XPI_ADDON_ID = "amosigned-xpi@tests.mozilla.org";
|
|
|
|
AddonTestUtils.initMochitest(this);
|
|
|
|
AddonTestUtils.hookAMTelemetryEvents();
|
|
|
|
add_setup(async function () {
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["extensions.webapi.testing", true],
|
|
["extensions.install.requireBuiltInCerts", false],
|
|
["extensions.InstallTrigger.enabled", true],
|
|
["extensions.InstallTriggerImpl.enabled", true],
|
|
// Relax the user input requirements while running this test.
|
|
["xpinstall.userActivation.required", false],
|
|
],
|
|
});
|
|
|
|
PermissionTestUtils.add(
|
|
"https://example.com/",
|
|
"install",
|
|
Services.perms.ALLOW_ACTION
|
|
);
|
|
|
|
registerCleanupFunction(async () => {
|
|
PermissionTestUtils.remove("https://example.com", "install");
|
|
await SpecialPowers.popPrefEnv();
|
|
});
|
|
});
|
|
|
|
async function testInstallTrigger(
|
|
msg,
|
|
tabURL,
|
|
contentFnArgs,
|
|
contentFn,
|
|
expectedTelemetryInfo,
|
|
expectBlockedOrigin
|
|
) {
|
|
// Clear collected events before each test, otherwise the test would fail
|
|
// intermittently when Glean is going to submit the events and clear them
|
|
// after reaching the max events length limit.
|
|
Services.fog.testResetFOG();
|
|
|
|
await BrowserTestUtils.withNewTab(tabURL, async browser => {
|
|
if (expectBlockedOrigin) {
|
|
const promiseOriginBlocked = TestUtils.topicObserved(
|
|
"addon-install-origin-blocked"
|
|
);
|
|
await SpecialPowers.spawn(browser, contentFnArgs, contentFn);
|
|
const [subject] = await promiseOriginBlocked;
|
|
const installId = subject.wrappedJSObject.installs[0].installId;
|
|
|
|
let gleanEvents = AddonTestUtils.getAMGleanEvents("install", {
|
|
install_id: `${installId}`,
|
|
step: "site_blocked",
|
|
});
|
|
ok(!!gleanEvents.length, "Found Glean events for the blocked install.");
|
|
Assert.deepEqual(
|
|
{ source: gleanEvents[0].source },
|
|
expectedTelemetryInfo,
|
|
`Got expected Glean telemetry on test case "${msg}"`
|
|
);
|
|
|
|
// Select all telemetry events related to the installId.
|
|
const telemetryEvents = AddonTestUtils.getAMTelemetryEvents().filter(
|
|
ev => {
|
|
return (
|
|
ev.method === "install" &&
|
|
ev.value === `${installId}` &&
|
|
ev.extra.step === "site_blocked"
|
|
);
|
|
}
|
|
);
|
|
ok(
|
|
!!telemetryEvents.length,
|
|
"Found telemetry events for the blocked install"
|
|
);
|
|
|
|
const source = telemetryEvents[0]?.extra.source;
|
|
Assert.deepEqual(
|
|
{ source },
|
|
expectedTelemetryInfo,
|
|
`Got expected telemetry on test case "${msg}"`
|
|
);
|
|
return;
|
|
}
|
|
|
|
let installPromptPromise = promisePopupNotificationShown(
|
|
"addon-webext-permissions"
|
|
).then(panel => {
|
|
panel.button.click();
|
|
});
|
|
|
|
let promptPromise = acceptAppMenuNotificationWhenShown(
|
|
"addon-installed",
|
|
XPI_ADDON_ID
|
|
);
|
|
|
|
await SpecialPowers.spawn(browser, contentFnArgs, contentFn);
|
|
|
|
await Promise.all([installPromptPromise, promptPromise]);
|
|
|
|
let addon = await promiseAddonByID(XPI_ADDON_ID);
|
|
|
|
registerCleanupFunction(async () => {
|
|
await addon.uninstall();
|
|
});
|
|
|
|
// Check that the expected installTelemetryInfo has been stored in the
|
|
// addon details.
|
|
AddonTestUtils.checkInstallInfo(
|
|
addon,
|
|
{ method: "installTrigger", ...expectedTelemetryInfo },
|
|
`on "${msg}"`
|
|
);
|
|
|
|
await addon.uninstall();
|
|
});
|
|
}
|
|
|
|
add_task(function testInstallAfterHistoryPushState() {
|
|
return testInstallTrigger(
|
|
"InstallTrigger after history.pushState",
|
|
SECURE_TESTROOT,
|
|
[SECURE_TESTROOT, XPI_URL],
|
|
(secureTestRoot, xpiURL) => {
|
|
// `sourceURL` should match the exact location, even after a location
|
|
// update using the history API. In this case, we update the URL with
|
|
// query parameters and expect `sourceURL` to contain those parameters.
|
|
content.history.pushState(
|
|
{}, // state
|
|
"", // title
|
|
`${secureTestRoot}?some=query&par=am`
|
|
);
|
|
content.InstallTrigger.install({ URL: xpiURL });
|
|
},
|
|
{
|
|
source: "test-host",
|
|
sourceURL:
|
|
"https://example.com/browser/toolkit/mozapps/extensions/test/browser/?some=query&par=am",
|
|
}
|
|
);
|
|
});
|
|
|
|
add_task(async function testInstallTriggerFromSubframe() {
|
|
function runTestCase(msg, tabURL, testFrameAttrs, expected) {
|
|
info(
|
|
`InstallTrigger from iframe test: ${msg} - frame attributes ${JSON.stringify(
|
|
testFrameAttrs
|
|
)}`
|
|
);
|
|
return testInstallTrigger(
|
|
msg,
|
|
tabURL,
|
|
[XPI_URL, testFrameAttrs],
|
|
async (xpiURL, frameAttrs) => {
|
|
const frame = content.document.createElement("iframe");
|
|
if (frameAttrs) {
|
|
for (const attr of Object.keys(frameAttrs)) {
|
|
let value = frameAttrs[attr];
|
|
if (value === "blob:") {
|
|
const blob = new content.Blob(["blob-testpage"]);
|
|
value = content.URL.createObjectURL(blob, "text/html");
|
|
}
|
|
frame[attr] = value;
|
|
}
|
|
}
|
|
const promiseLoaded = new Promise(resolve =>
|
|
frame.addEventListener("load", resolve, { once: true })
|
|
);
|
|
content.document.body.appendChild(frame);
|
|
await promiseLoaded;
|
|
frame.contentWindow.InstallTrigger.install({ URL: xpiURL });
|
|
},
|
|
expected.telemetryInfo,
|
|
expected.blockedOrigin
|
|
);
|
|
}
|
|
|
|
// On Windows "file:///" does not load the default files index html page
|
|
// and the test would get stuck.
|
|
const fileURL = AppConstants.platform === "win" ? "file:///C:/" : "file:///";
|
|
|
|
const expected = {
|
|
http: {
|
|
telemetryInfo: {
|
|
source: "test-host",
|
|
sourceURL:
|
|
"https://example.com/browser/toolkit/mozapps/extensions/test/browser/",
|
|
},
|
|
blockedOrigin: false,
|
|
},
|
|
httpBlob: {
|
|
telemetryInfo: {
|
|
source: "test-host",
|
|
// Example: "blob:https://example.com/BLOB_URL_UUID"
|
|
sourceURL: /^blob:https:\/\/example\.com\//,
|
|
},
|
|
blockedOrigin: false,
|
|
},
|
|
file: {
|
|
telemetryInfo: {
|
|
source: "unknown",
|
|
sourceURL: fileURL,
|
|
},
|
|
blockedOrigin: false,
|
|
},
|
|
fileBlob: {
|
|
telemetryInfo: {
|
|
source: "unknown",
|
|
// Example: "blob:null/BLOB_URL_UUID"
|
|
sourceURL: /^blob:null\//,
|
|
},
|
|
blockedOrigin: false,
|
|
},
|
|
httpBlockedOnOrigin: {
|
|
telemetryInfo: {
|
|
source: "test-host",
|
|
},
|
|
blockedOrigin: true,
|
|
},
|
|
otherBlockedOnOrigin: {
|
|
telemetryInfo: {
|
|
source: "unknown",
|
|
},
|
|
blockedOrigin: true,
|
|
},
|
|
};
|
|
|
|
const testCases = [
|
|
["blank iframe with no attributes", SECURE_TESTROOT, {}, expected.http],
|
|
["iframe srcdoc=''", SECURE_TESTROOT, { srcdoc: "" }, expected.http],
|
|
[
|
|
"http page iframe src='blob:...'",
|
|
SECURE_TESTROOT,
|
|
{ src: "blob:" },
|
|
expected.httpBlob,
|
|
],
|
|
[
|
|
"file page iframe src='blob:...'",
|
|
fileURL,
|
|
{ src: "blob:" },
|
|
expected.fileBlob,
|
|
],
|
|
|
|
// These are blocked by a Firefox doorhanger and the user can't allow it neither.
|
|
[
|
|
"blank iframe embedded into a top-level sandbox page",
|
|
`${SECURE_TESTROOT}sandboxed.html`,
|
|
{},
|
|
expected.httpBlockedOnOrigin,
|
|
],
|
|
[
|
|
"blank iframe with sandbox='allow-scripts'",
|
|
SECURE_TESTROOT,
|
|
{ sandbox: "allow-scripts" },
|
|
expected.httpBlockedOnOrigin,
|
|
],
|
|
[
|
|
"iframe srcdoc='' sandbox='allow-scripts'",
|
|
SECURE_TESTROOT,
|
|
{ srcdoc: "", sandbox: "allow-scripts" },
|
|
expected.httpBlockedOnOrigin,
|
|
],
|
|
[
|
|
"http page iframe src='blob:...' sandbox='allow-scripts'",
|
|
SECURE_TESTROOT,
|
|
{ src: "blob:", sandbox: "allow-scripts" },
|
|
expected.httpBlockedOnOrigin,
|
|
],
|
|
[
|
|
"iframe src='data:...'",
|
|
SECURE_TESTROOT,
|
|
{ src: "data:text/html,data-testpage" },
|
|
expected.httpBlockedOnOrigin,
|
|
],
|
|
[
|
|
"blank frame embedded in a data url",
|
|
"data:text/html,data-testpage",
|
|
{},
|
|
expected.otherBlockedOnOrigin,
|
|
],
|
|
[
|
|
"blank frame embedded into a about:blank page",
|
|
"about:blank",
|
|
{},
|
|
expected.otherBlockedOnOrigin,
|
|
],
|
|
];
|
|
|
|
for (const testCase of testCases) {
|
|
await runTestCase(...testCase);
|
|
}
|
|
});
|
|
|
|
add_task(function testInstallBlankFrameNestedIntoBlobURLPage() {
|
|
return testInstallTrigger(
|
|
"Blank frame nested into a blob url page",
|
|
SECURE_TESTROOT,
|
|
[XPI_URL],
|
|
async xpiURL => {
|
|
const url = content.URL.createObjectURL(
|
|
new content.Blob(["blob-testpage"]),
|
|
"text/html"
|
|
);
|
|
const topframe = content.document.createElement("iframe");
|
|
topframe.src = url;
|
|
const topframeLoaded = new Promise(resolve => {
|
|
topframe.addEventListener("load", resolve, { once: true });
|
|
});
|
|
content.document.body.appendChild(topframe);
|
|
await topframeLoaded;
|
|
const subframe = topframe.contentDocument.createElement("iframe");
|
|
topframe.contentDocument.body.appendChild(subframe);
|
|
subframe.contentWindow.InstallTrigger.install({ URL: xpiURL });
|
|
},
|
|
{
|
|
source: "test-host",
|
|
},
|
|
/* expectBlockedOrigin */ false
|
|
);
|
|
});
|
|
|
|
add_task(function testInstallTriggerTopLevelDataURL() {
|
|
return testInstallTrigger(
|
|
"Blank frame nested into a blob url page",
|
|
"data:text/html,testpage",
|
|
[XPI_URL],
|
|
async xpiURL => {
|
|
this.content.InstallTrigger.install({ URL: xpiURL });
|
|
},
|
|
{
|
|
source: "unknown",
|
|
},
|
|
/* expectBlockedOrigin */ true
|
|
);
|
|
});
|
|
|
|
add_task(function teardown_clearUnexamitedTelemetry() {
|
|
// Clear collected telemetry events when we are not going to run any assertion on them.
|
|
// (otherwise the test will fail because of unexamined telemetry events).
|
|
AddonTestUtils.getAMTelemetryEvents();
|
|
});
|