summaryrefslogtreecommitdiffstats
path: root/browser/extensions/search-detection/tests
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/search-detection/tests')
-rw-r--r--browser/extensions/search-detection/tests/browser/.eslintrc.js7
-rw-r--r--browser/extensions/search-detection/tests/browser/browser.ini9
-rw-r--r--browser/extensions/search-detection/tests/browser/browser_client_side_redirection.js204
-rw-r--r--browser/extensions/search-detection/tests/browser/browser_extension_loaded.js15
-rw-r--r--browser/extensions/search-detection/tests/browser/browser_server_side_redirection.js260
-rw-r--r--browser/extensions/search-detection/tests/browser/redirect.sjs32
6 files changed, 527 insertions, 0 deletions
diff --git a/browser/extensions/search-detection/tests/browser/.eslintrc.js b/browser/extensions/search-detection/tests/browser/.eslintrc.js
new file mode 100644
index 0000000000..e57058ecb1
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ env: {
+ webextensions: true,
+ },
+};
diff --git a/browser/extensions/search-detection/tests/browser/browser.ini b/browser/extensions/search-detection/tests/browser/browser.ini
new file mode 100644
index 0000000000..1bd22fe386
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ redirect.sjs
+
+[browser_client_side_redirection.js]
+[browser_extension_loaded.js]
+[browser_server_side_redirection.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
diff --git a/browser/extensions/search-detection/tests/browser/browser_client_side_redirection.js b/browser/extensions/search-detection/tests/browser/browser_client_side_redirection.js
new file mode 100644
index 0000000000..5dad39dba4
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/browser_client_side_redirection.js
@@ -0,0 +1,204 @@
+/* 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 { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+const TELEMETRY_EVENTS_FILTERS = {
+ category: "addonsSearchDetection",
+ method: "etld_change",
+};
+
+// The search-detection built-in add-on registers dynamic events.
+const TELEMETRY_TEST_UTILS_OPTIONS = { clear: true, process: "dynamic" };
+
+async function testClientSideRedirect({
+ background,
+ permissions,
+ telemetryExpected = false,
+}) {
+ Services.telemetry.clearEvents();
+
+ // Load an extension that does a client-side redirect. We expect this
+ // extension to be reported in a Telemetry event when `telemetryExpected` is
+ // set to `true`.
+ const addonId = "some@addon-id";
+ const addonVersion = "1.2.3";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: addonVersion,
+ browser_specific_settings: { gecko: { id: addonId } },
+ permissions,
+ },
+ useAddonManager: "temporary",
+ background,
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("ready");
+
+ // Simulate a search (with the test search engine) by navigating to it.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "https://example.com/search?q=babar",
+ },
+ () => {}
+ );
+
+ await extension.unload();
+
+ TelemetryTestUtils.assertEvents(
+ telemetryExpected
+ ? [
+ {
+ object: "webrequest",
+ value: "extension",
+ extra: {
+ addonId,
+ addonVersion,
+ from: "example.com",
+ to: "mochi.test",
+ },
+ },
+ ]
+ : [],
+ TELEMETRY_EVENTS_FILTERS,
+ TELEMETRY_TEST_UTILS_OPTIONS
+ );
+}
+
+add_setup(async function () {
+ const searchEngineName = "test search engine";
+
+ let searchEngine;
+
+ // This cleanup function has to be registered before the one registered
+ // internally by loadExtension, otherwise it is going to trigger a test
+ // failure (because it will be called too late).
+ registerCleanupFunction(async () => {
+ await searchEngine.unload();
+ ok(
+ !Services.search.getEngineByName(searchEngineName),
+ "test search engine unregistered"
+ );
+ });
+
+ searchEngine = ExtensionTestUtils.loadExtension({
+ manifest: {
+ chrome_settings_overrides: {
+ search_provider: {
+ name: searchEngineName,
+ keyword: "test",
+ search_url: "https://example.com/?q={searchTerms}",
+ },
+ },
+ },
+ // NOTE: the search extension needs to be installed through the
+ // AddonManager to be correctly unregistered when it is uninstalled.
+ useAddonManager: "temporary",
+ });
+
+ await searchEngine.startup();
+ await AddonTestUtils.waitForSearchProviderStartup(searchEngine);
+ ok(
+ Services.search.getEngineByName(searchEngineName),
+ "test search engine registered"
+ );
+});
+
+add_task(function test_onBeforeRequest() {
+ return testClientSideRedirect({
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ () => {
+ return {
+ redirectUrl: "http://mochi.test:8888/",
+ };
+ },
+ { urls: ["*://example.com/*"] },
+ ["blocking"]
+ );
+
+ browser.test.sendMessage("ready");
+ },
+ permissions: ["webRequest", "webRequestBlocking", "*://example.com/*"],
+ telemetryExpected: true,
+ });
+});
+
+add_task(function test_onBeforeRequest_url_not_monitored() {
+ // Here, we load an extension that does a client-side redirect. Because this
+ // extension does not listen to the URL of the search engine registered
+ // above, we don't expect this extension to be reported in a Telemetry event.
+ return testClientSideRedirect({
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ () => {
+ return {
+ redirectUrl: "http://mochi.test:8888/",
+ };
+ },
+ { urls: ["*://google.com/*"] },
+ ["blocking"]
+ );
+
+ browser.test.sendMessage("ready");
+ },
+ permissions: ["webRequest", "webRequestBlocking", "*://google.com/*"],
+ telemetryExpected: false,
+ });
+});
+
+add_task(function test_onHeadersReceived() {
+ return testClientSideRedirect({
+ background() {
+ browser.webRequest.onHeadersReceived.addListener(
+ () => {
+ return {
+ redirectUrl: "http://mochi.test:8888/",
+ };
+ },
+ { urls: ["*://example.com/*"], types: ["main_frame"] },
+ ["blocking"]
+ );
+
+ browser.test.sendMessage("ready");
+ },
+ permissions: ["webRequest", "webRequestBlocking", "*://example.com/*"],
+ telemetryExpected: true,
+ });
+});
+
+add_task(function test_onHeadersReceived_url_not_monitored() {
+ // Here, we load an extension that does a client-side redirect. Because this
+ // extension does not listen to the URL of the search engine registered
+ // above, we don't expect this extension to be reported in a Telemetry event.
+ return testClientSideRedirect({
+ background() {
+ browser.webRequest.onHeadersReceived.addListener(
+ () => {
+ return {
+ redirectUrl: "http://mochi.test:8888/",
+ };
+ },
+ { urls: ["*://google.com/*"], types: ["main_frame"] },
+ ["blocking"]
+ );
+
+ browser.test.sendMessage("ready");
+ },
+ permissions: ["webRequest", "webRequestBlocking", "*://google.com/*"],
+ telemetryExpected: false,
+ });
+});
diff --git a/browser/extensions/search-detection/tests/browser/browser_extension_loaded.js b/browser/extensions/search-detection/tests/browser/browser_extension_loaded.js
new file mode 100644
index 0000000000..65f6ed09a8
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/browser_extension_loaded.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_searchDetection_isActive() {
+ let addon = await AddonManager.getAddonByID(
+ "addons-search-detection@mozilla.com"
+ );
+
+ ok(addon, "Add-on exists");
+ ok(addon.isActive, "Add-on is active");
+ ok(addon.isBuiltin, "Add-on is built-in");
+ ok(addon.hidden, "Add-on is hidden");
+});
diff --git a/browser/extensions/search-detection/tests/browser/browser_server_side_redirection.js b/browser/extensions/search-detection/tests/browser/browser_server_side_redirection.js
new file mode 100644
index 0000000000..ea235406a4
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/browser_server_side_redirection.js
@@ -0,0 +1,260 @@
+/* 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 { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+const TELEMETRY_EVENTS_FILTERS = {
+ category: "addonsSearchDetection",
+ method: "etld_change",
+};
+
+// The search-detection built-in add-on registers dynamic events.
+const TELEMETRY_TEST_UTILS_OPTIONS = { clear: true, process: "dynamic" };
+
+const REDIRECT_SJS =
+ "browser/browser/extensions/search-detection/tests/browser/redirect.sjs?q={searchTerms}";
+// This URL will redirect to `example.net`, which is different than
+// `*.example.com`. That will be the final URL of a redirect chain:
+// www.example.com -> example.net
+const SEARCH_URL_WWW = `https://www.example.com/${REDIRECT_SJS}`;
+// This URL will redirect to `www.example.com`, which will create a redirect
+// chain with two hops:
+// test2.example.com -> www.example.com -> example.net
+const SEARCH_URL_TEST2 = `https://test2.example.com/${REDIRECT_SJS}`;
+// This URL will redirect to `test2.example.com`, which will create a redirect
+// chain with three hops:
+// test1.example.com -> test2.example.com -> www.example.com -> example.net
+const SEARCH_URL_TEST1 = `https://test1.example.com/${REDIRECT_SJS}`;
+
+const TEST_SEARCH_ENGINE_ADDON_ID = "some@addon-id";
+const TEST_SEARCH_ENGINE_ADDON_VERSION = "4.5.6";
+
+const testServerSideRedirect = async ({
+ searchURL,
+ expectedEvents,
+ tabURL,
+}) => {
+ Services.telemetry.clearEvents();
+
+ const searchEngineName = "test search engine";
+ // Load a default search engine because the add-on we are testing here
+ // monitors the search engines.
+ const searchEngine = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: TEST_SEARCH_ENGINE_ADDON_VERSION,
+ browser_specific_settings: {
+ gecko: { id: TEST_SEARCH_ENGINE_ADDON_ID },
+ },
+ chrome_settings_overrides: {
+ search_provider: {
+ name: searchEngineName,
+ keyword: "test",
+ search_url: searchURL,
+ },
+ },
+ },
+ useAddonManager: "temporary",
+ });
+
+ await searchEngine.startup();
+ ok(
+ Services.search.getEngineByName(searchEngineName),
+ "test search engine registered"
+ );
+ await AddonTestUtils.waitForSearchProviderStartup(searchEngine);
+
+ // Simulate a search (with the test search engine) by navigating to it.
+ const url = tabURL || searchURL.replace("{searchTerms}", "some terms");
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ // Wait for the tab to be fully loaded.
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+ });
+
+ await searchEngine.unload();
+ ok(
+ !Services.search.getEngineByName(searchEngineName),
+ "test search engine unregistered"
+ );
+
+ TelemetryTestUtils.assertEvents(
+ expectedEvents,
+ TELEMETRY_EVENTS_FILTERS,
+ TELEMETRY_TEST_UTILS_OPTIONS
+ );
+};
+
+add_task(function test_redirect_final() {
+ return testServerSideRedirect({
+ // www.example.com -> example.net
+ searchURL: SEARCH_URL_WWW,
+ expectedEvents: [
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: TEST_SEARCH_ENGINE_ADDON_ID,
+ addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ ],
+ });
+});
+
+add_task(function test_redirect_two_hops() {
+ return testServerSideRedirect({
+ // test2.example.com -> www.example.com -> example.net
+ searchURL: SEARCH_URL_TEST2,
+ expectedEvents: [
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: TEST_SEARCH_ENGINE_ADDON_ID,
+ addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ ],
+ });
+});
+
+add_task(function test_redirect_three_hops() {
+ return testServerSideRedirect({
+ // test1.example.com -> test2.example.com -> www.example.com -> example.net
+ searchURL: SEARCH_URL_TEST1,
+ expectedEvents: [
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: TEST_SEARCH_ENGINE_ADDON_ID,
+ addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ ],
+ });
+});
+
+add_task(function test_no_event_when_search_engine_not_used() {
+ return testServerSideRedirect({
+ // www.example.com -> example.net
+ searchURL: SEARCH_URL_WWW,
+ // We do not expect any events because the user is not using the search
+ // engine that was registered.
+ tabURL: "http://mochi.test:8888/search?q=foobar",
+ expectedEvents: [],
+ });
+});
+
+add_task(function test_redirect_chain_does_not_start_on_first_request() {
+ return testServerSideRedirect({
+ // www.example.com -> example.net
+ searchURL: SEARCH_URL_WWW,
+ // User first navigates to an URL that isn't monitored and will be
+ // redirected to another URL that is monitored.
+ tabURL: `http://mochi.test:8888/browser/browser/extensions/search-detection/tests/browser/redirect.sjs?q={searchTerms}`,
+ expectedEvents: [
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: TEST_SEARCH_ENGINE_ADDON_ID,
+ addonVersion: TEST_SEARCH_ENGINE_ADDON_VERSION,
+ // We expect this and not `mochi.test` because we do not monitor
+ // `mochi.test`, only `example.com`, which is coming from the search
+ // engine registered in the test setup.
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ ],
+ });
+});
+
+add_task(async function test_two_extensions_reported() {
+ Services.telemetry.clearEvents();
+
+ const searchEngines = [];
+ for (const [addonId, addonVersion, isDefault] of [
+ ["1-addon@guid", "1.2", false],
+ ["2-addon@guid", "3.4", true],
+ ]) {
+ const searchEngine = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: addonVersion,
+ browser_specific_settings: {
+ gecko: { id: addonId },
+ },
+ chrome_settings_overrides: {
+ search_provider: {
+ is_default: isDefault,
+ name: `test search engine - ${addonId}`,
+ keyword: "test",
+ search_url: `${SEARCH_URL_WWW}&id=${addonId}`,
+ },
+ },
+ },
+ useAddonManager: "temporary",
+ });
+
+ await searchEngine.startup();
+ await AddonTestUtils.waitForSearchProviderStartup(searchEngine);
+
+ searchEngines.push(searchEngine);
+ }
+
+ // Simulate a search by navigating to it.
+ const url = SEARCH_URL_WWW.replace("{searchTerms}", "some terms");
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ // Wait for the tab to be fully loaded.
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+ });
+
+ await Promise.all(searchEngines.map(engine => engine.unload()));
+
+ TelemetryTestUtils.assertEvents(
+ [
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: "1-addon@guid",
+ addonVersion: "1.2",
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ {
+ object: "other",
+ value: "server",
+ extra: {
+ addonId: "2-addon@guid",
+ addonVersion: "3.4",
+ from: "example.com",
+ to: "example.net",
+ },
+ },
+ ],
+ TELEMETRY_EVENTS_FILTERS,
+ TELEMETRY_TEST_UTILS_OPTIONS
+ );
+});
diff --git a/browser/extensions/search-detection/tests/browser/redirect.sjs b/browser/extensions/search-detection/tests/browser/redirect.sjs
new file mode 100644
index 0000000000..27cb29b32e
--- /dev/null
+++ b/browser/extensions/search-detection/tests/browser/redirect.sjs
@@ -0,0 +1,32 @@
+const REDIRECT_SJS =
+ "browser/browser/extensions/search-detection/tests/browser/redirect.sjs";
+
+// This handler is used to create redirect chains with multiple sub-domains,
+// and the next hop is defined by the current `host`.
+function handleRequest(request, response) {
+ let newLocation;
+
+ // test1.example.com -> test2.example.com -> www.example.com -> example.net
+ switch (request.host) {
+ case "test1.example.com":
+ newLocation = `https://test2.example.com/${REDIRECT_SJS}`;
+ break;
+ case "test2.example.com":
+ newLocation = `https://www.example.com/${REDIRECT_SJS}`;
+ break;
+ case "www.example.com":
+ newLocation = "https://example.net/";
+ break;
+ // We redirect `mochi.test` to `www` in
+ // `test_redirect_chain_does_not_start_on_first_request()`.
+ case "mochi.test":
+ newLocation = `https://www.example.com/${REDIRECT_SJS}`;
+ break;
+ default:
+ // Redirect to a different website in case of unexpected events.
+ newLocation = "https://mozilla.org/";
+ }
+
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ response.setHeader("Location", newLocation);
+}