summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/browser')
-rw-r--r--toolkit/components/extensions/test/browser/.eslintrc.js11
-rw-r--r--toolkit/components/extensions/test/browser/browser-serviceworker.ini9
-rw-r--r--toolkit/components/extensions/test/browser/browser.ini50
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js292
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js122
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js138
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js82
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_management_themes.js149
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_test_mock.js45
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js102
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js34
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js99
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js170
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js161
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js203
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js154
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js185
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js401
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js217
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js66
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js61
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js81
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js57
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js216
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js157
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js249
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js58
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_reset.js112
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js175
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_separators.js69
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js274
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js66
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js50
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js51
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js54
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js38
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js70
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js48
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js145
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js102
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js56
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js107
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js105
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js143
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js94
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js48
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js61
-rw-r--r--toolkit/components/extensions/test/browser/data/test-download.txt1
-rw-r--r--toolkit/components/extensions/test/browser/data/test_downloads_referrer.html10
-rw-r--r--toolkit/components/extensions/test/browser/head.js103
-rw-r--r--toolkit/components/extensions/test/browser/head_serviceworker.js123
51 files changed, 5674 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/browser/.eslintrc.js b/toolkit/components/extensions/test/browser/.eslintrc.js
new file mode 100644
index 0000000000..ef228570e3
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {
+ env: {
+ webextensions: true,
+ },
+
+ rules: {
+ "no-shadow": "off",
+ },
+};
diff --git a/toolkit/components/extensions/test/browser/browser-serviceworker.ini b/toolkit/components/extensions/test/browser/browser-serviceworker.ini
new file mode 100644
index 0000000000..58e9082f7b
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser-serviceworker.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head_serviceworker.js
+ data/**
+
+prefs =
+ extensions.backgroundServiceWorker.enabled=true
+
+[browser_ext_background_serviceworker.js]
diff --git a/toolkit/components/extensions/test/browser/browser.ini b/toolkit/components/extensions/test/browser/browser.ini
new file mode 100644
index 0000000000..0cfb5fcd89
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -0,0 +1,50 @@
+[DEFAULT]
+support-files =
+ head.js
+ data/**
+
+[browser_ext_background_serviceworker_pref_disabled.js]
+[browser_ext_downloads_filters.js]
+[browser_ext_downloads_referrer.js]
+[browser_ext_management_themes.js]
+skip-if = verify
+[browser_ext_test_mock.js]
+[browser_ext_themes_additional_backgrounds_alignment.js]
+[browser_ext_themes_alpha_accentcolor.js]
+[browser_ext_themes_arrowpanels.js]
+[browser_ext_themes_autocomplete_popup.js]
+[browser_ext_themes_chromeparity.js]
+[browser_ext_themes_dynamic_getCurrent.js]
+[browser_ext_themes_dynamic_onUpdated.js]
+[browser_ext_themes_dynamic_updates.js]
+[browser_ext_themes_experiment.js]
+[browser_ext_themes_findbar.js]
+[browser_ext_themes_getCurrent_differentExt.js]
+[browser_ext_themes_highlight.js]
+[browser_ext_themes_incognito.js]
+[browser_ext_themes_lwtsupport.js]
+[browser_ext_themes_multiple_backgrounds.js]
+[browser_ext_themes_ntp_colors.js]
+[browser_ext_themes_ntp_colors_perwindow.js]
+[browser_ext_themes_persistence.js]
+[browser_ext_themes_reset.js]
+[browser_ext_themes_sanitization.js]
+[browser_ext_themes_separators.js]
+[browser_ext_themes_sidebars.js]
+[browser_ext_themes_static_onUpdated.js]
+[browser_ext_themes_tab_line.js]
+[browser_ext_themes_tab_loading.js]
+[browser_ext_themes_tab_selected.js]
+[browser_ext_themes_tab_separators.js]
+[browser_ext_themes_tab_text.js]
+[browser_ext_themes_toolbar_fields_focus.js]
+[browser_ext_themes_toolbar_fields.js]
+[browser_ext_themes_toolbarbutton_colors.js]
+[browser_ext_themes_toolbarbutton_icons.js]
+[browser_ext_themes_toolbars.js]
+[browser_ext_themes_theme_transition.js]
+[browser_ext_themes_warnings.js]
+[browser_ext_thumbnails_bg_extension.js]
+support-files = !/toolkit/components/thumbnails/test/head.js
+[browser_ext_webRequest_redirect_mozextension.js]
+[browser_ext_windows_popup_title.js]
diff --git a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js
new file mode 100644
index 0000000000..a43a49cc0a
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js
@@ -0,0 +1,292 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals getBackgroundServiceWorkerRegistration, waitForServiceWorkerTerminated */
+
+Services.scriptloader.loadSubScript(
+ new URL("head_serviceworker.js", gTestPath).href,
+ this
+);
+
+add_task(assert_background_serviceworker_pref_enabled);
+
+add_task(async function test_serviceWorker_register_guarded_by_pref() {
+ // Test with backgroundServiceWorkeEnable set to true and the
+ // extensions.serviceWorkerRegist.allowed pref set to false.
+ // NOTE: the scenario with backgroundServiceWorkeEnable set to false
+ // is part of "browser_ext_background_serviceworker_pref_disabled.js".
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.serviceWorkerRegister.allowed", false]],
+ });
+
+ let extensionData = {
+ files: {
+ "page.html": "<!DOCTYPE html><script src='page.js'></script>",
+ "page.js": async function() {
+ try {
+ await navigator.serviceWorker.register("sw.js");
+ browser.test.fail(
+ `An extension page should not be able to register a serviceworker successfully`
+ );
+ } catch (err) {
+ browser.test.assertEq(
+ String(err),
+ "SecurityError: The operation is insecure.",
+ "Got the expected error on registering a service worker from a script"
+ );
+ }
+ browser.test.sendMessage("test-serviceWorker-register-disallowed");
+ },
+ "sw.js": "",
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ // Verify that an extension page can't register a moz-extension url
+ // as a service worker.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `moz-extension://${extension.uuid}/page.html`,
+ },
+ async () => {
+ await extension.awaitMessage("test-serviceWorker-register-disallowed");
+ }
+ );
+
+ await extension.unload();
+
+ await SpecialPowers.popPrefEnv();
+
+ // Test again with the pref set to true.
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.serviceWorkerRegister.allowed", true]],
+ });
+
+ extension = ExtensionTestUtils.loadExtension({
+ files: {
+ ...extensionData.files,
+ "page.js": async function() {
+ try {
+ await navigator.serviceWorker.register("sw.js");
+ } catch (err) {
+ browser.test.fail(
+ `Unexpected error on registering a service worker: ${err}`
+ );
+ throw err;
+ } finally {
+ browser.test.sendMessage("test-serviceworker-register-allowed");
+ }
+ },
+ },
+ });
+ await extension.startup();
+
+ // Verify that an extension page can register a moz-extension url
+ // as a service worker if enabled by the related pref.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `moz-extension://${extension.uuid}/page.html`,
+ },
+ async () => {
+ await extension.awaitMessage("test-serviceworker-register-allowed");
+ }
+ );
+
+ await extension.unload();
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_cache_api_allowed() {
+ // Verify that Cache API support for moz-extension url availability is also
+ // conditioned by the extensions.backgroundServiceWorker.enabled pref.
+ // NOTE: the scenario with backgroundServiceWorkeEnable set to false
+ // is part of "browser_ext_background_serviceworker_pref_disabled.js".
+ const extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ try {
+ let cache = await window.caches.open("test-cache-api");
+ browser.test.assertTrue(
+ await window.caches.has("test-cache-api"),
+ "CacheStorage.has should resolve to true"
+ );
+
+ // Test that adding and requesting cached moz-extension urls
+ // works as well.
+ let url = browser.runtime.getURL("file.txt");
+ await cache.add(url);
+ const content = await cache.match(url).then(res => res.text());
+ browser.test.assertEq(
+ "file content",
+ content,
+ "Got the expected content from the cached moz-extension url"
+ );
+
+ // Test that deleting the cache storage works as expected.
+ browser.test.assertTrue(
+ await window.caches.delete("test-cache-api"),
+ "Cache deleted successfully"
+ );
+ browser.test.assertTrue(
+ !(await window.caches.has("test-cache-api")),
+ "CacheStorage.has should resolve to false"
+ );
+ } catch (err) {
+ browser.test.fail(`Unexpected error on using Cache API: ${err}`);
+ throw err;
+ } finally {
+ browser.test.sendMessage("test-cache-api-allowed");
+ }
+ },
+ files: {
+ "file.txt": "file content",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("test-cache-api-allowed");
+ await extension.unload();
+});
+
+function createTestSWScript({ postMessageReply }) {
+ return `
+ self.onmessage = msg => {
+ dump("Background ServiceWorker - onmessage handler\\n");
+ msg.ports[0].postMessage("${postMessageReply}");
+ dump("Background ServiceWorker - postMessage\\n");
+ };
+ dump("Background ServiceWorker - executed\\n");
+ `;
+}
+
+async function testServiceWorker({ extension, expectMessageReply }) {
+ // Verify that the WebExtensions framework has successfully registered the
+ // background service worker declared in the extension manifest.
+ const swRegInfo = getBackgroundServiceWorkerRegistration(extension);
+
+ // Activate the background service worker by exchanging a message
+ // with it.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `moz-extension://${extension.uuid}/page.html`,
+ },
+ async browser => {
+ let msgFromV1 = await SpecialPowers.spawn(
+ browser,
+ [swRegInfo.scriptURL],
+ async url => {
+ const { active } = await content.navigator.serviceWorker.ready;
+ const { port1, port2 } = new content.MessageChannel();
+
+ return new Promise(resolve => {
+ port1.onmessage = msg => resolve(msg.data);
+ active.postMessage("test", [port2]);
+ });
+ }
+ );
+
+ Assert.deepEqual(
+ msgFromV1,
+ expectMessageReply,
+ "Got the expected reply from the extension service worker"
+ );
+ }
+ );
+}
+
+function loadTestExtension({ version }) {
+ const postMessageReply = `reply:sw-v${version}`;
+
+ return ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+ manifest: {
+ version,
+ background: {
+ service_worker: "sw.js",
+ },
+ applications: { gecko: { id: "test-bg-sw@mochi.test" } },
+ },
+ files: {
+ "page.html": "<!DOCTYPE html><body></body>",
+ "sw.js": createTestSWScript({ postMessageReply }),
+ },
+ });
+}
+
+async function assertWorkerIsRunningInExtensionProcess(extension) {
+ // Activate the background service worker by exchanging a message
+ // with it.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `moz-extension://${extension.uuid}/page.html`,
+ },
+ async browser => {
+ const workerScriptURL = `moz-extension://${extension.uuid}/sw.js`;
+ const workerDebuggerURLs = await SpecialPowers.spawn(
+ browser,
+ [workerScriptURL],
+ async url => {
+ await content.navigator.serviceWorker.ready;
+ const wdm = Cc[
+ "@mozilla.org/dom/workers/workerdebuggermanager;1"
+ ].getService(Ci.nsIWorkerDebuggerManager);
+
+ return Array.from(wdm.getWorkerDebuggerEnumerator())
+ .map(wd => {
+ return wd.url;
+ })
+ .filter(swURL => swURL == url);
+ }
+ );
+
+ Assert.deepEqual(
+ workerDebuggerURLs,
+ [workerScriptURL],
+ "The worker should be running in the extension child process"
+ );
+ }
+ );
+}
+
+add_task(async function test_background_serviceworker_with_no_ext_apis() {
+ const extensionV1 = loadTestExtension({ version: "1" });
+ await extensionV1.startup();
+
+ const swRegInfo = getBackgroundServiceWorkerRegistration(extensionV1);
+ const { uuid } = extensionV1;
+
+ await assertWorkerIsRunningInExtensionProcess(extensionV1);
+ await testServiceWorker({
+ extension: extensionV1,
+ expectMessageReply: "reply:sw-v1",
+ });
+
+ // Load a new version of the same addon and verify that the
+ // expected worker script is being executed.
+ const extensionV2 = loadTestExtension({ version: "2" });
+ await extensionV2.startup();
+ is(extensionV2.uuid, uuid, "The extension uuid did not change as expected");
+
+ await testServiceWorker({
+ extension: extensionV2,
+ expectMessageReply: "reply:sw-v2",
+ });
+
+ await Promise.all([
+ extensionV2.unload(),
+ // test extension v1 wrapper has to be unloaded explicitly, otherwise
+ // will be detected as a failure by the test harness.
+ extensionV1.unload(),
+ ]);
+ await waitForServiceWorkerTerminated(swRegInfo);
+ await waitForServiceWorkerRegistrationsRemoved(extensionV2);
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js
new file mode 100644
index 0000000000..a2d9004801
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker_pref_disabled.js
@@ -0,0 +1,122 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function assert_background_serviceworker_pref_disabled() {
+ is(
+ WebExtensionPolicy.backgroundServiceWorkerEnabled,
+ false,
+ "Expect extensions.backgroundServiceWorker.enabled to be false"
+ );
+});
+
+add_task(async function test_background_serviceworker_disallowed() {
+ const id = "test-disallowed-worker@test";
+
+ const extensionData = {
+ manifest: {
+ background: {
+ service_worker: "sw.js",
+ },
+ applicantions: { gecko: { id } },
+ useAddonManager: "temporary",
+ },
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [
+ {
+ message: /Reading manifest: Error processing background: background.service_worker is currently disabled/,
+ },
+ ]);
+ });
+
+ const extension = ExtensionTestUtils.loadExtension(extensionData);
+ await Assert.rejects(
+ extension.startup(),
+ /startup failed/,
+ "Startup failed with background.service_worker while disabled by pref"
+ );
+
+ SimpleTest.endMonitorConsole();
+ await waitForConsole;
+});
+
+add_task(async function test_serviceWorker_register_disallowed() {
+ // Verify that setting extensions.serviceWorkerRegist.allowed pref to false
+ // doesn't allow serviceWorker.register if backgroundServiceWorkeEnable is
+ // set to false
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.serviceWorkerRegister.allowed", true]],
+ });
+
+ let extensionData = {
+ files: {
+ "page.html": "<!DOCTYPE html><script src='page.js'></script>",
+ "page.js": async function() {
+ try {
+ await navigator.serviceWorker.register("sw.js");
+ browser.test.fail(
+ `An extension page should not be able to register a serviceworker successfully`
+ );
+ } catch (err) {
+ browser.test.assertEq(
+ String(err),
+ "SecurityError: The operation is insecure.",
+ "Got the expected error on registering a service worker from a script"
+ );
+ }
+ browser.test.sendMessage("test-serviceWorker-register-disallowed");
+ },
+ "sw.js": "",
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ // Verify that an extension page can't register a moz-extension url
+ // as a service worker.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `moz-extension://${extension.uuid}/page.html`,
+ },
+ async () => {
+ await extension.awaitMessage("test-serviceWorker-register-disallowed");
+ }
+ );
+
+ await extension.unload();
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_cache_api_disallowed() {
+ // Verify that Cache API support for moz-extension url availability is also
+ // conditioned by the extensions.backgroundServiceWorker.enabled pref.
+ const extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ try {
+ await window.caches.open("test-cache-api");
+ browser.test.fail(
+ `An extension page should not be allowed to use the Cache API successfully`
+ );
+ } catch (err) {
+ browser.test.assertEq(
+ String(err),
+ "SecurityError: The operation is insecure.",
+ "Got the expected error on registering a service worker from a script"
+ );
+ } finally {
+ browser.test.sendMessage("test-cache-api-disallowed");
+ }
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("test-cache-api-disallowed");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js
new file mode 100644
index 0000000000..f8672597cd
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js
@@ -0,0 +1,138 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+async function testAppliedFilters(ext, expectedFilter, expectedFilterCount) {
+ let tempDir = FileUtils.getDir(
+ "TmpD",
+ [`testDownloadDir-${Math.random()}`],
+ true
+ );
+
+ let filterCount = 0;
+
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+ MockFilePicker.displayDirectory = tempDir;
+ MockFilePicker.returnValue = MockFilePicker.returnCancel;
+ MockFilePicker.appendFiltersCallback = function(fp, val) {
+ const hexstr = "0x" + ("000" + val.toString(16)).substr(-3);
+ filterCount++;
+ if (filterCount < expectedFilterCount) {
+ is(val, expectedFilter, "Got expected filter: " + hexstr);
+ } else if (filterCount == expectedFilterCount) {
+ is(val, MockFilePicker.filterAll, "Got all files filter: " + hexstr);
+ } else {
+ is(val, null, "Got unexpected filter: " + hexstr);
+ }
+ };
+ MockFilePicker.showCallback = function(fp) {
+ const filename = fp.defaultString;
+ info("MockFilePicker - save as: " + filename);
+ };
+
+ let manifest = {
+ description: ext,
+ permissions: ["downloads"],
+ };
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: manifest,
+
+ background: async function() {
+ let ext = chrome.runtime.getManifest().description;
+ await browser.test.assertRejects(
+ browser.downloads.download({
+ url: "http://any-origin/any-path/any-resource",
+ filename: "any-file" + ext,
+ saveAs: true,
+ }),
+ "Download canceled by the user",
+ "expected request to be canceled"
+ );
+ browser.test.sendMessage("canceled");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("canceled");
+ await extension.unload();
+
+ is(
+ filterCount,
+ expectedFilterCount,
+ "Got correct number of filters: " + filterCount
+ );
+
+ MockFilePicker.cleanup();
+
+ tempDir.remove(true);
+}
+
+// Missing extension
+add_task(async function testDownload_missing_All() {
+ await testAppliedFilters("", null, 1);
+});
+
+// Unrecognized extension
+add_task(async function testDownload_unrecognized_All() {
+ await testAppliedFilters(".xxx", null, 1);
+});
+
+// Recognized extensions
+add_task(async function testDownload_html_HTML() {
+ await testAppliedFilters(".html", Ci.nsIFilePicker.filterHTML, 2);
+});
+
+add_task(async function testDownload_xhtml_HTML() {
+ await testAppliedFilters(".xhtml", Ci.nsIFilePicker.filterHTML, 2);
+});
+
+add_task(async function testDownload_txt_Text() {
+ await testAppliedFilters(".txt", Ci.nsIFilePicker.filterText, 2);
+});
+
+add_task(async function testDownload_text_Text() {
+ await testAppliedFilters(".text", Ci.nsIFilePicker.filterText, 2);
+});
+
+add_task(async function testDownload_jpe_Images() {
+ await testAppliedFilters(".jpe", Ci.nsIFilePicker.filterImages, 2);
+});
+
+add_task(async function testDownload_tif_Images() {
+ await testAppliedFilters(".tif", Ci.nsIFilePicker.filterImages, 2);
+});
+
+add_task(async function testDownload_webp_Images() {
+ await testAppliedFilters(".webp", Ci.nsIFilePicker.filterImages, 2);
+});
+
+add_task(async function testDownload_xml_XML() {
+ await testAppliedFilters(".xml", Ci.nsIFilePicker.filterXML, 2);
+});
+
+add_task(async function testDownload_aac_Audio() {
+ await testAppliedFilters(".aac", Ci.nsIFilePicker.filterAudio, 2);
+});
+
+add_task(async function testDownload_mp3_Audio() {
+ await testAppliedFilters(".mp3", Ci.nsIFilePicker.filterAudio, 2);
+});
+
+add_task(async function testDownload_wma_Audio() {
+ await testAppliedFilters(".wma", Ci.nsIFilePicker.filterAudio, 2);
+});
+
+add_task(async function testDownload_avi_Video() {
+ await testAppliedFilters(".avi", Ci.nsIFilePicker.filterVideo, 2);
+});
+
+add_task(async function testDownload_mp4_Video() {
+ await testAppliedFilters(".mp4", Ci.nsIFilePicker.filterVideo, 2);
+});
+
+add_task(async function testDownload_xvid_Video() {
+ await testAppliedFilters(".xvid", Ci.nsIFilePicker.filterVideo, 2);
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js
new file mode 100644
index 0000000000..b2931e0b6f
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js
@@ -0,0 +1,82 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm"
+);
+
+const URL_PATH = "browser/toolkit/components/extensions/test/browser/data";
+const TEST_URL = `http://example.com/${URL_PATH}/test_downloads_referrer.html`;
+const DOWNLOAD_URL = `http://example.com/${URL_PATH}/test-download.txt`;
+
+async function triggerSaveAs({ selector }) {
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ selector,
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ let saveLinkCommand = window.document.getElementById("context-savelink");
+ saveLinkCommand.doCommand();
+}
+
+add_task(function test_setup() {
+ const tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tempDir.append("test-download-dir");
+ if (!tempDir.exists()) {
+ tempDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+ registerCleanupFunction(function() {
+ MockFilePicker.cleanup();
+
+ if (tempDir.exists()) {
+ tempDir.remove(true);
+ }
+ });
+
+ MockFilePicker.displayDirectory = tempDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("MockFilePicker: shown");
+ const filename = fp.defaultString;
+ info("MockFilePicker: save as " + filename);
+ const destFile = tempDir.clone();
+ destFile.append(filename);
+ MockFilePicker.setFiles([destFile]);
+ info("MockFilePicker: showCallback done");
+ };
+});
+
+add_task(async function test_download_item_referrer_info() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["downloads"],
+ },
+ async background() {
+ browser.downloads.onCreated.addListener(async downloadInfo => {
+ browser.test.sendMessage("download-on-created", downloadInfo);
+ });
+
+ // Call an API method implemented in the parent process to make sure
+ // registering the downloas.onCreated event listener has been completed.
+ await browser.runtime.getBrowserInfo();
+
+ browser.test.sendMessage("bg-page:ready");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("bg-page:ready");
+
+ await BrowserTestUtils.withNewTab({ gBrowser, url: TEST_URL }, async () => {
+ await triggerSaveAs({ selector: "a.test-link" });
+ const downloadInfo = await extension.awaitMessage("download-on-created");
+ is(downloadInfo.url, DOWNLOAD_URL, "Got the expected download url");
+ is(downloadInfo.referrer, TEST_URL, "Got the expected referrer");
+ });
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
new file mode 100644
index 0000000000..f74f418ace
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
@@ -0,0 +1,149 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PromiseTestUtils.jsm"
+);
+PromiseTestUtils.allowMatchingRejectionsGlobally(
+ /Message manager disconnected/
+);
+
+add_task(async function test_management_themes() {
+ const TEST_ID = "test_management_themes@tests.mozilla.com";
+
+ let theme = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name: "Simple theme test",
+ version: "1.0",
+ description: "test theme",
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ useAddonManager: "temporary",
+ });
+
+ async function background(TEST_ID) {
+ browser.management.onInstalled.addListener(info => {
+ if (info.name == TEST_ID) {
+ return;
+ }
+ browser.test.log(`${info.name} was installed`);
+ browser.test.assertEq(info.type, "theme", "addon is theme");
+ browser.test.sendMessage("onInstalled", info.name);
+ });
+ browser.management.onDisabled.addListener(info => {
+ browser.test.log(`${info.name} was disabled`);
+ browser.test.assertEq(info.type, "theme", "addon is theme");
+ browser.test.sendMessage("onDisabled", info.name);
+ });
+ browser.management.onEnabled.addListener(info => {
+ browser.test.log(`${info.name} was enabled`);
+ browser.test.assertEq(info.type, "theme", "addon is theme");
+ browser.test.sendMessage("onEnabled", info.name);
+ });
+ browser.management.onUninstalled.addListener(info => {
+ browser.test.log(`${info.name} was uninstalled`);
+ browser.test.assertEq(info.type, "theme", "addon is theme");
+ browser.test.sendMessage("onUninstalled", info.name);
+ });
+
+ async function getAddon(type) {
+ let addons = await browser.management.getAll();
+ let themes = addons.filter(addon => addon.type === "theme");
+ // We get the 4 built-in themes plus the lwt and our addon.
+ browser.test.assertEq(5, themes.length, "got expected addons");
+ // We should also get our test extension.
+ let testExtension = addons.find(addon => {
+ return addon.id === TEST_ID;
+ });
+ browser.test.assertTrue(
+ !!testExtension,
+ `The extension with id ${TEST_ID} was returned by getAll.`
+ );
+ let found;
+ for (let addon of themes) {
+ browser.test.assertEq(addon.type, "theme", "addon is theme");
+ if (type == "theme" && addon.id.includes("temporary-addon")) {
+ found = addon;
+ } else if (type == "enabled" && addon.enabled) {
+ found = addon;
+ }
+ }
+ return found;
+ }
+
+ browser.test.onMessage.addListener(async msg => {
+ let theme = await getAddon("theme");
+ browser.test.assertEq(
+ theme.description,
+ "test theme",
+ "description is correct"
+ );
+ browser.test.assertTrue(theme.enabled, "theme is enabled");
+ await browser.management.setEnabled(theme.id, false);
+
+ theme = await getAddon("theme");
+
+ browser.test.assertTrue(!theme.enabled, "theme is disabled");
+ let addon = getAddon("enabled");
+ browser.test.assertTrue(addon, "another theme was enabled");
+
+ await browser.management.setEnabled(theme.id, true);
+ theme = await getAddon("theme");
+ addon = await getAddon("enabled");
+ browser.test.assertEq(theme.id, addon.id, "theme is enabled");
+
+ browser.test.sendMessage("done");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ applications: {
+ gecko: {
+ id: TEST_ID,
+ },
+ },
+ name: TEST_ID,
+ permissions: ["management"],
+ },
+ background: `(${background})("${TEST_ID}")`,
+ useAddonManager: "temporary",
+ });
+ await extension.startup();
+
+ await theme.startup();
+ is(
+ await extension.awaitMessage("onInstalled"),
+ "Simple theme test",
+ "webextension theme installed"
+ );
+ is(await extension.awaitMessage("onDisabled"), "Default", "default disabled");
+
+ extension.sendMessage("test");
+ is(await extension.awaitMessage("onEnabled"), "Default", "default enabled");
+ is(
+ await extension.awaitMessage("onDisabled"),
+ "Simple theme test",
+ "addon disabled"
+ );
+ is(
+ await extension.awaitMessage("onEnabled"),
+ "Simple theme test",
+ "addon enabled"
+ );
+ is(await extension.awaitMessage("onDisabled"), "Default", "default disabled");
+ await extension.awaitMessage("done");
+
+ await Promise.all([theme.unload(), extension.awaitMessage("onUninstalled")]);
+
+ is(await extension.awaitMessage("onEnabled"), "Default", "default enabled");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_test_mock.js b/toolkit/components/extensions/test/browser/browser_ext_test_mock.js
new file mode 100644
index 0000000000..fc71cacc66
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_test_mock.js
@@ -0,0 +1,45 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// This test verifies that the extension mocks behave consistently, regardless
+// of test type (xpcshell vs browser test).
+// See also toolkit/components/extensions/test/xpcshell/test_ext_test_mock.js
+
+// Check the state of the extension object. This should be consistent between
+// browser tests and xpcshell tests.
+async function checkExtensionStartupAndUnload(ext) {
+ await ext.startup();
+ Assert.ok(ext.id, "Extension ID should be available");
+ Assert.ok(ext.uuid, "Extension UUID should be available");
+ await ext.unload();
+ // Once set nothing clears the UUID.
+ Assert.ok(ext.uuid, "Extension UUID exists after unload");
+}
+
+add_task(async function test_MockExtension() {
+ // When "useAddonManager" is set, a MockExtension is created in the main
+ // process, which does not necessarily behave identically to an Extension.
+ let ext = ExtensionTestUtils.loadExtension({
+ // xpcshell/test_ext_test_mock.js tests "temporary", so here we use
+ // "permanent" to have even more test coverage.
+ useAddonManager: "permanent",
+ manifest: { applications: { gecko: { id: "@permanent-mock-extension" } } },
+ });
+
+ Assert.ok(!ext.id, "Extension ID is initially unavailable");
+ Assert.ok(!ext.uuid, "Extension UUID is initially unavailable");
+ await checkExtensionStartupAndUnload(ext);
+ Assert.ok(ext.id, "Extension ID exists after unload");
+});
+
+add_task(async function test_generated_Extension() {
+ let ext = ExtensionTestUtils.loadExtension({
+ manifest: {},
+ });
+
+ Assert.ok(!ext.id, "Extension ID is initially unavailable");
+ Assert.ok(!ext.uuid, "Extension UUID is initially unavailable");
+ await checkExtensionStartupAndUnload(ext);
+ Assert.ok(ext.id, "Extension ID exists after unload");
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js
new file mode 100644
index 0000000000..f265a724e5
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_additional_backgrounds_alignment.js
@@ -0,0 +1,102 @@
+"use strict";
+
+// Case 1 - When there is a theme_frame image and additional_backgrounds_alignment is not specified.
+// So background-position should default to "right top"
+add_task(async function test_default_additional_backgrounds_alignment() {
+ const RIGHT_TOP = "100% 0%";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ additional_backgrounds: ["image1.png", "image1.png"],
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = document.documentElement;
+ let rootCS = window.getComputedStyle(docEl);
+
+ Assert.equal(
+ rootCS.getPropertyValue("background-position"),
+ RIGHT_TOP,
+ "root only contains theme_frame alignment property"
+ );
+
+ let toolbox = document.querySelector("#navigator-toolbox");
+ let toolboxCS = window.getComputedStyle(toolbox);
+
+ Assert.equal(
+ toolboxCS.getPropertyValue("background-position"),
+ RIGHT_TOP,
+ toolbox.id +
+ " only contains default additional backgrounds alignment property"
+ );
+
+ await extension.unload();
+});
+
+// Case 2 - When there is a theme_frame image and additional_backgrounds_alignment is specified.
+add_task(async function test_additional_backgrounds_alignment() {
+ const LEFT_BOTTOM = "0% 100%";
+ const CENTER_CENTER = "50% 50%";
+ const RIGHT_TOP = "100% 0%";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ additional_backgrounds: ["image1.png", "image1.png", "image1.png"],
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ properties: {
+ additional_backgrounds_alignment: [
+ "left bottom",
+ "center center",
+ "right top",
+ ],
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = document.documentElement;
+ let rootCS = window.getComputedStyle(docEl);
+
+ Assert.equal(
+ rootCS.getPropertyValue("background-position"),
+ RIGHT_TOP,
+ "root only contains theme_frame alignment property"
+ );
+
+ let toolbox = document.querySelector("#navigator-toolbox");
+ let toolboxCS = window.getComputedStyle(toolbox);
+
+ Assert.equal(
+ toolboxCS.getPropertyValue("background-position"),
+ LEFT_BOTTOM + ", " + CENTER_CENTER + ", " + RIGHT_TOP,
+ toolbox.id + " contains additional backgrounds alignment properties"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js b/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js
new file mode 100644
index 0000000000..65e3a6c9bf
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_alpha_accentcolor.js
@@ -0,0 +1,34 @@
+"use strict";
+
+add_task(async function test_alpha_frame_color() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: "rgba(230, 128, 0, 0.1)",
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ // Add the event listener before loading the extension
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(230, 128, 0)",
+ "Window background color should be opaque"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
new file mode 100644
index 0000000000..e7024b0479
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
@@ -0,0 +1,99 @@
+"use strict";
+
+function openIdentityPopup() {
+ let promise = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == gIdentityHandler._identityPopup
+ );
+ gIdentityHandler._identityBox.click();
+ return promise;
+}
+
+function closeIdentityPopup() {
+ let promise = BrowserTestUtils.waitForEvent(
+ gIdentityHandler._identityPopup,
+ "popuphidden"
+ );
+ gIdentityHandler._identityPopup.hidePopup();
+ return promise;
+}
+
+// This test checks applied WebExtension themes that attempt to change
+// popup properties
+
+add_task(async function test_popup_styling(browser, accDoc) {
+ const POPUP_BACKGROUND_COLOR = "#FF0000";
+ const POPUP_TEXT_COLOR = "#008000";
+ const POPUP_BORDER_COLOR = "#0000FF";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ popup: POPUP_BACKGROUND_COLOR,
+ popup_text: POPUP_TEXT_COLOR,
+ popup_border: POPUP_BORDER_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "https://example.com" },
+ async function(browser) {
+ await extension.startup();
+
+ // Open the information arrow panel
+ await openIdentityPopup();
+
+ let arrowContent = gIdentityHandler._identityPopup.shadowRoot.querySelector(
+ ".panel-arrowcontent"
+ );
+ let arrowContentComputedStyle = window.getComputedStyle(arrowContent);
+ // Ensure popup background color was set properly
+ Assert.equal(
+ arrowContentComputedStyle.getPropertyValue("background-color"),
+ `rgb(${hexToRGB(POPUP_BACKGROUND_COLOR).join(", ")})`,
+ "Popup background color should have been themed"
+ );
+
+ // Ensure popup text color was set properly
+ Assert.equal(
+ arrowContentComputedStyle.getPropertyValue("color"),
+ `rgb(${hexToRGB(POPUP_TEXT_COLOR).join(", ")})`,
+ "Popup text color should have been themed"
+ );
+
+ Assert.equal(
+ arrowContentComputedStyle.getPropertyValue("--panel-description-color"),
+ `rgba(${hexToRGB(POPUP_TEXT_COLOR).join(", ")}, 0.65)`,
+ "Popup text description color should have been themed"
+ );
+
+ // Ensure popup border color was set properly
+ if (AppConstants.platform == "macosx") {
+ Assert.ok(
+ arrowContentComputedStyle
+ .getPropertyValue("box-shadow")
+ .includes(`rgb(${hexToRGB(POPUP_BORDER_COLOR).join(", ")})`),
+ "Popup border color should be set"
+ );
+ } else {
+ testBorderColor(arrowContent, POPUP_BORDER_COLOR);
+ }
+
+ await closeIdentityPopup();
+ await extension.unload();
+ }
+ );
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
new file mode 100644
index 0000000000..2c5a0123f4
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
@@ -0,0 +1,170 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// popup properties are applied correctly to the autocomplete bar.
+const POPUP_COLOR = "#85A400";
+const POPUP_TEXT_COLOR_DARK = "#000000";
+const POPUP_TEXT_COLOR_BRIGHT = "#ffffff";
+const POPUP_SELECTED_COLOR = "#9400ff";
+const POPUP_SELECTED_TEXT_COLOR = "#09b9a6";
+
+const POPUP_URL_COLOR_DARK = "#1c78d4";
+const POPUP_ACTION_COLOR_DARK = "#008f8a";
+const POPUP_URL_COLOR_BRIGHT = "#74c0ff";
+const POPUP_ACTION_COLOR_BRIGHT = "#30e60b";
+
+const SEARCH_TERM = "urlbar-reflows-" + Date.now();
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
+});
+
+add_task(async function setup() {
+ await PlacesUtils.history.clear();
+ const NUM_VISITS = 10;
+ let visits = [];
+
+ for (let i = 0; i < NUM_VISITS; ++i) {
+ visits.push({
+ uri: `http://example.com/urlbar-reflows-${i}`,
+ title: `Reflow test for URL bar entry #${i} - ${SEARCH_TERM}`,
+ });
+ }
+
+ await PlacesTestUtils.addVisits(visits);
+
+ registerCleanupFunction(async function() {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function test_popup_url() {
+ // Load extension with brighttext not set
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field_focus: POPUP_COLOR,
+ toolbar_field_text_focus: POPUP_TEXT_COLOR_DARK,
+ popup_highlight: POPUP_SELECTED_COLOR,
+ popup_highlight_text: POPUP_SELECTED_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ registerCleanupFunction(async function() {
+ await PlacesUtils.history.clear();
+ await BrowserTestUtils.removeTab(tab);
+ });
+
+ let visits = [];
+
+ for (let i = 0; i < maxResults; i++) {
+ visits.push({ uri: makeURI("http://example.com/autocomplete/?" + i) });
+ }
+
+ await PlacesTestUtils.addVisits(visits);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "example.com/autocomplete",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, maxResults - 1);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ maxResults,
+ "Should get maxResults=" + maxResults + " results"
+ );
+
+ // Set the selected attribute to true to test the highlight popup properties
+ UrlbarTestUtils.setSelectedRowIndex(window, 1);
+ let actionResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ let urlResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ let resultCS = window.getComputedStyle(urlResult.element.row._content);
+
+ Assert.equal(
+ resultCS.backgroundColor,
+ `rgb(${hexToRGB(POPUP_SELECTED_COLOR).join(", ")})`,
+ `Popup highlight background color should be set to ${POPUP_SELECTED_COLOR}`
+ );
+
+ Assert.equal(
+ resultCS.color,
+ `rgb(${hexToRGB(POPUP_SELECTED_TEXT_COLOR).join(", ")})`,
+ `Popup highlight color should be set to ${POPUP_SELECTED_TEXT_COLOR}`
+ );
+
+ // Now set the index to somewhere not on the first two, so that we can test both
+ // url and action text colors.
+ UrlbarTestUtils.setSelectedRowIndex(window, 2);
+
+ Assert.equal(
+ window.getComputedStyle(urlResult.element.url).color,
+ `rgb(${hexToRGB(POPUP_URL_COLOR_DARK).join(", ")})`,
+ `Urlbar popup url color should be set to ${POPUP_URL_COLOR_DARK}`
+ );
+
+ Assert.equal(
+ window.getComputedStyle(actionResult.element.action).color,
+ `rgb(${hexToRGB(POPUP_ACTION_COLOR_DARK).join(", ")})`,
+ `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_DARK}`
+ );
+
+ await extension.unload();
+
+ // Load a manifest with popup_text being bright. Test for bright text properties.
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field_focus: POPUP_COLOR,
+ toolbar_field_text_focus: POPUP_TEXT_COLOR_BRIGHT,
+ popup_highlight: POPUP_SELECTED_COLOR,
+ popup_highlight_text: POPUP_SELECTED_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ Assert.equal(
+ window.getComputedStyle(urlResult.element.url).color,
+ `rgb(${hexToRGB(POPUP_URL_COLOR_BRIGHT).join(", ")})`,
+ `Urlbar popup url color should be set to ${POPUP_URL_COLOR_BRIGHT}`
+ );
+
+ Assert.equal(
+ window.getComputedStyle(actionResult.element.action).color,
+ `rgb(${hexToRGB(POPUP_ACTION_COLOR_BRIGHT).join(", ")})`,
+ `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_BRIGHT}`
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
new file mode 100644
index 0000000000..4366764a20
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_chromeparity.js
@@ -0,0 +1,161 @@
+"use strict";
+
+add_task(async function test_support_theme_frame() {
+ const FRAME_COLOR = [71, 105, 91];
+ const TAB_TEXT_COLOR = [0, 0, 0];
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "face.png",
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ tab_background_text: TAB_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+
+ Assert.ok(
+ docEl.hasAttribute("lwtheme-image"),
+ "LWT image attribute should be set"
+ );
+
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "dark",
+ "LWT text color attribute should be set"
+ );
+
+ let style = window.getComputedStyle(docEl);
+ Assert.ok(
+ style.backgroundImage.includes("face.png"),
+ `The backgroundImage should use face.png. Actual value is: ${style.backgroundImage}`
+ );
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(" + FRAME_COLOR.join(", ") + ")",
+ "Expected correct background color"
+ );
+ Assert.equal(
+ style.color,
+ "rgb(" + TAB_TEXT_COLOR.join(", ") + ")",
+ "Expected correct text color"
+ );
+
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+
+ Assert.ok(
+ !docEl.hasAttribute("lwtheme-image"),
+ "LWT image attribute should not be set"
+ );
+
+ Assert.ok(
+ !docEl.hasAttribute("lwthemetextcolor"),
+ "LWT text color attribute should not be set"
+ );
+});
+
+add_task(async function test_support_theme_frame_inactive() {
+ const FRAME_COLOR = [71, 105, 91];
+ const FRAME_COLOR_INACTIVE = [255, 0, 0];
+ const TAB_TEXT_COLOR = [207, 221, 192];
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ frame_inactive: FRAME_COLOR_INACTIVE,
+ tab_background_text: TAB_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(" + FRAME_COLOR.join(", ") + ")",
+ "Window background is set to the colors.frame property"
+ );
+
+ // Now we'll open a new window to see if the inactive browser accent color changed
+ let window2 = await BrowserTestUtils.openNewBrowserWindow();
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(" + FRAME_COLOR_INACTIVE.join(", ") + ")",
+ `Inactive window background color should be ${FRAME_COLOR_INACTIVE}`
+ );
+
+ await BrowserTestUtils.closeWindow(window2);
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
+
+add_task(async function test_lack_of_theme_frame_inactive() {
+ const FRAME_COLOR = [71, 105, 91];
+ const TAB_TEXT_COLOR = [207, 221, 192];
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ tab_background_text: TAB_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(" + FRAME_COLOR.join(", ") + ")",
+ "Window background is set to the colors.frame property"
+ );
+
+ // Now we'll open a new window to make sure the inactive browser accent color stayed the same
+ let window2 = await BrowserTestUtils.openNewBrowserWindow();
+
+ Assert.equal(
+ style.backgroundColor,
+ "rgb(" + FRAME_COLOR.join(", ") + ")",
+ "Inactive window background should not change if colors.frame_inactive isn't set"
+ );
+
+ await BrowserTestUtils.closeWindow(window2);
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js
new file mode 100644
index 0000000000..4a379edfbf
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_getCurrent.js
@@ -0,0 +1,203 @@
+"use strict";
+
+// This test checks whether browser.theme.getCurrent() works correctly in different
+// configurations and with different parameter.
+
+// PNG image data for a simple red dot.
+const BACKGROUND_1 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+// PNG image data for the Mozilla dino head.
+const BACKGROUND_2 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+
+add_task(async function test_get_current() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ const ACCENT_COLOR_1 = "#a14040";
+ const TEXT_COLOR_1 = "#fac96e";
+
+ const ACCENT_COLOR_2 = "#03fe03";
+ const TEXT_COLOR_2 = "#0ef325";
+
+ const theme1 = {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_1,
+ tab_background_text: TEXT_COLOR_1,
+ },
+ };
+
+ const theme2 = {
+ images: {
+ theme_frame: "image2.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_2,
+ tab_background_text: TEXT_COLOR_2,
+ },
+ };
+
+ function ensureWindowFocused(winId) {
+ browser.test.log("Waiting for focused window to be " + winId);
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async resolve => {
+ let listener = windowId => {
+ if (windowId === winId) {
+ browser.windows.onFocusChanged.removeListener(listener);
+ resolve();
+ }
+ };
+ // We first add a listener and then check whether the window is
+ // focused using .get(), because the .get() Promise resolving
+ // could race with the listener running, in which case we'd
+ // never be notified.
+ browser.windows.onFocusChanged.addListener(listener);
+ let { focused } = await browser.windows.get(winId);
+ if (focused) {
+ browser.windows.onFocusChanged.removeListener(listener);
+ resolve();
+ }
+ });
+ }
+
+ function testTheme1(returnedTheme) {
+ browser.test.assertTrue(
+ returnedTheme.images.theme_frame.includes("image1.png"),
+ "Theme 1 theme_frame image should be applied"
+ );
+ browser.test.assertEq(
+ ACCENT_COLOR_1,
+ returnedTheme.colors.frame,
+ "Theme 1 frame color should be applied"
+ );
+ browser.test.assertEq(
+ TEXT_COLOR_1,
+ returnedTheme.colors.tab_background_text,
+ "Theme 1 tab_background_text color should be applied"
+ );
+ }
+
+ function testTheme2(returnedTheme) {
+ browser.test.assertTrue(
+ returnedTheme.images.theme_frame.includes("image2.png"),
+ "Theme 2 theme_frame image should be applied"
+ );
+ browser.test.assertEq(
+ ACCENT_COLOR_2,
+ returnedTheme.colors.frame,
+ "Theme 2 frame color should be applied"
+ );
+ browser.test.assertEq(
+ TEXT_COLOR_2,
+ returnedTheme.colors.tab_background_text,
+ "Theme 2 tab_background_text color should be applied"
+ );
+ }
+
+ function testEmptyTheme(returnedTheme) {
+ browser.test.assertEq(
+ JSON.stringify({ colors: null, images: null, properties: null }),
+ JSON.stringify(returnedTheme),
+ JSON.stringify(returnedTheme, null, 2)
+ );
+ }
+
+ browser.test.log("Testing getCurrent() with initial unthemed window");
+ const firstWin = await browser.windows.getCurrent();
+ testEmptyTheme(await browser.theme.getCurrent());
+ testEmptyTheme(await browser.theme.getCurrent(firstWin.id));
+
+ browser.test.log("Testing getCurrent() with after theme.update()");
+ await browser.theme.update(theme1);
+ testTheme1(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+
+ browser.test.log(
+ "Testing getCurrent() with after theme.update(windowId)"
+ );
+ const secondWin = await browser.windows.create();
+ await ensureWindowFocused(secondWin.id);
+ await browser.theme.update(secondWin.id, theme2);
+ testTheme2(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log("Testing getCurrent() after window focus change");
+ let focusChanged = ensureWindowFocused(firstWin.id);
+ await browser.windows.update(firstWin.id, { focused: true });
+ await focusChanged;
+ testTheme1(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log(
+ "Testing getCurrent() after another window focus change"
+ );
+ focusChanged = ensureWindowFocused(secondWin.id);
+ await browser.windows.update(secondWin.id, { focused: true });
+ await focusChanged;
+ testTheme2(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log("Testing getCurrent() after theme.reset(windowId)");
+ await browser.theme.reset(firstWin.id);
+ testTheme2(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log(
+ "Testing getCurrent() after reset and window focus change"
+ );
+ focusChanged = ensureWindowFocused(firstWin.id);
+ await browser.windows.update(firstWin.id, { focused: true });
+ await focusChanged;
+ testTheme1(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log("Testing getCurrent() after theme.update(windowId)");
+ await browser.theme.update(firstWin.id, theme1);
+ testTheme1(await browser.theme.getCurrent());
+ testTheme1(await browser.theme.getCurrent(firstWin.id));
+ testTheme2(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log("Testing getCurrent() after theme.reset()");
+ await browser.theme.reset();
+ testEmptyTheme(await browser.theme.getCurrent());
+ testEmptyTheme(await browser.theme.getCurrent(firstWin.id));
+ testEmptyTheme(await browser.theme.getCurrent(secondWin.id));
+
+ browser.test.log("Testing getCurrent() after closing a window");
+ await browser.windows.remove(secondWin.id);
+ testEmptyTheme(await browser.theme.getCurrent());
+ testEmptyTheme(await browser.theme.getCurrent(firstWin.id));
+
+ browser.test.log("Testing update calls with invalid window ID");
+ await browser.test.assertRejects(
+ browser.theme.reset(secondWin.id),
+ /Invalid window/,
+ "Invalid window should throw"
+ );
+ await browser.test.assertRejects(
+ browser.theme.update(secondWin.id, theme2),
+ /Invalid window/,
+ "Invalid window should throw"
+ );
+ browser.test.notifyPass("get_current");
+ },
+ manifest: {
+ permissions: ["theme"],
+ },
+ files: {
+ "image1.png": BACKGROUND_1,
+ "image2.png": BACKGROUND_2,
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("get_current");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js
new file mode 100644
index 0000000000..34c7162810
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_onUpdated.js
@@ -0,0 +1,154 @@
+"use strict";
+
+// This test checks whether browser.theme.onUpdated works correctly with different
+// types of dynamic theme updates.
+
+// PNG image data for a simple red dot.
+const BACKGROUND_1 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+// PNG image data for the Mozilla dino head.
+const BACKGROUND_2 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+
+add_task(async function test_on_updated() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ const ACCENT_COLOR_1 = "#a14040";
+ const TEXT_COLOR_1 = "#fac96e";
+
+ const ACCENT_COLOR_2 = "#03fe03";
+ const TEXT_COLOR_2 = "#0ef325";
+
+ const theme1 = {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_1,
+ tab_background_text: TEXT_COLOR_1,
+ },
+ };
+
+ const theme2 = {
+ images: {
+ theme_frame: "image2.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_2,
+ tab_background_text: TEXT_COLOR_2,
+ },
+ };
+
+ function testTheme1(returnedTheme) {
+ browser.test.assertTrue(
+ returnedTheme.images.theme_frame.includes("image1.png"),
+ "Theme 1 theme_frame image should be applied"
+ );
+ browser.test.assertEq(
+ ACCENT_COLOR_1,
+ returnedTheme.colors.frame,
+ "Theme 1 frame color should be applied"
+ );
+ browser.test.assertEq(
+ TEXT_COLOR_1,
+ returnedTheme.colors.tab_background_text,
+ "Theme 1 tab_background_text color should be applied"
+ );
+ }
+
+ function testTheme2(returnedTheme) {
+ browser.test.assertTrue(
+ returnedTheme.images.theme_frame.includes("image2.png"),
+ "Theme 2 theme_frame image should be applied"
+ );
+ browser.test.assertEq(
+ ACCENT_COLOR_2,
+ returnedTheme.colors.frame,
+ "Theme 2 frame color should be applied"
+ );
+ browser.test.assertEq(
+ TEXT_COLOR_2,
+ returnedTheme.colors.tab_background_text,
+ "Theme 2 tab_background_text color should be applied"
+ );
+ }
+
+ const firstWin = await browser.windows.getCurrent();
+ const secondWin = await browser.windows.create();
+
+ const onceThemeUpdated = () =>
+ new Promise(resolve => {
+ const listener = updateInfo => {
+ browser.theme.onUpdated.removeListener(listener);
+ resolve(updateInfo);
+ };
+ browser.theme.onUpdated.addListener(listener);
+ });
+
+ browser.test.log("Testing update with no windowId parameter");
+ let updateInfo1 = onceThemeUpdated();
+ await browser.theme.update(theme1);
+ updateInfo1 = await updateInfo1;
+ testTheme1(updateInfo1.theme);
+ browser.test.assertTrue(
+ !updateInfo1.windowId,
+ "No window id on first update"
+ );
+
+ browser.test.log("Testing update with windowId parameter");
+ let updateInfo2 = onceThemeUpdated();
+ await browser.theme.update(secondWin.id, theme2);
+ updateInfo2 = await updateInfo2;
+ testTheme2(updateInfo2.theme);
+ browser.test.assertEq(
+ secondWin.id,
+ updateInfo2.windowId,
+ "window id on second update"
+ );
+
+ browser.test.log("Testing reset with windowId parameter");
+ let updateInfo3 = onceThemeUpdated();
+ await browser.theme.reset(firstWin.id);
+ updateInfo3 = await updateInfo3;
+ browser.test.assertEq(
+ 0,
+ Object.keys(updateInfo3.theme).length,
+ "Empty theme given on reset"
+ );
+ browser.test.assertEq(
+ firstWin.id,
+ updateInfo3.windowId,
+ "window id on third update"
+ );
+
+ browser.test.log("Testing reset with no windowId parameter");
+ let updateInfo4 = onceThemeUpdated();
+ await browser.theme.reset();
+ updateInfo4 = await updateInfo4;
+ browser.test.assertEq(
+ 0,
+ Object.keys(updateInfo4.theme).length,
+ "Empty theme given on reset"
+ );
+ browser.test.assertTrue(
+ !updateInfo4.windowId,
+ "no window id on fourth update"
+ );
+
+ browser.test.log("Cleaning up test");
+ await browser.windows.remove(secondWin.id);
+ browser.test.notifyPass("onUpdated");
+ },
+ manifest: {
+ permissions: ["theme"],
+ },
+ files: {
+ "image1.png": BACKGROUND_1,
+ "image2.png": BACKGROUND_2,
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("onUpdated");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
new file mode 100644
index 0000000000..34e719262d
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
@@ -0,0 +1,185 @@
+"use strict";
+
+// PNG image data for a simple red dot.
+const BACKGROUND_1 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+const ACCENT_COLOR_1 = "#a14040";
+const TEXT_COLOR_1 = "#fac96e";
+
+// PNG image data for the Mozilla dino head.
+const BACKGROUND_2 =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+const ACCENT_COLOR_2 = "#03fe03";
+const TEXT_COLOR_2 = "#0ef325";
+
+function hexToRGB(hex) {
+ hex = parseInt(hex.indexOf("#") > -1 ? hex.substring(1) : hex, 16);
+ return (
+ "rgb(" + [hex >> 16, (hex & 0x00ff00) >> 8, hex & 0x0000ff].join(", ") + ")"
+ );
+}
+
+function validateTheme(backgroundImage, accentColor, textColor, isLWT) {
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ if (isLWT) {
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+ }
+
+ Assert.ok(
+ style.backgroundImage.includes(backgroundImage),
+ "Expected correct background image"
+ );
+ if (accentColor.startsWith("#")) {
+ accentColor = hexToRGB(accentColor);
+ }
+ if (textColor.startsWith("#")) {
+ textColor = hexToRGB(textColor);
+ }
+ Assert.equal(
+ style.backgroundColor,
+ accentColor,
+ "Expected correct accent color"
+ );
+ Assert.equal(style.color, textColor, "Expected correct text color");
+}
+
+add_task(async function test_dynamic_theme_updates() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ files: {
+ "image1.png": BACKGROUND_1,
+ "image2.png": BACKGROUND_2,
+ },
+ background() {
+ browser.test.onMessage.addListener((msg, details) => {
+ if (msg === "update-theme") {
+ browser.theme.update(details).then(() => {
+ browser.test.sendMessage("theme-updated");
+ });
+ } else {
+ browser.theme.reset().then(() => {
+ browser.test.sendMessage("theme-reset");
+ });
+ }
+ });
+ },
+ });
+
+ let defaultStyle = window.getComputedStyle(window.document.documentElement);
+ await extension.startup();
+
+ extension.sendMessage("update-theme", {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_1,
+ tab_background_text: TEXT_COLOR_1,
+ },
+ });
+
+ await extension.awaitMessage("theme-updated");
+
+ validateTheme("image1.png", ACCENT_COLOR_1, TEXT_COLOR_1, true);
+
+ // Check with the LWT aliases (to update on Firefox 69, because the
+ // LWT aliases are going to be removed).
+ extension.sendMessage("update-theme", {
+ images: {
+ theme_frame: "image2.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR_2,
+ tab_background_text: TEXT_COLOR_2,
+ },
+ });
+
+ await extension.awaitMessage("theme-updated");
+
+ validateTheme("image2.png", ACCENT_COLOR_2, TEXT_COLOR_2, true);
+
+ extension.sendMessage("reset-theme");
+
+ await extension.awaitMessage("theme-reset");
+
+ let { backgroundImage, backgroundColor, color } = defaultStyle;
+ validateTheme(backgroundImage, backgroundColor, color, false);
+
+ await extension.unload();
+
+ let docEl = window.document.documentElement;
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
+
+add_task(async function test_dynamic_theme_updates_with_data_url() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ background() {
+ browser.test.onMessage.addListener((msg, details) => {
+ if (msg === "update-theme") {
+ browser.theme.update(details).then(() => {
+ browser.test.sendMessage("theme-updated");
+ });
+ } else {
+ browser.theme.reset().then(() => {
+ browser.test.sendMessage("theme-reset");
+ });
+ }
+ });
+ },
+ });
+
+ let defaultStyle = window.getComputedStyle(window.document.documentElement);
+ await extension.startup();
+
+ extension.sendMessage("update-theme", {
+ images: {
+ theme_frame: BACKGROUND_1,
+ },
+ colors: {
+ frame: ACCENT_COLOR_1,
+ tab_background_text: TEXT_COLOR_1,
+ },
+ });
+
+ await extension.awaitMessage("theme-updated");
+
+ validateTheme(BACKGROUND_1, ACCENT_COLOR_1, TEXT_COLOR_1, true);
+
+ extension.sendMessage("update-theme", {
+ images: {
+ theme_frame: BACKGROUND_2,
+ },
+ colors: {
+ frame: ACCENT_COLOR_2,
+ tab_background_text: TEXT_COLOR_2,
+ },
+ });
+
+ await extension.awaitMessage("theme-updated");
+
+ validateTheme(BACKGROUND_2, ACCENT_COLOR_2, TEXT_COLOR_2, true);
+
+ extension.sendMessage("reset-theme");
+
+ await extension.awaitMessage("theme-reset");
+
+ let { backgroundImage, backgroundColor, color } = defaultStyle;
+ validateTheme(backgroundImage, backgroundColor, color, false);
+
+ await extension.unload();
+
+ let docEl = window.document.documentElement;
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js b/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js
new file mode 100644
index 0000000000..7b362498e7
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_experiment.js
@@ -0,0 +1,401 @@
+"use strict";
+
+const { AddonSettings } = ChromeUtils.import(
+ "resource://gre/modules/addons/AddonSettings.jsm"
+);
+
+// This test checks whether the theme experiments work
+add_task(async function test_experiment_static_theme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ some_color_property: "#ff00ff",
+ },
+ images: {
+ some_image_property: "background.jpg",
+ },
+ properties: {
+ some_random_property: "no-repeat",
+ },
+ },
+ theme_experiment: {
+ colors: {
+ some_color_property: "--some-color-property",
+ },
+ images: {
+ some_image_property: "--some-image-property",
+ },
+ properties: {
+ some_random_property: "--some-random-property",
+ },
+ },
+ },
+ });
+
+ const root = window.document.documentElement;
+
+ is(
+ root.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ root.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ root.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+
+ await extension.startup();
+
+ const testExperimentApplied = rootEl => {
+ if (AddonSettings.EXPERIMENTS_ENABLED) {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ hexToCSS("#ff00ff"),
+ "Color property should be parsed and set."
+ );
+ ok(
+ rootEl.style
+ .getPropertyValue("--some-image-property")
+ .startsWith("url("),
+ "Image property should be parsed."
+ );
+ ok(
+ rootEl.style
+ .getPropertyValue("--some-image-property")
+ .endsWith("background.jpg)"),
+ "Image property should be set."
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "no-repeat",
+ "Generic Property should be set."
+ );
+ } else {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+ }
+ };
+
+ info("Testing that current window updated with the experiment applied");
+ testExperimentApplied(root);
+
+ info("Testing that new window initialized with the experiment applied");
+ const newWindow = await BrowserTestUtils.openNewBrowserWindow();
+ const newWindowRoot = newWindow.document.documentElement;
+ testExperimentApplied(newWindowRoot);
+
+ await extension.unload();
+
+ info("Testing that both windows unapplied the experiment");
+ for (const rootEl of [root, newWindowRoot]) {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+ }
+ await BrowserTestUtils.closeWindow(newWindow);
+});
+
+add_task(async function test_experiment_dynamic_theme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ theme_experiment: {
+ colors: {
+ some_color_property: "--some-color-property",
+ },
+ images: {
+ some_image_property: "--some-image-property",
+ },
+ properties: {
+ some_random_property: "--some-random-property",
+ },
+ },
+ },
+ background() {
+ const theme = {
+ colors: {
+ some_color_property: "#ff00ff",
+ },
+ images: {
+ some_image_property: "background.jpg",
+ },
+ properties: {
+ some_random_property: "no-repeat",
+ },
+ };
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "update-theme") {
+ browser.theme.update(theme).then(() => {
+ browser.test.sendMessage("theme-updated");
+ });
+ } else {
+ browser.theme.reset().then(() => {
+ browser.test.sendMessage("theme-reset");
+ });
+ }
+ });
+ },
+ });
+
+ await extension.startup();
+
+ const root = window.document.documentElement;
+
+ is(
+ root.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ root.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ root.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+
+ extension.sendMessage("update-theme");
+ await extension.awaitMessage("theme-updated");
+
+ const testExperimentApplied = rootEl => {
+ if (AddonSettings.EXPERIMENTS_ENABLED) {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ hexToCSS("#ff00ff"),
+ "Color property should be parsed and set."
+ );
+ ok(
+ rootEl.style
+ .getPropertyValue("--some-image-property")
+ .startsWith("url("),
+ "Image property should be parsed."
+ );
+ ok(
+ rootEl.style
+ .getPropertyValue("--some-image-property")
+ .endsWith("background.jpg)"),
+ "Image property should be set."
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "no-repeat",
+ "Generic Property should be set."
+ );
+ } else {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+ }
+ };
+ testExperimentApplied(root);
+
+ const newWindow = await BrowserTestUtils.openNewBrowserWindow();
+ const newWindowRoot = newWindow.document.documentElement;
+
+ testExperimentApplied(newWindowRoot);
+
+ extension.sendMessage("reset-theme");
+ await extension.awaitMessage("theme-reset");
+
+ for (const rootEl of [root, newWindowRoot]) {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+ }
+
+ extension.sendMessage("update-theme");
+ await extension.awaitMessage("theme-updated");
+
+ testExperimentApplied(root);
+ testExperimentApplied(newWindowRoot);
+
+ await extension.unload();
+
+ for (const rootEl of [root, newWindowRoot]) {
+ is(
+ rootEl.style.getPropertyValue("--some-color-property"),
+ "",
+ "Color property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-image-property"),
+ "",
+ "Image property should be unset"
+ );
+ is(
+ rootEl.style.getPropertyValue("--some-random-property"),
+ "",
+ "Generic Property should be unset."
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(newWindow);
+});
+
+add_task(async function test_experiment_stylesheet() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ menu_button_background: "#ff00ff",
+ },
+ },
+ theme_experiment: {
+ stylesheet: "experiment.css",
+ colors: {
+ menu_button_background: "--menu-button-background",
+ },
+ },
+ },
+ files: {
+ "experiment.css": `#PanelUI-menu-button {
+ background-color: var(--menu-button-background);
+ fill: white;
+ }`,
+ },
+ });
+
+ const root = window.document.documentElement;
+ const menuButton = document.getElementById("PanelUI-menu-button");
+ const computedStyle = window.getComputedStyle(menuButton);
+ const expectedColor = hexToCSS("#ff00ff");
+ const expectedFill = hexToCSS("#ffffff");
+
+ is(
+ root.style.getPropertyValue("--menu-button-background"),
+ "",
+ "Variable should be unset"
+ );
+ isnot(
+ computedStyle.backgroundColor,
+ expectedColor,
+ "Menu button should not have custom background"
+ );
+ isnot(
+ computedStyle.fill,
+ expectedFill,
+ "Menu button should not have stylesheet fill"
+ );
+
+ await extension.startup();
+
+ if (AddonSettings.EXPERIMENTS_ENABLED) {
+ // Wait for stylesheet load.
+ await BrowserTestUtils.waitForCondition(
+ () => computedStyle.fill === expectedFill
+ );
+
+ is(
+ root.style.getPropertyValue("--menu-button-background"),
+ expectedColor,
+ "Variable should be parsed and set."
+ );
+ is(
+ computedStyle.backgroundColor,
+ expectedColor,
+ "Menu button should be have correct background"
+ );
+ is(
+ computedStyle.fill,
+ expectedFill,
+ "Menu button should be have correct fill"
+ );
+ } else {
+ is(
+ root.style.getPropertyValue("--menu-button-background"),
+ "",
+ "Variable should be unset"
+ );
+ isnot(
+ computedStyle.backgroundColor,
+ expectedColor,
+ "Menu button should not have custom background"
+ );
+ isnot(
+ computedStyle.fill,
+ expectedFill,
+ "Menu button should not have stylesheet fill"
+ );
+ }
+
+ await extension.unload();
+
+ is(
+ root.style.getPropertyValue("--menu-button-background"),
+ "",
+ "Variable should be unset"
+ );
+ isnot(
+ computedStyle.backgroundColor,
+ expectedColor,
+ "Menu button should not have custom background"
+ );
+ isnot(
+ computedStyle.fill,
+ expectedFill,
+ "Menu button should not have stylesheet fill"
+ );
+});
+
+add_task(async function cleanup() {
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
new file mode 100644
index 0000000000..fe9689bc74
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
@@ -0,0 +1,217 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the toolbar and toolbar_field properties also theme the findbar.
+
+add_task(async function test_support_toolbar_properties_on_findbar() {
+ const TOOLBAR_COLOR = "#ff00ff";
+ const TOOLBAR_TEXT_COLOR = "#9400ff";
+ const ACCENT_COLOR_INACTIVE = "#ffff00";
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ frame_inactive: ACCENT_COLOR_INACTIVE,
+ tab_background_text: TEXT_COLOR,
+ toolbar: TOOLBAR_COLOR,
+ bookmark_text: TOOLBAR_TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+ await gBrowser.getFindBar();
+
+ let findbar_button = gFindBar.getElement("highlight");
+
+ info("Checking findbar background is set as toolbar color");
+ Assert.equal(
+ window.getComputedStyle(gFindBar).backgroundColor,
+ hexToCSS(ACCENT_COLOR),
+ "Findbar background color should be the same as toolbar background color."
+ );
+
+ info("Checking findbar and button text color is set as toolbar text color");
+ Assert.equal(
+ window.getComputedStyle(gFindBar).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Findbar text color should be the same as toolbar text color."
+ );
+ Assert.equal(
+ window.getComputedStyle(findbar_button).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Findbar button text color should be the same as toolbar text color."
+ );
+
+ // Open a new window to check frame_inactive
+ let window2 = await BrowserTestUtils.openNewBrowserWindow();
+ Assert.equal(
+ window.getComputedStyle(gFindBar).backgroundColor,
+ hexToCSS(ACCENT_COLOR_INACTIVE),
+ "Findbar background changed in inactive window."
+ );
+ await BrowserTestUtils.closeWindow(window2);
+
+ await extension.unload();
+});
+
+add_task(async function test_support_toolbar_field_properties_on_findbar() {
+ const TOOLBAR_FIELD_COLOR = "#ff00ff";
+ const TOOLBAR_FIELD_TEXT_COLOR = "#9400ff";
+ const TOOLBAR_FIELD_BORDER_COLOR = "#ffffff";
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field: TOOLBAR_FIELD_COLOR,
+ toolbar_field_text: TOOLBAR_FIELD_TEXT_COLOR,
+ toolbar_field_border: TOOLBAR_FIELD_BORDER_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+ await gBrowser.getFindBar();
+
+ let findbar_textbox = gFindBar.getElement("findbar-textbox");
+
+ let findbar_prev_button = gFindBar.getElement("find-previous");
+
+ let findbar_next_button = gFindBar.getElement("find-next");
+
+ info(
+ "Checking findbar textbox background is set as toolbar field background color"
+ );
+ Assert.equal(
+ window.getComputedStyle(findbar_textbox).backgroundColor,
+ hexToCSS(TOOLBAR_FIELD_COLOR),
+ "Findbar textbox background color should be the same as toolbar field color."
+ );
+
+ info("Checking findbar textbox color is set as toolbar field text color");
+ Assert.equal(
+ window.getComputedStyle(findbar_textbox).color,
+ hexToCSS(TOOLBAR_FIELD_TEXT_COLOR),
+ "Findbar textbox text color should be the same as toolbar field text color."
+ );
+ testBorderColor(findbar_textbox, TOOLBAR_FIELD_BORDER_COLOR);
+ testBorderColor(findbar_prev_button, TOOLBAR_FIELD_BORDER_COLOR);
+ testBorderColor(findbar_next_button, TOOLBAR_FIELD_BORDER_COLOR);
+
+ await extension.unload();
+});
+
+// Test that theme properties are *not* applied with a theme_frame (see bug 1506913)
+add_task(async function test_toolbar_properties_on_findbar_with_theme_frame() {
+ const TOOLBAR_COLOR = "#ff00ff";
+ const TOOLBAR_TEXT_COLOR = "#9400ff";
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar: TOOLBAR_COLOR,
+ bookmark_text: TOOLBAR_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+ await gBrowser.getFindBar();
+
+ let findbar_button = gFindBar.getElement("highlight");
+
+ info("Checking findbar background is *not* set as toolbar color");
+ Assert.notEqual(
+ window.getComputedStyle(gFindBar).backgroundColor,
+ hexToCSS(ACCENT_COLOR),
+ "Findbar background color should not be set by theme."
+ );
+
+ info(
+ "Checking findbar and button text color is *not* set as toolbar text color"
+ );
+ Assert.notEqual(
+ window.getComputedStyle(gFindBar).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Findbar text color should not be set by theme."
+ );
+ Assert.notEqual(
+ window.getComputedStyle(findbar_button).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Findbar button text color should not be set by theme."
+ );
+
+ await extension.unload();
+});
+
+add_task(
+ async function test_toolbar_field_properties_on_findbar_with_theme_frame() {
+ const TOOLBAR_FIELD_COLOR = "#ff00ff";
+ const TOOLBAR_FIELD_TEXT_COLOR = "#9400ff";
+ const TOOLBAR_FIELD_BORDER_COLOR = "#ffffff";
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field: TOOLBAR_FIELD_COLOR,
+ toolbar_field_text: TOOLBAR_FIELD_TEXT_COLOR,
+ toolbar_field_border: TOOLBAR_FIELD_BORDER_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+ await gBrowser.getFindBar();
+
+ let findbar_textbox = gFindBar.getElement("findbar-textbox");
+
+ Assert.notEqual(
+ window.getComputedStyle(findbar_textbox).backgroundColor,
+ hexToCSS(TOOLBAR_FIELD_COLOR),
+ "Findbar textbox background color should not be set by theme."
+ );
+
+ Assert.notEqual(
+ window.getComputedStyle(findbar_textbox).color,
+ hexToCSS(TOOLBAR_FIELD_TEXT_COLOR),
+ "Findbar textbox text color should not be set by theme."
+ );
+
+ await extension.unload();
+ }
+);
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js b/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js
new file mode 100644
index 0000000000..981f32d7fb
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_getCurrent_differentExt.js
@@ -0,0 +1,66 @@
+"use strict";
+
+// This test checks whether browser.theme.getCurrent() works correctly when theme
+// does not originate from extension querying the theme.
+
+add_task(async function test_getcurrent() {
+ const theme = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ const extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.theme.onUpdated.addListener(() => {
+ browser.theme.getCurrent().then(theme => {
+ browser.test.sendMessage("theme-updated", theme);
+ });
+ });
+ },
+ });
+
+ await extension.startup();
+
+ info("Testing getCurrent after static theme startup");
+ let updatedPromise = extension.awaitMessage("theme-updated");
+ await theme.startup();
+ let receivedTheme = await updatedPromise;
+ Assert.ok(
+ receivedTheme.images.theme_frame.includes("image1.png"),
+ "getCurrent returns correct theme_frame image"
+ );
+ Assert.equal(
+ receivedTheme.colors.frame,
+ ACCENT_COLOR,
+ "getCurrent returns correct frame color"
+ );
+ Assert.equal(
+ receivedTheme.colors.tab_background_text,
+ TEXT_COLOR,
+ "getCurrent returns correct tab_background_text color"
+ );
+
+ info("Testing getCurrent after static theme unload");
+ updatedPromise = extension.awaitMessage("theme-updated");
+ await theme.unload();
+ receivedTheme = await updatedPromise;
+ Assert.equal(
+ JSON.stringify({ colors: null, images: null, properties: null }),
+ JSON.stringify(receivedTheme),
+ "getCurrent returns empty theme"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js b/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js
new file mode 100644
index 0000000000..083eb85486
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_highlight.js
@@ -0,0 +1,61 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the color of the font and background in a selection are applied properly.
+ChromeUtils.import(
+ "resource://testing-common/CustomizableUITestUtils.jsm",
+ this
+);
+let gCUITestUtils = new CustomizableUITestUtils(window);
+add_task(async function setup() {
+ await gCUITestUtils.addSearchBar();
+ registerCleanupFunction(() => {
+ gCUITestUtils.removeSearchBar();
+ });
+});
+
+add_task(async function test_support_selection() {
+ const HIGHLIGHT_TEXT_COLOR = "#9400FF";
+ const HIGHLIGHT_COLOR = "#F89919";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ toolbar_field_highlight: HIGHLIGHT_COLOR,
+ toolbar_field_highlight_text: HIGHLIGHT_TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let fields = [
+ gURLBar.inputField,
+ document.querySelector("#searchbar .searchbar-textbox"),
+ ].filter(field => {
+ let bounds = field.getBoundingClientRect();
+ return bounds.width > 0 && bounds.height > 0;
+ });
+
+ Assert.equal(fields.length, 2, "Should be testing two elements");
+
+ info(
+ `Checking background colors and colors for ${fields.length} toolbar input fields.`
+ );
+ for (let field of fields) {
+ info(`Testing ${field.id || field.className}`);
+ Assert.equal(
+ window.getComputedStyle(field, "::selection").backgroundColor,
+ hexToCSS(HIGHLIGHT_COLOR),
+ "Input selection background should be set."
+ );
+ Assert.equal(
+ window.getComputedStyle(field, "::selection").color,
+ hexToCSS(HIGHLIGHT_TEXT_COLOR),
+ "Input selection color should be set."
+ );
+ }
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js
new file mode 100644
index 0000000000..4917f6f830
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js
@@ -0,0 +1,81 @@
+"use strict";
+
+add_task(async function test_theme_incognito_not_allowed() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.allowPrivateBrowsingByDefault", false]],
+ });
+
+ let windowExtension = ExtensionTestUtils.loadExtension({
+ incognitoOverride: "spanning",
+ async background() {
+ const theme = {
+ colors: {
+ frame: "black",
+ tab_background_text: "black",
+ },
+ };
+ let window = await browser.windows.create({ incognito: true });
+ browser.test.onMessage.addListener(async message => {
+ if (message == "update") {
+ browser.theme.update(window.id, theme);
+ return;
+ }
+ await browser.windows.remove(window.id);
+ browser.test.sendMessage("done");
+ });
+ browser.test.sendMessage("ready", window.id);
+ },
+ manifest: {
+ permissions: ["theme"],
+ },
+ });
+ await windowExtension.startup();
+ let wId = await windowExtension.awaitMessage("ready");
+
+ async function background(windowId) {
+ const theme = {
+ colors: {
+ frame: "black",
+ tab_background_text: "black",
+ },
+ };
+
+ browser.theme.onUpdated.addListener(info => {
+ browser.test.log("got theme onChanged");
+ browser.test.fail("theme");
+ });
+ await browser.test.assertRejects(
+ browser.theme.getCurrent(windowId),
+ /Invalid window ID/,
+ "API should reject getting window theme"
+ );
+ await browser.test.assertRejects(
+ browser.theme.update(windowId, theme),
+ /Invalid window ID/,
+ "API should reject updating theme"
+ );
+ await browser.test.assertRejects(
+ browser.theme.reset(windowId),
+ /Invalid window ID/,
+ "API should reject reseting theme on window"
+ );
+
+ browser.test.sendMessage("start");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: `(${background})(${wId})`,
+ manifest: {
+ permissions: ["theme"],
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("start");
+ windowExtension.sendMessage("update");
+
+ windowExtension.sendMessage("close");
+ await windowExtension.awaitMessage("done");
+ await windowExtension.unload();
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
new file mode 100644
index 0000000000..af2eef6ffb
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const DEFAULT_THEME_BG_COLOR = "rgb(255, 255, 255)";
+const DEFAULT_THEME_TEXT_COLOR = "rgb(0, 0, 0)";
+
+add_task(async function test_deprecated_LWT_properties_ignored() {
+ // This test uses deprecated theme properties, so warnings are expected.
+ ExtensionTestUtils.failOnSchemaWarnings(false);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ headerURL: "image1.png",
+ },
+ colors: {
+ accentcolor: ACCENT_COLOR,
+ textcolor: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.ok(
+ !docEl.hasAttribute("lwtheme-image"),
+ "LWT image attribute should not be set on deprecated headerURL alias"
+ );
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "dark",
+ "LWT text color attribute should not be set on deprecated textcolor alias"
+ );
+
+ Assert.equal(
+ style.backgroundColor,
+ DEFAULT_THEME_BG_COLOR,
+ "Expected default theme background color"
+ );
+ Assert.equal(
+ style.color,
+ DEFAULT_THEME_TEXT_COLOR,
+ "Expected default theme text color"
+ );
+
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
new file mode 100644
index 0000000000..1395647683
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_multiple_backgrounds.js
@@ -0,0 +1,216 @@
+"use strict";
+
+add_task(async function test_support_backgrounds_position() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "face1.png",
+ additional_backgrounds: ["face2.png", "face2.png", "face2.png"],
+ },
+ colors: {
+ frame: `rgb(${FRAME_COLOR.join(",")})`,
+ tab_background_text: `rgb(${TAB_BACKGROUND_TEXT_COLOR.join(",")})`,
+ },
+ properties: {
+ additional_backgrounds_alignment: [
+ "left top",
+ "center top",
+ "right bottom",
+ ],
+ },
+ },
+ },
+ files: {
+ "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let toolbox = document.querySelector("#navigator-toolbox");
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+
+ let toolboxCS = window.getComputedStyle(toolbox);
+ let rootCS = window.getComputedStyle(docEl);
+ let rootBgImage = rootCS.backgroundImage.split(",")[0].trim();
+ let bgImage = toolboxCS.backgroundImage.split(",")[0].trim();
+ Assert.ok(
+ rootBgImage.includes("face1.png"),
+ `The backgroundImage should use face1.png. Actual value is: ${rootBgImage}`
+ );
+ Assert.equal(
+ toolboxCS.backgroundImage,
+ Array(3)
+ .fill(bgImage)
+ .join(", "),
+ "The backgroundImage should use face2.png three times."
+ );
+ Assert.equal(
+ toolboxCS.backgroundPosition,
+ "0% 0%, 50% 0%, 100% 100%",
+ "The backgroundPosition should use the three values provided."
+ );
+ Assert.equal(
+ toolboxCS.backgroundRepeat,
+ "no-repeat",
+ "The backgroundPosition should use the default value."
+ );
+
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+ toolboxCS = window.getComputedStyle(toolbox);
+
+ // Styles should've reverted to their initial values.
+ Assert.equal(rootCS.backgroundImage, "none");
+ Assert.equal(rootCS.backgroundPosition, "0% 0%");
+ Assert.equal(rootCS.backgroundRepeat, "repeat");
+ Assert.equal(toolboxCS.backgroundImage, "none");
+ Assert.equal(toolboxCS.backgroundPosition, "0% 0%");
+ Assert.equal(toolboxCS.backgroundRepeat, "repeat");
+});
+
+add_task(async function test_support_backgrounds_repeat() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "face0.png",
+ additional_backgrounds: ["face1.png", "face2.png", "face3.png"],
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ tab_background_text: TAB_BACKGROUND_TEXT_COLOR,
+ },
+ properties: {
+ additional_backgrounds_tiling: ["repeat-x", "repeat-y", "repeat"],
+ },
+ },
+ },
+ files: {
+ "face0.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ "face1.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ "face2.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ "face3.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let toolbox = document.querySelector("#navigator-toolbox");
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+
+ let rootCS = window.getComputedStyle(docEl);
+ let toolboxCS = window.getComputedStyle(toolbox);
+ let bgImage = rootCS.backgroundImage.split(",")[0].trim();
+ Assert.ok(
+ bgImage.includes("face0.png"),
+ `The backgroundImage should use face.png. Actual value is: ${bgImage}`
+ );
+ Assert.equal(
+ [1, 2, 3].map(num => bgImage.replace(/face[\d]*/, `face${num}`)).join(", "),
+ toolboxCS.backgroundImage,
+ "The backgroundImage should use face.png three times."
+ );
+ Assert.equal(
+ rootCS.backgroundPosition,
+ "100% 0%",
+ "The backgroundPosition should use the default value for root."
+ );
+ Assert.equal(
+ toolboxCS.backgroundPosition,
+ "100% 0%",
+ "The backgroundPosition should use the default value for navigator-toolbox."
+ );
+ Assert.equal(
+ rootCS.backgroundRepeat,
+ "no-repeat",
+ "The backgroundRepeat should use the default values for root."
+ );
+ Assert.equal(
+ toolboxCS.backgroundRepeat,
+ "repeat-x, repeat-y, repeat",
+ "The backgroundRepeat should use the three values provided for navigator-toolbox."
+ );
+
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
+
+add_task(async function test_additional_images_check() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "face.png",
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ tab_background_text: TAB_BACKGROUND_TEXT_COLOR,
+ },
+ properties: {
+ additional_backgrounds_tiling: ["repeat-x", "repeat-y", "repeat"],
+ },
+ },
+ },
+ files: {
+ "face.png": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let toolbox = document.querySelector("#navigator-toolbox");
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+
+ let rootCS = window.getComputedStyle(docEl);
+ let toolboxCS = window.getComputedStyle(toolbox);
+ let bgImage = rootCS.backgroundImage.split(",")[0];
+ Assert.ok(
+ bgImage.includes("face.png"),
+ `The backgroundImage should use face.png. Actual value is: ${bgImage}`
+ );
+ Assert.equal(
+ "none",
+ toolboxCS.backgroundImage,
+ "The backgroundImage should not use face.png."
+ );
+ Assert.equal(
+ rootCS.backgroundPosition,
+ "100% 0%",
+ "The backgroundPosition should use the default value."
+ );
+ Assert.equal(
+ rootCS.backgroundRepeat,
+ "no-repeat",
+ "The backgroundPosition should use only one (default) value."
+ );
+
+ await extension.unload();
+
+ Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
new file mode 100644
index 0000000000..3e5d789709
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
@@ -0,0 +1,157 @@
+"use strict";
+
+// This test checks whether the new tab page color properties work.
+
+/**
+ * Test whether the selected browser has the new tab page theme applied
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ */
+async function test_ntp_theme(theme, isBrightText) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme,
+ },
+ });
+
+ let browser = gBrowser.selectedBrowser;
+
+ let { originalBackground, originalColor } = await SpecialPowers.spawn(
+ browser,
+ [],
+ function() {
+ let doc = content.document;
+ ok(
+ !doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute"
+ );
+ ok(
+ !doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`
+ );
+
+ return {
+ originalBackground: content.getComputedStyle(doc.body).backgroundColor,
+ originalColor: content.getComputedStyle(
+ doc.querySelector(".outer-wrapper")
+ ).color,
+ };
+ }
+ );
+
+ await extension.startup();
+
+ Services.ppmm.sharedData.flush();
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ isBrightText,
+ background: hexToCSS(theme.colors.ntp_background),
+ color: hexToCSS(theme.colors.ntp_text),
+ },
+ ],
+ function({ isBrightText, background, color }) {
+ let doc = content.document;
+ ok(
+ doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should have lwt-newtab attribute"
+ );
+ is(
+ doc.body.hasAttribute("lwt-newtab-brighttext"),
+ isBrightText,
+ `New tab page should${
+ !isBrightText ? " not" : ""
+ } have lwt-newtab-brighttext attribute`
+ );
+
+ is(
+ content.getComputedStyle(doc.body).backgroundColor,
+ background,
+ "New tab page background should be set."
+ );
+ is(
+ content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ color,
+ "New tab page text color should be set."
+ );
+ }
+ );
+
+ await extension.unload();
+
+ Services.ppmm.sharedData.flush();
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ originalBackground,
+ originalColor,
+ },
+ ],
+ function({ originalBackground, originalColor }) {
+ let doc = content.document;
+ ok(
+ !doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute"
+ );
+ ok(
+ !doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`
+ );
+
+ is(
+ content.getComputedStyle(doc.body).backgroundColor,
+ originalBackground,
+ "New tab page background should be reset."
+ );
+ is(
+ content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ originalColor,
+ "New tab page text color should be reset."
+ );
+ }
+ );
+}
+
+add_task(async function test_support_ntp_colors() {
+ // BrowserTestUtils.withNewTab waits for about:newtab to load
+ // so we disable preloading before running the test.
+ SpecialPowers.setBoolPref("browser.newtab.preload", false);
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("browser.newtab.preload");
+ });
+ NewTabPagePreloading.removePreloadedBrowser(window);
+ for (let url of ["about:newtab", "about:home", "about:welcome"]) {
+ info("Opening url: " + url);
+ await BrowserTestUtils.withNewTab({ gBrowser, url }, async browser => {
+ await test_ntp_theme(
+ {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ ntp_background: "#add8e6",
+ ntp_text: "#00008b",
+ },
+ },
+ false,
+ url
+ );
+
+ await test_ntp_theme(
+ {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ ntp_background: "#00008b",
+ ntp_text: "#add8e6",
+ },
+ },
+ true,
+ url
+ );
+ });
+ }
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
new file mode 100644
index 0000000000..bf204632ec
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
@@ -0,0 +1,249 @@
+"use strict";
+
+// This test checks whether the new tab page color properties work per-window.
+
+/**
+ * Test whether a given browser has the new tab page theme applied
+ * @param {Object} browser to test against
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ * @returns {Promise} The task as a promise
+ */
+function test_ntp_theme(browser, theme, isBrightText) {
+ Services.ppmm.sharedData.flush();
+ return SpecialPowers.spawn(
+ browser,
+ [
+ {
+ isBrightText,
+ background: hexToCSS(theme.colors.ntp_background),
+ color: hexToCSS(theme.colors.ntp_text),
+ },
+ ],
+ function({ isBrightText, background, color }) {
+ let doc = content.document;
+ ok(
+ doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should have lwt-newtab attribute"
+ );
+ is(
+ doc.body.hasAttribute("lwt-newtab-brighttext"),
+ isBrightText,
+ `New tab page should${
+ !isBrightText ? " not" : ""
+ } have lwt-newtab-brighttext attribute`
+ );
+
+ is(
+ content.getComputedStyle(doc.body).backgroundColor,
+ background,
+ "New tab page background should be set."
+ );
+ is(
+ content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ color,
+ "New tab page text color should be set."
+ );
+ }
+ );
+}
+
+/**
+ * Test whether a given browser has the default theme applied
+ * @param {Object} browser to test against
+ * @param {string} url being tested
+ * @returns {Promise} The task as a promise
+ */
+function test_ntp_default_theme(browser, url) {
+ Services.ppmm.sharedData.flush();
+ if (url === "about:welcome") {
+ return SpecialPowers.spawn(
+ browser,
+ [
+ {
+ background: hexToCSS("#EDEDF0"),
+ color: hexToCSS("#0C0C0D"),
+ },
+ ],
+ function({ background, color }) {
+ let doc = content.document;
+ ok(
+ !doc.body.hasAttribute("lwt-newtab"),
+ "About:welcome page should not have lwt-newtab attribute"
+ );
+ ok(
+ !doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `About:welcome page should not have lwt-newtab-brighttext attribute`
+ );
+
+ is(
+ content.getComputedStyle(doc.body).backgroundColor,
+ background,
+ "About:welcome page background should be reset."
+ );
+ is(
+ content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ color,
+ "About:welcome page text color should be reset."
+ );
+ }
+ );
+ }
+ return SpecialPowers.spawn(
+ browser,
+ [
+ {
+ background: hexToCSS("#F9F9FA"),
+ color: hexToCSS("#0C0C0D"),
+ },
+ ],
+ function({ background, color }) {
+ let doc = content.document;
+ ok(
+ !doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute"
+ );
+ ok(
+ !doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`
+ );
+
+ is(
+ content.getComputedStyle(doc.body).backgroundColor,
+ background,
+ "New tab page background should be reset."
+ );
+ is(
+ content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ color,
+ "New tab page text color should be reset."
+ );
+ }
+ );
+}
+
+add_task(async function test_per_window_ntp_theme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ async background() {
+ function promiseWindowChecked() {
+ return new Promise(resolve => {
+ let listener = msg => {
+ if (msg == "checked-window") {
+ browser.test.onMessage.removeListener(listener);
+ resolve();
+ }
+ };
+ browser.test.onMessage.addListener(listener);
+ });
+ }
+
+ function removeWindow(winId) {
+ return new Promise(resolve => {
+ let listener = removedWinId => {
+ if (removedWinId == winId) {
+ browser.windows.onRemoved.removeListener(listener);
+ resolve();
+ }
+ };
+ browser.windows.onRemoved.addListener(listener);
+ browser.windows.remove(winId);
+ });
+ }
+
+ async function checkWindow(theme, isBrightText, winId) {
+ let windowChecked = promiseWindowChecked();
+ browser.test.sendMessage("check-window", {
+ theme,
+ isBrightText,
+ winId,
+ });
+ await windowChecked;
+ }
+
+ const darkTextTheme = {
+ colors: {
+ frame: "#add8e6",
+ tab_background_text: "#000",
+ ntp_background: "#add8e6",
+ ntp_text: "#000",
+ },
+ };
+
+ const brightTextTheme = {
+ colors: {
+ frame: "#00008b",
+ tab_background_text: "#add8e6",
+ ntp_background: "#00008b",
+ ntp_text: "#add8e6",
+ },
+ };
+
+ let { id: winId } = await browser.windows.getCurrent();
+ // We are opening about:blank instead of the default homepage,
+ // because using the default homepage results in intermittent
+ // test failures on debug builds due to browser window leaks.
+ let { id: secondWinId } = await browser.windows.create({
+ url: "about:blank",
+ });
+
+ browser.test.log("Test that single window update works");
+ await browser.theme.update(winId, darkTextTheme);
+ await checkWindow(darkTextTheme, false, winId);
+ await checkWindow(null, false, secondWinId);
+
+ browser.test.log("Test that applying different themes on both windows");
+ await browser.theme.update(secondWinId, brightTextTheme);
+ await checkWindow(darkTextTheme, false, winId);
+ await checkWindow(brightTextTheme, true, secondWinId);
+
+ browser.test.log("Test resetting the theme on one window");
+ await browser.theme.reset(winId);
+ await checkWindow(null, false, winId);
+ await checkWindow(brightTextTheme, true, secondWinId);
+
+ await removeWindow(secondWinId);
+ await checkWindow(null, false, winId);
+ browser.test.notifyPass("perwindow-ntp-theme");
+ },
+ });
+
+ extension.onMessage(
+ "check-window",
+ async ({ theme, isBrightText, winId }) => {
+ let win = Services.wm.getOuterWindowWithId(winId);
+ win.NewTabPagePreloading.removePreloadedBrowser(win);
+ // These pages were initially chosen because LightweightThemeChild.jsm
+ // treats them specially.
+ for (let url of ["about:newtab", "about:home", "about:welcome"]) {
+ info("Opening url: " + url);
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url },
+ async browser => {
+ if (theme) {
+ await test_ntp_theme(browser, theme, isBrightText);
+ } else {
+ await test_ntp_default_theme(browser, url);
+ }
+ }
+ );
+ }
+ extension.sendMessage("checked-window");
+ }
+ );
+
+ // BrowserTestUtils.withNewTab waits for about:newtab to load
+ // so we disable preloading before running the test.
+ await SpecialPowers.setBoolPref("browser.newtab.preload", false);
+ await SpecialPowers.setBoolPref("browser.aboutwelcome.enabled", true);
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("browser.newtab.preload");
+ SpecialPowers.clearUserPref("browser.aboutwelcome.enabled");
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("perwindow-ntp-theme");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
new file mode 100644
index 0000000000..bc72609acd
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_persistence.js
@@ -0,0 +1,58 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes are persisted and applied
+// on newly opened windows.
+
+add_task(async function test_multiple_windows() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = window.document.documentElement;
+ let style = window.getComputedStyle(docEl);
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+ Assert.ok(
+ style.backgroundImage.includes("image1.png"),
+ "Expected background image"
+ );
+
+ // Now we'll open a new window to see if the theme is also applied there.
+ let window2 = await BrowserTestUtils.openNewBrowserWindow();
+ docEl = window2.document.documentElement;
+ style = window2.getComputedStyle(docEl);
+
+ Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+ Assert.equal(
+ docEl.getAttribute("lwthemetextcolor"),
+ "bright",
+ "LWT text color attribute should be set"
+ );
+ Assert.ok(
+ style.backgroundImage.includes("image1.png"),
+ "Expected background image"
+ );
+
+ await BrowserTestUtils.closeWindow(window2);
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js b/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js
new file mode 100644
index 0000000000..d8b3b14073
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_reset.js
@@ -0,0 +1,112 @@
+"use strict";
+
+add_task(async function theme_reset_global_static_theme() {
+ let global_theme_extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: "#123456",
+ tab_background_text: "#fedcba",
+ },
+ },
+ },
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ async background() {
+ await browser.theme.reset();
+ let theme_after_reset = await browser.theme.getCurrent();
+
+ browser.test.assertEq(
+ "#123456",
+ theme_after_reset.colors.frame,
+ "Theme from other extension should not be cleared upon reset()"
+ );
+
+ let theme = {
+ colors: {
+ frame: "#CF723F",
+ },
+ };
+
+ await browser.theme.update(theme);
+ await browser.theme.reset();
+ let final_reset_theme = await browser.theme.getCurrent();
+
+ browser.test.assertEq(
+ JSON.stringify({ colors: null, images: null, properties: null }),
+ JSON.stringify(final_reset_theme),
+ "Should reset when extension had replaced the global theme"
+ );
+ browser.test.sendMessage("done");
+ },
+ });
+ await global_theme_extension.startup();
+ await extension.startup();
+ await extension.awaitMessage("done");
+
+ await global_theme_extension.unload();
+ await extension.unload();
+});
+
+add_task(async function theme_reset_by_windowId() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ async background() {
+ let theme = {
+ colors: {
+ frame: "#CF723F",
+ },
+ };
+
+ let { id: winId } = await browser.windows.getCurrent();
+ await browser.theme.update(winId, theme);
+ let update_theme = await browser.theme.getCurrent(winId);
+
+ browser.test.onMessage.addListener(async () => {
+ let current_theme = await browser.theme.getCurrent(winId);
+ browser.test.assertEq(
+ update_theme.colors.frame,
+ current_theme.colors.frame,
+ "Should not be reset by a reset(windowId) call from another extension"
+ );
+
+ browser.test.sendMessage("done");
+ });
+
+ browser.test.sendMessage("ready", winId);
+ },
+ });
+
+ let anotherExtension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ background() {
+ browser.test.onMessage.addListener(async winId => {
+ await browser.theme.reset(winId);
+ browser.test.sendMessage("done");
+ });
+ },
+ });
+
+ await extension.startup();
+ let winId = await extension.awaitMessage("ready");
+
+ await anotherExtension.startup();
+
+ // theme.reset should be ignored if the theme was set by another extension.
+ anotherExtension.sendMessage(winId);
+ await anotherExtension.awaitMessage("done");
+
+ extension.sendMessage();
+ await extension.awaitMessage("done");
+
+ await anotherExtension.unload();
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
new file mode 100644
index 0000000000..36658cd5b4
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sanitization.js
@@ -0,0 +1,175 @@
+"use strict";
+
+// This test checks color sanitization in various situations
+
+add_task(async function test_sanitization_invalid() {
+ // This test checks that invalid values are sanitized
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ bookmark_text: "ntimsfavoriteblue",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let navbar = document.querySelector("#nav-bar");
+ Assert.equal(
+ window.getComputedStyle(navbar).color,
+ "rgb(0, 0, 0)",
+ "All invalid values should always compute to black."
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_sanitization_css_variables() {
+ // This test checks that CSS variables are sanitized
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ bookmark_text: "var(--arrowpanel-dimmed)",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let navbar = document.querySelector("#nav-bar");
+ Assert.equal(
+ window.getComputedStyle(navbar).color,
+ "rgb(0, 0, 0)",
+ "All CSS variables should always compute to black."
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_sanitization_important() {
+ // This test checks that the sanitizer cannot be fooled with !important
+ let stylesheetAttr = `href="data:text/css,*{color:red!important}" type="text/css"`;
+ let stylesheet = document.createProcessingInstruction(
+ "xml-stylesheet",
+ stylesheetAttr
+ );
+ let load = BrowserTestUtils.waitForEvent(stylesheet, "load");
+ document.insertBefore(stylesheet, document.documentElement);
+ await load;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ bookmark_text: "green",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let navbar = document.querySelector("#nav-bar");
+ Assert.equal(
+ window.getComputedStyle(navbar).color,
+ "rgb(255, 0, 0)",
+ "Sheet applies"
+ );
+
+ stylesheet.remove();
+
+ Assert.equal(
+ window.getComputedStyle(navbar).color,
+ "rgb(0, 128, 0)",
+ "Shouldn't be able to fool the color sanitizer with !important"
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_sanitization_transparent() {
+ // This test checks whether transparent values are applied properly
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_top_separator: "transparent",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let navbar = document.querySelector("#nav-bar");
+ Assert.ok(
+ window.getComputedStyle(navbar).boxShadow.includes("rgba(0, 0, 0, 0)"),
+ "Top separator should be transparent"
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_sanitization_transparent_frame_color() {
+ // This test checks whether transparent frame color falls back to white.
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: "transparent",
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = document.documentElement;
+ Assert.equal(
+ window.getComputedStyle(docEl).backgroundColor,
+ "rgb(255, 255, 255)",
+ "Accent color should be white"
+ );
+
+ await extension.unload();
+});
+
+add_task(
+ async function test_sanitization_transparent_tab_background_text_color() {
+ // This test checks whether transparent textcolor falls back to black.
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: "transparent",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let docEl = document.documentElement;
+ Assert.equal(
+ window.getComputedStyle(docEl).color,
+ "rgb(0, 0, 0)",
+ "Text color should be black"
+ );
+
+ await extension.unload();
+ }
+);
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
new file mode 100644
index 0000000000..4266a982d8
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
@@ -0,0 +1,69 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the separator colors are applied properly.
+
+add_task(async function test_support_separator_properties() {
+ const SEPARATOR_TOP_COLOR = "#ff00ff";
+ const SEPARATOR_VERTICAL_COLOR = "#f0000f";
+ const SEPARATOR_FIELD_COLOR = "#9400ff";
+ const SEPARATOR_BOTTOM_COLOR = "#3366cc";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_top_separator: SEPARATOR_TOP_COLOR,
+ toolbar_vertical_separator: SEPARATOR_VERTICAL_COLOR,
+ toolbar_field_separator: SEPARATOR_FIELD_COLOR,
+ toolbar_bottom_separator: SEPARATOR_BOTTOM_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let navbar = document.querySelector("#nav-bar");
+ Assert.ok(
+ window
+ .getComputedStyle(navbar)
+ .boxShadow.includes(`rgb(${hexToRGB(SEPARATOR_TOP_COLOR).join(", ")})`),
+ "Top separator color properly set"
+ );
+
+ let mainWin = document.querySelector("#main-window");
+ Assert.equal(
+ window
+ .getComputedStyle(mainWin)
+ .getPropertyValue("--urlbar-separator-color"),
+ `rgb(${hexToRGB(SEPARATOR_FIELD_COLOR).join(", ")})`,
+ "Toolbar field separator color properly set"
+ );
+
+ let panelUIButton = document.querySelector("#PanelUI-button");
+ Assert.ok(
+ window
+ .getComputedStyle(panelUIButton)
+ .getPropertyValue("border-image-source")
+ .includes(`rgb(${hexToRGB(SEPARATOR_VERTICAL_COLOR).join(", ")})`),
+ "Vertical separator color properly set"
+ );
+
+ let toolbox = document.querySelector("#navigator-toolbox");
+ Assert.equal(
+ window.getComputedStyle(toolbox).borderBottomColor,
+ `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`,
+ "Bottom separator color properly set"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
new file mode 100644
index 0000000000..3d814d1082
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
@@ -0,0 +1,274 @@
+"use strict";
+
+// This test checks whether the sidebar color properties work.
+
+/**
+ * Test whether the selected browser has the sidebar theme applied
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ */
+async function test_sidebar_theme(theme, isBrightText) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme,
+ },
+ });
+
+ const sidebarBox = document.getElementById("sidebar-box");
+ const content = SidebarUI.browser.contentWindow;
+ const root = content.document.documentElement;
+
+ ok(
+ !sidebarBox.hasAttribute("lwt-sidebar"),
+ "Sidebar box should not have lwt-sidebar attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar"),
+ "Sidebar should not have lwt-sidebar attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar-brighttext"),
+ "Sidebar should not have lwt-sidebar-brighttext attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar-highlight"),
+ "Sidebar should not have lwt-sidebar-highlight attribute"
+ );
+
+ const rootCS = content.getComputedStyle(root);
+ const originalBackground = rootCS.backgroundColor;
+ const originalColor = rootCS.color;
+
+ // ::-moz-tree-row(selected, focus) computed style can't be accessed, so we create a fake one.
+ const highlightCS = {
+ get backgroundColor() {
+ // Standardize to rgb like other computed style.
+ let color = rootCS.getPropertyValue(
+ "--lwt-sidebar-highlight-background-color"
+ );
+ let [r, g, b] = color
+ .replace("rgba(", "")
+ .split(",")
+ .map(channel => parseInt(channel, 10));
+ return `rgb(${r}, ${g}, ${b})`;
+ },
+
+ get color() {
+ let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-text-color");
+ let [r, g, b] = color
+ .replace("rgba(", "")
+ .split(",")
+ .map(channel => parseInt(channel, 10));
+ return `rgb(${r}, ${g}, ${b})`;
+ },
+ };
+ const originalHighlightBackground = highlightCS.backgroundColor;
+ const originalHighlightColor = highlightCS.color;
+
+ await extension.startup();
+
+ Services.ppmm.sharedData.flush();
+
+ const actualBackground = hexToCSS(theme.colors.sidebar) || originalBackground;
+ const actualColor = hexToCSS(theme.colors.sidebar_text) || originalColor;
+ const actualHighlightBackground =
+ hexToCSS(theme.colors.sidebar_highlight) || originalHighlightBackground;
+ const actualHighlightColor =
+ hexToCSS(theme.colors.sidebar_highlight_text) || originalHighlightColor;
+ const isCustomHighlight = !!theme.colors.sidebar_highlight_text;
+ const isCustomSidebar = !!theme.colors.sidebar_text;
+
+ is(
+ sidebarBox.hasAttribute("lwt-sidebar"),
+ isCustomSidebar,
+ `Sidebar box should${
+ !isCustomSidebar ? " not" : ""
+ } have lwt-sidebar attribute`
+ );
+ is(
+ root.hasAttribute("lwt-sidebar"),
+ isCustomSidebar,
+ `Sidebar should${!isCustomSidebar ? " not" : ""} have lwt-sidebar attribute`
+ );
+ is(
+ root.hasAttribute("lwt-sidebar-brighttext"),
+ isBrightText,
+ `Sidebar should${
+ !isBrightText ? " not" : ""
+ } have lwt-sidebar-brighttext attribute`
+ );
+ is(
+ root.hasAttribute("lwt-sidebar-highlight"),
+ isCustomHighlight,
+ `Sidebar should${
+ !isCustomHighlight ? " not" : ""
+ } have lwt-sidebar-highlight attribute`
+ );
+
+ if (isCustomSidebar) {
+ const sidebarBoxCS = window.getComputedStyle(sidebarBox);
+ is(
+ sidebarBoxCS.backgroundColor,
+ actualBackground,
+ "Sidebar box background should be set."
+ );
+ is(
+ sidebarBoxCS.color,
+ actualColor,
+ "Sidebar box text color should be set."
+ );
+ }
+
+ is(
+ rootCS.backgroundColor,
+ actualBackground,
+ "Sidebar background should be set."
+ );
+ is(rootCS.color, actualColor, "Sidebar text color should be set.");
+
+ is(
+ highlightCS.backgroundColor,
+ actualHighlightBackground,
+ "Sidebar highlight background color should be set."
+ );
+ is(
+ highlightCS.color,
+ actualHighlightColor,
+ "Sidebar highlight text color should be set."
+ );
+
+ await extension.unload();
+
+ Services.ppmm.sharedData.flush();
+
+ ok(
+ !sidebarBox.hasAttribute("lwt-sidebar"),
+ "Sidebar box should not have lwt-sidebar attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar"),
+ "Sidebar should not have lwt-sidebar attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar-brighttext"),
+ "Sidebar should not have lwt-sidebar-brighttext attribute"
+ );
+ ok(
+ !root.hasAttribute("lwt-sidebar-highlight"),
+ "Sidebar should not have lwt-sidebar-highlight attribute"
+ );
+
+ is(
+ rootCS.backgroundColor,
+ originalBackground,
+ "Sidebar background should be reset."
+ );
+ is(rootCS.color, originalColor, "Sidebar text color should be reset.");
+ is(
+ highlightCS.backgroundColor,
+ originalHighlightBackground,
+ "Sidebar highlight background color should be reset."
+ );
+ is(
+ highlightCS.color,
+ originalHighlightColor,
+ "Sidebar highlight text color should be reset."
+ );
+}
+
+add_task(async function test_support_sidebar_colors() {
+ for (let command of ["viewBookmarksSidebar", "viewHistorySidebar"]) {
+ info("Executing command: " + command);
+
+ await SidebarUI.show(command);
+
+ await test_sidebar_theme(
+ {
+ colors: {
+ sidebar: "#fafad2", // lightgoldenrodyellow
+ sidebar_text: "#2f4f4f", // darkslategrey
+ },
+ },
+ false
+ );
+
+ await test_sidebar_theme(
+ {
+ colors: {
+ sidebar: "#8b4513", // saddlebrown
+ sidebar_text: "#ffa07a", // lightsalmon
+ },
+ },
+ true
+ );
+
+ await test_sidebar_theme(
+ {
+ colors: {
+ sidebar: "#fffafa", // snow
+ sidebar_text: "#663399", // rebeccapurple
+ sidebar_highlight: "#7cfc00", // lawngreen
+ sidebar_highlight_text: "#ffefd5", // papayawhip
+ },
+ },
+ false
+ );
+
+ await test_sidebar_theme(
+ {
+ colors: {
+ sidebar_highlight: "#a0522d", // sienna
+ sidebar_highlight_text: "#fff5ee", // seashell
+ },
+ },
+ false
+ );
+ }
+});
+
+add_task(async function test_support_sidebar_border_color() {
+ const LIGHT_SALMON = "#ffa07a";
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ sidebar_border: LIGHT_SALMON,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ const sidebarHeader = document.getElementById("sidebar-header");
+ const sidebarHeaderCS = window.getComputedStyle(sidebarHeader);
+
+ is(
+ sidebarHeaderCS.borderBottomColor,
+ hexToCSS(LIGHT_SALMON),
+ "Sidebar header border should be colored properly"
+ );
+
+ if (AppConstants.platform !== "linux") {
+ const sidebarSplitter = document.getElementById("sidebar-splitter");
+ const sidebarSplitterCS = window.getComputedStyle(sidebarSplitter);
+
+ is(
+ sidebarSplitterCS.borderInlineEndColor,
+ hexToCSS(LIGHT_SALMON),
+ "Sidebar splitter should be colored properly"
+ );
+
+ SidebarUI.reversePosition();
+
+ is(
+ sidebarSplitterCS.borderInlineStartColor,
+ hexToCSS(LIGHT_SALMON),
+ "Sidebar splitter should be colored properly after switching sides"
+ );
+
+ SidebarUI.reversePosition();
+ }
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js b/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js
new file mode 100644
index 0000000000..081322faa3
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_static_onUpdated.js
@@ -0,0 +1,66 @@
+"use strict";
+
+// This test checks whether browser.theme.onUpdated works
+// when a static theme is applied
+
+add_task(async function test_on_updated() {
+ const theme = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ const extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.theme.onUpdated.addListener(updateInfo => {
+ browser.test.sendMessage("theme-updated", updateInfo);
+ });
+ },
+ });
+
+ await extension.startup();
+
+ info("Testing update event on static theme startup");
+ let updatedPromise = extension.awaitMessage("theme-updated");
+ await theme.startup();
+ const { theme: receivedTheme, windowId } = await updatedPromise;
+ Assert.ok(!windowId, "No window id in static theme update event");
+ Assert.ok(
+ receivedTheme.images.theme_frame.includes("image1.png"),
+ "Theme theme_frame image should be applied"
+ );
+ Assert.equal(
+ receivedTheme.colors.frame,
+ ACCENT_COLOR,
+ "Theme frame color should be applied"
+ );
+ Assert.equal(
+ receivedTheme.colors.tab_background_text,
+ TEXT_COLOR,
+ "Theme tab_background_text color should be applied"
+ );
+
+ info("Testing update event on static theme unload");
+ updatedPromise = extension.awaitMessage("theme-updated");
+ await theme.unload();
+ const updateInfo = await updatedPromise;
+ Assert.ok(!windowId, "No window id in static theme update event on unload");
+ Assert.equal(
+ Object.keys(updateInfo.theme),
+ 0,
+ "unloading theme sends empty theme in update event"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
new file mode 100644
index 0000000000..928fa4edee
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_line.js
@@ -0,0 +1,50 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the color of the tab line are applied properly.
+
+add_task(async function test_support_tab_line() {
+ for (let protonTabsEnabled of [true, false]) {
+ SpecialPowers.pushPrefEnv({
+ set: [["browser.proton.tabs.enabled", protonTabsEnabled]],
+ });
+ let newWin = await BrowserTestUtils.openNewWindowWithFlushedXULCacheForMozSupports();
+
+ const TAB_LINE_COLOR = "#9400ff";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ tab_line: TAB_LINE_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking selected tab line color");
+ let selectedTab = newWin.document.querySelector(
+ ".tabbrowser-tab[selected]"
+ );
+ let line = selectedTab.querySelector(".tab-line");
+ if (protonTabsEnabled) {
+ Assert.equal(
+ newWin.getComputedStyle(line).display,
+ "none",
+ "Tab line should not be displayed when Proton is enabled"
+ );
+ } else {
+ Assert.equal(
+ newWin.getComputedStyle(line).backgroundColor,
+ `rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`,
+ "Tab line should have theme color"
+ );
+ }
+
+ await extension.unload();
+ await BrowserTestUtils.closeWindow(newWin);
+ }
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
new file mode 100644
index 0000000000..1e402dbcc6
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_loading.js
@@ -0,0 +1,51 @@
+"use strict";
+
+add_task(async function test_support_tab_loading_filling() {
+ const TAB_LOADING_COLOR = "#FF0000";
+
+ // Make sure we use the animating loading icon
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.prefersReducedMotion", 0]],
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: "#000",
+ toolbar: "#124455",
+ tab_background_text: "#9400ff",
+ tab_loading: TAB_LOADING_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking selected tab loading indicator colors");
+
+ let selectedTab = document.querySelector(
+ ".tabbrowser-tab[visuallyselected=true]"
+ );
+
+ selectedTab.setAttribute("busy", "true");
+ selectedTab.setAttribute("progress", "true");
+
+ let throbber = selectedTab.throbber;
+ Assert.equal(
+ window.getComputedStyle(throbber, "::before").fill,
+ `rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`,
+ "Throbber is filled with theme color"
+ );
+
+ selectedTab.removeAttribute("busy");
+ selectedTab.removeAttribute("progress");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
new file mode 100644
index 0000000000..21f3c6d38b
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_selected.js
@@ -0,0 +1,54 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the background color of selected tab are applied correctly.
+
+add_task(async function test_tab_background_color_property() {
+ const TAB_BACKGROUND_COLOR = "#9400ff";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ tab_selected: TAB_BACKGROUND_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking selected tab color");
+
+ let openTab = document.querySelector(
+ ".tabbrowser-tab[visuallyselected=true]"
+ );
+ let openTabBackground = openTab.querySelector(".tab-background");
+
+ let selectedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let selectedTabBackground = selectedTab.querySelector(".tab-background");
+
+ let openTabGradient = window
+ .getComputedStyle(openTabBackground)
+ .getPropertyValue("background-image");
+ let selectedTabGradient = window
+ .getComputedStyle(selectedTabBackground)
+ .getPropertyValue("background-image");
+
+ let rgbRegex = /rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/g;
+ let selectedTabColors = selectedTabGradient.match(rgbRegex);
+
+ Assert.equal(
+ selectedTabColors[0],
+ "rgb(" + hexToRGB(TAB_BACKGROUND_COLOR).join(", ") + ")",
+ "Selected tab background color should be set."
+ );
+ Assert.equal(openTabGradient, "none");
+
+ gBrowser.removeTab(selectedTab);
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js
new file mode 100644
index 0000000000..722c7dd99c
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_separators.js
@@ -0,0 +1,38 @@
+"use strict";
+
+add_task(async function test_support_tab_separators() {
+ const TAB_SEPARATOR_COLOR = "#FF0000";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: "#000",
+ tab_background_text: "#9400ff",
+ tab_background_separator: TAB_SEPARATOR_COLOR,
+ },
+ },
+ },
+ });
+ await extension.startup();
+
+ info("Checking background tab separator color");
+
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+
+ Assert.equal(
+ window.getComputedStyle(tab, "::before").borderLeftColor,
+ `rgb(${hexToRGB(TAB_SEPARATOR_COLOR).join(", ")})`,
+ "Left separator has right color."
+ );
+
+ Assert.equal(
+ window.getComputedStyle(tab, "::after").borderLeftColor,
+ `rgb(${hexToRGB(TAB_SEPARATOR_COLOR).join(", ")})`,
+ "Right separator has right color."
+ );
+
+ gBrowser.removeTab(tab);
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js
new file mode 100644
index 0000000000..d819f3a5f1
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_tab_text.js
@@ -0,0 +1,70 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the text color of the selected tab are applied properly.
+
+add_task(async function test_support_tab_text_property_css_color() {
+ const TAB_TEXT_COLOR = "#9400ff";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ tab_text: TAB_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking selected tab colors");
+ let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
+ Assert.equal(
+ window.getComputedStyle(selectedTab).color,
+ "rgb(" + hexToRGB(TAB_TEXT_COLOR).join(", ") + ")",
+ "Selected tab text color should be set."
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_support_tab_text_chrome_array() {
+ const TAB_TEXT_COLOR = [148, 0, 255];
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: FRAME_COLOR,
+ tab_background_text: TAB_BACKGROUND_TEXT_COLOR,
+ tab_text: TAB_TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking selected tab colors");
+ let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
+ Assert.equal(
+ window.getComputedStyle(selectedTab).color,
+ "rgb(" + TAB_TEXT_COLOR.join(", ") + ")",
+ "Selected tab text color should be set."
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js b/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js
new file mode 100644
index 0000000000..39934200ac
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_theme_transition.js
@@ -0,0 +1,48 @@
+"use strict";
+
+// This test checks whether the applied theme transition effects are applied
+// correctly.
+
+add_task(async function test_theme_transition_effects() {
+ const TOOLBAR = "#f27489";
+ const TEXT_COLOR = "#000000";
+ const TRANSITION_PROPERTY = "background-color";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ tab_background_text: TEXT_COLOR,
+ toolbar: TOOLBAR,
+ bookmark_text: TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ // check transition effect for toolbars
+ let navbar = document.querySelector("#nav-bar");
+ let navbarCS = window.getComputedStyle(navbar);
+
+ Assert.ok(
+ navbarCS
+ .getPropertyValue("transition-property")
+ .includes(TRANSITION_PROPERTY),
+ "Transition property set for #nav-bar"
+ );
+
+ let bookmarksBar = document.querySelector("#PersonalToolbar");
+ setToolbarVisibility(bookmarksBar, true, false, true);
+ let bookmarksBarCS = window.getComputedStyle(bookmarksBar);
+
+ Assert.ok(
+ bookmarksBarCS
+ .getPropertyValue("transition-property")
+ .includes(TRANSITION_PROPERTY),
+ "Transition property set for #PersonalToolbar"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js
new file mode 100644
index 0000000000..cd4d08c38f
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields.js
@@ -0,0 +1,145 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the background color and the color of the navbar text fields are applied properly.
+
+ChromeUtils.import(
+ "resource://testing-common/CustomizableUITestUtils.jsm",
+ this
+);
+let gCUITestUtils = new CustomizableUITestUtils(window);
+
+add_task(async function setup() {
+ await gCUITestUtils.addSearchBar();
+ registerCleanupFunction(() => {
+ gCUITestUtils.removeSearchBar();
+ });
+});
+
+add_task(async function test_support_toolbar_field_properties() {
+ const TOOLBAR_FIELD_BACKGROUND = "#ff00ff";
+ const TOOLBAR_FIELD_COLOR = "#00ff00";
+ const TOOLBAR_FIELD_BORDER = "#aaaaff";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field: TOOLBAR_FIELD_BACKGROUND,
+ toolbar_field_text: TOOLBAR_FIELD_COLOR,
+ toolbar_field_border: TOOLBAR_FIELD_BORDER,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let root = document.documentElement;
+ // Remove the `remotecontrol` attribute since it interferes with the urlbar styling.
+ root.removeAttribute("remotecontrol");
+ registerCleanupFunction(() => {
+ root.setAttribute("remotecontrol", "true");
+ });
+
+ let fields = [
+ document.querySelector("#urlbar-background"),
+ BrowserSearch.searchBar,
+ ].filter(field => {
+ let bounds = field.getBoundingClientRect();
+ return bounds.width > 0 && bounds.height > 0;
+ });
+
+ Assert.equal(fields.length, 2, "Should be testing two elements");
+
+ info(
+ `Checking toolbar background colors and colors for ${fields.length} toolbar fields.`
+ );
+ for (let field of fields) {
+ info(`Testing ${field.id || field.className}`);
+ Assert.equal(
+ window.getComputedStyle(field).backgroundColor,
+ hexToCSS(TOOLBAR_FIELD_BACKGROUND),
+ "Field background should be set."
+ );
+ Assert.equal(
+ window.getComputedStyle(field).color,
+ hexToCSS(TOOLBAR_FIELD_COLOR),
+ "Field color should be set."
+ );
+ testBorderColor(field, TOOLBAR_FIELD_BORDER);
+ }
+
+ await extension.unload();
+});
+
+add_task(async function test_support_toolbar_field_brighttext() {
+ let root = document.documentElement;
+ // Remove the `remotecontrol` attribute since it interferes with the urlbar styling.
+ root.removeAttribute("remotecontrol");
+ registerCleanupFunction(() => {
+ root.setAttribute("remotecontrol", "true");
+ });
+ let urlbar = gURLBar.textbox;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field: "#fff",
+ toolbar_field_text: "#000",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ Assert.equal(
+ window.getComputedStyle(urlbar).color,
+ hexToCSS("#000000"),
+ "Color has been set"
+ );
+ Assert.ok(
+ !root.hasAttribute("lwt-toolbar-field-brighttext"),
+ "Brighttext attribute should not be set"
+ );
+
+ await extension.unload();
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar_field: "#000",
+ toolbar_field_text: "#fff",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ Assert.equal(
+ window.getComputedStyle(urlbar).color,
+ hexToCSS("#ffffff"),
+ "Color has been set"
+ );
+ Assert.ok(
+ root.hasAttribute("lwt-toolbar-field-brighttext"),
+ "Brighttext attribute should be set"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js
new file mode 100644
index 0000000000..05b6a186d2
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbar_fields_focus.js
@@ -0,0 +1,102 @@
+"use strict";
+
+add_task(async function setup() {
+ // Remove the `remotecontrol` attribute since it interferes with the urlbar styling.
+ document.documentElement.removeAttribute("remotecontrol");
+ registerCleanupFunction(() => {
+ document.documentElement.setAttribute("remotecontrol", "true");
+ });
+});
+
+add_task(async function test_toolbar_field_focus() {
+ const TOOLBAR_FIELD_BACKGROUND = "#FF00FF";
+ const TOOLBAR_FIELD_COLOR = "#00FF00";
+ const TOOLBAR_FOCUS_BACKGROUND = "#FF0000";
+ const TOOLBAR_FOCUS_TEXT = "#9400FF";
+ const TOOLBAR_FOCUS_BORDER = "#FFFFFF";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: "#FF0000",
+ tab_background_color: "#ffffff",
+ toolbar_field: TOOLBAR_FIELD_BACKGROUND,
+ toolbar_field_text: TOOLBAR_FIELD_COLOR,
+ toolbar_field_focus: TOOLBAR_FOCUS_BACKGROUND,
+ toolbar_field_text_focus: TOOLBAR_FOCUS_TEXT,
+ toolbar_field_border_focus: TOOLBAR_FOCUS_BORDER,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ info("Checking toolbar field's focus color");
+
+ let urlBar = document.querySelector("#urlbar-background");
+ gURLBar.textbox.setAttribute("focused", "true");
+
+ Assert.equal(
+ window.getComputedStyle(urlBar).backgroundColor,
+ `rgb(${hexToRGB(TOOLBAR_FOCUS_BACKGROUND).join(", ")})`,
+ "Background Color is changed"
+ );
+ Assert.equal(
+ window.getComputedStyle(urlBar).color,
+ `rgb(${hexToRGB(TOOLBAR_FOCUS_TEXT).join(", ")})`,
+ "Text Color is changed"
+ );
+ testBorderColor(urlBar, TOOLBAR_FOCUS_BORDER);
+
+ gURLBar.textbox.removeAttribute("focused");
+
+ Assert.equal(
+ window.getComputedStyle(urlBar).backgroundColor,
+ `rgb(${hexToRGB(TOOLBAR_FIELD_BACKGROUND).join(", ")})`,
+ "Background Color is set back to initial"
+ );
+ Assert.equal(
+ window.getComputedStyle(urlBar).color,
+ `rgb(${hexToRGB(TOOLBAR_FIELD_COLOR).join(", ")})`,
+ "Text Color is set back to initial"
+ );
+ await extension.unload();
+});
+
+add_task(async function test_toolbar_field_focus_low_alpha() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: "#FF0000",
+ tab_background_color: "#ffffff",
+ toolbar_field: "#FF00FF",
+ toolbar_field_text: "#00FF00",
+ toolbar_field_focus: "rgba(0, 0, 255, 0.4)",
+ toolbar_field_text_focus: "red",
+ toolbar_field_border_focus: "#FFFFFF",
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+ gURLBar.textbox.setAttribute("focused", "true");
+
+ let urlBar = document.querySelector("#urlbar-background");
+ Assert.equal(
+ window.getComputedStyle(urlBar).backgroundColor,
+ `rgba(0, 0, 255, 0.9)`,
+ "Background color has minimum opacity enforced"
+ );
+ Assert.equal(
+ window.getComputedStyle(urlBar).color,
+ `rgb(255, 255, 255)`,
+ "Text color has been overridden to match background"
+ );
+
+ gURLBar.textbox.removeAttribute("focused");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js
new file mode 100644
index 0000000000..f31e0fce8a
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_colors.js
@@ -0,0 +1,56 @@
+"use strict";
+
+/* globals InspectorUtils */
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the button background color properties are applied correctly.
+
+add_task(async function test_button_background_properties() {
+ const BUTTON_BACKGROUND_ACTIVE = "#FFFFFF";
+ const BUTTON_BACKGROUND_HOVER = "#59CBE8";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ button_background_active: BUTTON_BACKGROUND_ACTIVE,
+ button_background_hover: BUTTON_BACKGROUND_HOVER,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let toolbarButton = document.querySelector("#home-button");
+ let toolbarButtonIcon = toolbarButton.icon;
+ let toolbarButtonIconCS = window.getComputedStyle(toolbarButtonIcon);
+
+ InspectorUtils.addPseudoClassLock(toolbarButton, ":hover");
+
+ Assert.equal(
+ toolbarButtonIconCS.getPropertyValue("background-color"),
+ `rgb(${hexToRGB(BUTTON_BACKGROUND_HOVER).join(", ")})`,
+ "Toolbar button hover background is set."
+ );
+
+ InspectorUtils.addPseudoClassLock(toolbarButton, ":active");
+
+ Assert.equal(
+ toolbarButtonIconCS.getPropertyValue("background-color"),
+ `rgb(${hexToRGB(BUTTON_BACKGROUND_ACTIVE).join(", ")})`,
+ "Toolbar button active background is set!"
+ );
+
+ InspectorUtils.clearPseudoClassLocks(toolbarButton);
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
new file mode 100644
index 0000000000..11643412dd
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
@@ -0,0 +1,107 @@
+"use strict";
+
+// This test checks applied WebExtension themes that attempt to change
+// icon color properties
+
+add_task(async function test_icons_properties() {
+ const ICONS_COLOR = "#001b47";
+ const ICONS_ATTENTION_COLOR = "#44ba77";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ icons: ICONS_COLOR,
+ icons_attention: ICONS_ATTENTION_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let toolbarbutton = document.querySelector("#home-button");
+ Assert.equal(
+ window.getComputedStyle(toolbarbutton).getPropertyValue("fill"),
+ `rgb(${hexToRGB(ICONS_COLOR).join(", ")})`,
+ "Buttons fill color set!"
+ );
+
+ let starButton = document.querySelector("#star-button");
+ starButton.setAttribute("starred", "true");
+
+ let starComputedStyle = window.getComputedStyle(starButton);
+ Assert.equal(
+ starComputedStyle.getPropertyValue(
+ "--lwt-toolbarbutton-icon-fill-attention"
+ ),
+ `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`,
+ "Variable is properly set"
+ );
+ Assert.equal(
+ starComputedStyle.getPropertyValue("fill"),
+ `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`,
+ "Starred icon fill is properly set"
+ );
+
+ starButton.removeAttribute("starred");
+
+ await extension.unload();
+});
+
+add_task(async function test_no_icons_properties() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ images: {
+ theme_frame: "image1.png",
+ },
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ },
+ },
+ },
+ files: {
+ "image1.png": BACKGROUND,
+ },
+ });
+
+ await extension.startup();
+
+ let toolbarbutton = document.querySelector("#home-button");
+ let toolbarbuttonCS = window.getComputedStyle(toolbarbutton);
+ Assert.equal(
+ toolbarbuttonCS.getPropertyValue("--lwt-toolbarbutton-icon-fill"),
+ "",
+ "Icon fill should not be set when the value is not specified in the manifest."
+ );
+ let currentColor = toolbarbuttonCS.getPropertyValue("color");
+ Assert.equal(
+ window.getComputedStyle(toolbarbutton).getPropertyValue("fill"),
+ currentColor,
+ "Button fill color should be currentColor when no icon color specified."
+ );
+
+ let starButton = document.querySelector("#star-button");
+ starButton.setAttribute("starred", "true");
+ let starComputedStyle = window.getComputedStyle(starButton);
+ Assert.equal(
+ starComputedStyle.getPropertyValue(
+ "--lwt-toolbarbutton-icon-fill-attention"
+ ),
+ "",
+ "Icon attention fill should not be set when the value is not specified in the manifest."
+ );
+ starButton.removeAttribute("starred");
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
new file mode 100644
index 0000000000..ee31d80888
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbars.js
@@ -0,0 +1,105 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// the background color of toolbars are applied properly.
+
+add_task(async function test_support_toolbar_property() {
+ const TOOLBAR_COLOR = "#ff00ff";
+ const TOOLBAR_TEXT_COLOR = "#9400ff";
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar: TOOLBAR_COLOR,
+ toolbar_text: TOOLBAR_TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ let toolbox = document.querySelector("#navigator-toolbox");
+ let toolbars = [
+ ...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)"),
+ ].filter(toolbar => {
+ let bounds = toolbar.getBoundingClientRect();
+ return bounds.width > 0 && bounds.height > 0;
+ });
+
+ let transitionPromise = waitForTransition(toolbars[0], "background-color");
+ await extension.startup();
+ await transitionPromise;
+
+ info(`Checking toolbar colors for ${toolbars.length} toolbars.`);
+ for (let toolbar of toolbars) {
+ info(`Testing ${toolbar.id}`);
+ Assert.equal(
+ window.getComputedStyle(toolbar).backgroundColor,
+ hexToCSS(TOOLBAR_COLOR),
+ "Toolbar background color should be set."
+ );
+ Assert.equal(
+ window.getComputedStyle(toolbar).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Toolbar text color should be set."
+ );
+ }
+
+ info("Checking selected tab colors");
+ let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
+ Assert.equal(
+ window.getComputedStyle(selectedTab).color,
+ hexToCSS(TOOLBAR_TEXT_COLOR),
+ "Selected tab text color should be set."
+ );
+
+ await extension.unload();
+});
+
+add_task(async function test_bookmark_text_property() {
+ const TOOLBAR_COLOR = [255, 0, 255];
+ const TOOLBAR_TEXT_COLOR = [48, 0, 255];
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ colors: {
+ frame: ACCENT_COLOR,
+ tab_background_text: TEXT_COLOR,
+ toolbar: TOOLBAR_COLOR,
+ bookmark_text: TOOLBAR_TEXT_COLOR,
+ },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ let toolbox = document.querySelector("#navigator-toolbox");
+ let toolbars = [
+ ...toolbox.querySelectorAll("toolbar:not(#TabsToolbar)"),
+ ].filter(toolbar => {
+ let bounds = toolbar.getBoundingClientRect();
+ return bounds.width > 0 && bounds.height > 0;
+ });
+
+ info(`Checking toolbar colors for ${toolbars.length} toolbars.`);
+ for (let toolbar of toolbars) {
+ info(`Testing ${toolbar.id}`);
+ Assert.equal(
+ window.getComputedStyle(toolbar).color,
+ rgbToCSS(TOOLBAR_TEXT_COLOR),
+ "bookmark_text should be an alias for toolbar_text"
+ );
+ }
+
+ info("Checking selected tab colors");
+ let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
+ Assert.equal(
+ window.getComputedStyle(selectedTab).color,
+ rgbToCSS(TOOLBAR_TEXT_COLOR),
+ "Selected tab text color should be set."
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js b/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js
new file mode 100644
index 0000000000..64155006d9
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_warnings.js
@@ -0,0 +1,143 @@
+"use strict";
+
+const { AddonSettings } = ChromeUtils.import(
+ "resource://gre/modules/addons/AddonSettings.jsm"
+);
+
+// This test checks that theme warnings are properly emitted.
+
+function waitForConsole(task, message) {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async resolve => {
+ SimpleTest.monitorConsole(resolve, [
+ {
+ message: new RegExp(message),
+ },
+ ]);
+ await task();
+ SimpleTest.endMonitorConsole();
+ });
+}
+
+add_task(async function setup() {
+ SimpleTest.waitForExplicitFinish();
+});
+
+add_task(async function test_static_theme() {
+ for (const property of ["colors", "images", "properties"]) {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ [property]: {
+ such_property: "much_wow",
+ },
+ },
+ },
+ });
+ await waitForConsole(
+ extension.startup,
+ `Unrecognized theme property found: ${property}.such_property`
+ );
+ await extension.unload();
+ }
+});
+
+add_task(async function test_dynamic_theme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ background() {
+ browser.test.onMessage.addListener((msg, details) => {
+ if (msg === "update-theme") {
+ browser.theme.update(details).then(() => {
+ browser.test.sendMessage("theme-updated");
+ });
+ } else {
+ browser.theme.reset().then(() => {
+ browser.test.sendMessage("theme-reset");
+ });
+ }
+ });
+ },
+ });
+
+ await extension.startup();
+
+ for (const property of ["colors", "images", "properties"]) {
+ extension.sendMessage("update-theme", {
+ [property]: {
+ such_property: "much_wow",
+ },
+ });
+ await waitForConsole(
+ () => extension.awaitMessage("theme-updated"),
+ `Unrecognized theme property found: ${property}.such_property`
+ );
+ }
+
+ await extension.unload();
+});
+
+add_task(async function test_experiments_enabled() {
+ info("Testing that experiments are handled correctly on nightly and deved");
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ properties: {
+ such_property: "much_wow",
+ unknown_property: "very_unknown",
+ },
+ },
+ theme_experiment: {
+ properties: {
+ such_property: "--such-property",
+ },
+ },
+ },
+ });
+ if (!AddonSettings.EXPERIMENTS_ENABLED) {
+ await waitForConsole(
+ extension.startup,
+ "This extension is not allowed to run theme experiments"
+ );
+ } else {
+ await waitForConsole(
+ extension.startup,
+ "Unrecognized theme property found: properties.unknown_property"
+ );
+ }
+ await extension.unload();
+});
+
+add_task(async function test_experiments_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.experiments.enabled", false]],
+ });
+
+ info(
+ "Testing that experiments are handled correctly when experiements pref is disabled"
+ );
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme: {
+ properties: {
+ such_property: "much_wow",
+ },
+ },
+ theme_experiment: {
+ properties: {
+ such_property: "--such-property",
+ },
+ },
+ },
+ });
+ await waitForConsole(
+ extension.startup,
+ "This extension is not allowed to run theme experiments"
+ );
+ await extension.unload();
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js b/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js
new file mode 100644
index 0000000000..96a2216067
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_thumbnails_bg_extension.js
@@ -0,0 +1,94 @@
+"use strict";
+
+/* import-globals-from ../../../thumbnails/test/head.js */
+loadTestSubscript("../../../thumbnails/test/head.js");
+
+// The service that creates thumbnails of webpages in the background loads a
+// web page in the background (with several features disabled). Extensions
+// should be able to observe requests, but not run content scripts.
+add_task(async function test_thumbnails_background_visibility_to_extensions() {
+ const iframeUrl = "http://example.com/?iframe";
+ const testPageUrl = bgTestPageURL({ iframe: iframeUrl });
+ // ^ testPageUrl is http://mochi.test:8888/.../thumbnails_background.sjs?...
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ content_scripts: [
+ {
+ // ":8888" omitted due to bug 1362809.
+ matches: [
+ "http://mochi.test/*/thumbnails_background.sjs*",
+ "http://example.com/?iframe*",
+ ],
+ js: ["contentscript.js"],
+ run_at: "document_start",
+ all_frames: true,
+ },
+ ],
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "http://example.com/*",
+ "http://mochi.test/*",
+ ],
+ },
+ files: {
+ "contentscript.js": () => {
+ // Content scripts are not expected to be run in the page of the
+ // thumbnail service, so this should never execute.
+ new Image().src = "http://example.com/?unexpected-content-script";
+ browser.test.fail("Content script ran in thumbs, unexpectedly.");
+ },
+ },
+ background() {
+ let requests = [];
+ browser.webRequest.onBeforeRequest.addListener(
+ ({ url, tabId, frameId, type }) => {
+ browser.test.assertEq(-1, tabId, "Thumb page is not a tab");
+ // We want to know if frameId is 0 or non-negative (or possibly -1).
+ if (type === "sub_frame") {
+ browser.test.assertTrue(frameId > 0, `frame ${frameId} for ${url}`);
+ } else {
+ browser.test.assertEq(0, frameId, `frameId for ${type} ${url}`);
+ }
+ requests.push({ type, url });
+ },
+ {
+ types: ["main_frame", "sub_frame", "image"],
+ urls: ["*://*/*"],
+ },
+ ["blocking"]
+ );
+ browser.test.onMessage.addListener(msg => {
+ browser.test.assertEq("get-results", msg, "expected message");
+ browser.test.sendMessage("webRequest-results", requests);
+ });
+ },
+ });
+
+ await extension.startup();
+
+ ok(!thumbnailExists(testPageUrl), "Thumbnail should not be cached yet.");
+
+ await bgCapture(testPageUrl);
+ ok(thumbnailExists(testPageUrl), "Thumbnail should be cached after capture");
+ removeThumbnail(testPageUrl);
+
+ extension.sendMessage("get-results");
+ Assert.deepEqual(
+ await extension.awaitMessage("webRequest-results"),
+ [
+ {
+ type: "main_frame",
+ url: testPageUrl,
+ },
+ {
+ type: "sub_frame",
+ url: iframeUrl,
+ },
+ ],
+ "Expected requests via webRequest"
+ );
+
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js b/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js
new file mode 100644
index 0000000000..674a10a5ef
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_webRequest_redirect_mozextension.js
@@ -0,0 +1,48 @@
+"use strict";
+
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1573456
+add_task(async function test_mozextension_page_loaded_in_extension_process() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "https://example.com/*",
+ ],
+ web_accessible_resources: ["test.html"],
+ },
+ files: {
+ "test.html": '<!DOCTYPE html><script src="test.js"></script>',
+ "test.js": () => {
+ browser.test.assertTrue(
+ browser.webRequest,
+ "webRequest API should be available"
+ );
+
+ browser.test.sendMessage("test_done");
+ },
+ },
+ background: () => {
+ browser.webRequest.onBeforeRequest.addListener(
+ () => {
+ return {
+ redirectUrl: browser.runtime.getURL("test.html"),
+ };
+ },
+ { urls: ["*://*/redir"] },
+ ["blocking"]
+ );
+ },
+ });
+ await extension.startup();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com/redir"
+ );
+
+ await extension.awaitMessage("test_done");
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js b/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js
new file mode 100644
index 0000000000..21ef6bf460
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_windows_popup_title.js
@@ -0,0 +1,61 @@
+"use strict";
+
+// Check that extension popup windows contain the name of the extension
+// as well as the title of the loaded document, but not the URL.
+add_task(async function test_popup_title() {
+ const name = "custom_title_number_9_please";
+ const docTitle = "popup-test-title";
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ name,
+ permissions: ["tabs"],
+ },
+
+ async background() {
+ let popup;
+
+ // Called after the popup loads
+ browser.runtime.onMessage.addListener(async ({ docTitle }) => {
+ const { id } = await popup;
+ const { title } = await browser.windows.get(id);
+ browser.windows.remove(id);
+
+ browser.test.assertTrue(
+ title.includes(name),
+ "popup title must include extension name"
+ );
+ browser.test.assertTrue(
+ title.includes(docTitle),
+ "popup title must include extension document title"
+ );
+ browser.test.assertFalse(
+ title.includes("moz-extension:"),
+ "popup title must not include extension URL"
+ );
+
+ browser.test.notifyPass("popup-window-title");
+ });
+
+ popup = browser.windows.create({
+ url: "/index.html",
+ type: "popup",
+ });
+ },
+ files: {
+ "index.html": `<!doctype html>
+ <meta charset="utf-8">
+ <title>${docTitle}</title>,
+ <script src="index.js"></script>
+ `,
+ "index.js": `addEventListener(
+ "load",
+ () => browser.runtime.sendMessage({docTitle: document.title})
+ );`,
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("popup-window-title");
+ await extension.unload();
+});
diff --git a/toolkit/components/extensions/test/browser/data/test-download.txt b/toolkit/components/extensions/test/browser/data/test-download.txt
new file mode 100644
index 0000000000..f416e0e291
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/data/test-download.txt
@@ -0,0 +1 @@
+test download content
diff --git a/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html b/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html
new file mode 100644
index 0000000000..85410abfcd
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/data/test_downloads_referrer.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test downloads referrer</title>
+ </head>
+ <body>
+ <a href="test-download.txt" class="test-link">test link</a>
+ </body>
+</html>
diff --git a/toolkit/components/extensions/test/browser/head.js b/toolkit/components/extensions/test/browser/head.js
new file mode 100644
index 0000000000..0dd1a1666c
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/head.js
@@ -0,0 +1,103 @@
+/* exported ACCENT_COLOR, BACKGROUND, ENCODED_IMAGE_DATA, FRAME_COLOR, TAB_TEXT_COLOR,
+ TEXT_COLOR, TAB_BACKGROUND_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB, testBorderColor,
+ waitForTransition, loadTestSubscript */
+
+"use strict";
+
+const BACKGROUND =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" +
+ "DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+const ENCODED_IMAGE_DATA =
+ "iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0h" +
+ "STQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAdhwAAHYcBj+XxZQAAB5dJREFUSMd" +
+ "91vmTlEcZB/Bvd7/vO+/ce83O3gfLDUsC4VgIghBUEo2GM9GCFTaQBEISA1qIEVNQ4aggJDGIgAGTlFUKKcqKQpVHaQyny7FrCMiywp4ze+/Mzs67M/P" +
+ "O+3a3v5jdWo32H/B86vv0U083weecV3+0C8lkEh6PhzS3tuLkieMSAKo3fW9Mb1eoUtM0jemerukLllzrbGlKheovUpeqkmt113hPfx/27tyFF7+/bbg" +
+ "e+U9g20s7kEwmMXXGNLrp2fWi4V5z/tFjJ3fWX726INbfU2xx0yelkJAKdJf3Xl5+2QcPTpv2U0JZR+u92+xvly5ygKDm20/hlX17/jvB6VNnIKXEOyd" +
+ "O0iFh4PLVy0XV1U83Vk54QI7JK+bl+UE5vjRfTCzJ5eWBTFEayBLjisvljKmzwmtWrVkEAPNmVrEZkyfh+fU1n59k//7X4Fbz8MK2DRSAWLNq/Yc36y9" +
+ "+3UVMsyAYVPMy/MTvdBKvriJhphDq6xa9vf0i1GMwPVhM5s9bsLw/EvtN2kywwnw/nzBuLDZs2z4auXGjHuvWbmBQdT5v7qytn165fLCyyGtXTR6j5GV" +
+ "kIsvlBCwTVNgQhMKCRDQ2iIbmJv7BpU+Ykl02UFOzdt6gkbzTEQ5Rl2KL3W8eGUE+/ssFXK+rJQ8vWigLgjk5z9ZsvpOniJzVi+ZKTUhCuATTKCjhoLA" +
+ "hhQAsjrSZBJcm7rZ22O+ev6mMmTLj55eu1T+jU8GOH/kJf2TZCiifIQsXfwEbN2yktxoaeYbf93DKSORMnTOZE0aZaVlQGYVKJCgjEJSCcgLB0xDERjI" +
+ "NFBUEaXmuB20t95eEutr0xrufpo4eepMAkMPIxx+dx9at25EWQNXsh77q0Bzwen0ShEF32HCrCpjksAWHFAKqokFhgEJt2DKJeFoQv8eDuz3duaseXZY" +
+ "dixthaQ+NRlRCcKO+FgCweP68wswMF/yZWcTkNpLJFAZEGi6XC07NCUIIoqaNSLQfFALCEpCSEL/bK/wuw+12sKlDQzKs6k5yZt+rI+2aNKUSNdUbSSQ" +
+ "Wh2mJP46rGPeYrjtkY0M7jFgciUQCiqqgrCAfBTle3G9rR1NHN3SnDq9Lg49QlBQEcbfbQCKZlhQEDkXBih27RpDOrmacfP8YB4CfHT7uNXrCMFM2FdD" +
+ "BVQ5TE/A5HbDSJoSpQXAbXm8A4b5+gKrwulU4KKEBnwuzHpiQu+n1jQoQsM+9cYQMT9fvf/FLBYTaDqdzbfgft95PKzbPyQqwnlAXGkJtGIgNYnJpMfw" +
+ "OghLG0GJE0ZdiaOnsQ16OD6XZLkiRROdAgud5sxk8ridsy/pQU1VlOIkZN6QtAGnx0FA0AtXvIA4C5OX4kOWbiLRhQBDApTmgJuLwEonMgBvjgpmgjIE" +
+ "hhX7DAIVKNeqE05/dJbgEgRy5eOJ1ieXr1gJA7ZNLTrVVlAZLyopLJAUlHsrAMrwwrRQ4t6E5VHgSBExjcGpO0JQNizCE05a41dhOi+cXXVm144e1AHD" +
+ "1vXfFMOLy+KSHEDoEJLZ8s+ZWKpUusWwpFKiMUQ4jbiAaj8Hp9oExBsMCUpEIfD6JLKZjKJVGV3RIZGdm0qxA5qmz+/cgMhBVuuMRewRRGF7fe4BYHMg" +
+ "N5LxdV3vhy1EjrrjA5GAyTuKpFHricfS0dSDNCQRPoSyQgSSPI+UBEtwShiWUQEHw5mMvbz4JRcXvDr3B3dBG1sq5X53GlMcX4JWVTyvRQcOumDD2vfK" +
+ "cjOqiQDZPGBF2ryUEnjRhJlP4d6/BiQ1TABPKiyQhgtzvjPCJlQ/OGRwauqESSUPX68U3Vi4fGeH83Hwc3bYHBWUV0m0k4HB6z7aGu6sznDos00R3exg" +
+ "l5ZMwc+FMaJoKKxHFnbo6DMYiELBlqLOXDBq8dsvuPTfKALpwdbX42iMLsHjLd0Zv4RNvvY1wZxdZunyVDGZm6D/47sv12RqbmOPVhG5LGnAH4S8sgu7" +
+ "1oK/pn2BWAoYw0dDbaTd19iqlZROejwzEjqgMSuXUifak8jF49JnNI0kAoGrBfET7+uXOrS+y5ta21JzZsw7faW45XJaXxSvyAtTpkOi483fwtAWP1wt" +
+ "vrhvd/VFx+26zojr9Les2PnfaTNu4cuGvvKe9BVv3/RgARiNTpk/Hod17MWikxcqzzfhK/+1jL2xc+YQAX1ISDHLV7WTpQQaLcASzPEiB41ZrmEeHkrT" +
+ "Q49uz/aXn+iilLKXq/MmlS0e/jFcuX4SmaQAAKSXlnIvVy1aQ6EBMFgRyCznDpfGFwdKqirF2tu5SdIeGrkiP+KS5yb7dHtIKsnI++kP9rS8RQvjmxxe" +
+ "jePxD2HHwwP9FdCllurGhUbx14CAbiMc4Y2qVJqwLbo0qfpdLSilILB4Xg0mT6h7vnSWzZn9RoaynobWF3K6rk1NmzMWZ83/+37+V4a1cVg5JACYF45b" +
+ "FGVVWOFS2V1HUCjOdBqW0Q9fYb7N9/tcSptnldjpott8rFEXBO+f+NKrWMHL9Wu1nSUAIAaUUa59aAyE43E4X3bD8W6K5K6x1h1snRaMDJDuQf7+vrzf" +
+ "eG+mgfrcLHh3C79bx6wttGEqERiH/AjPohWMouv2ZAAAAAElFTkSuQmCC";
+const ACCENT_COLOR = "#a14040";
+const TEXT_COLOR = "#fac96e";
+// For testing aliases of the colors above:
+const FRAME_COLOR = [71, 105, 91];
+const TAB_BACKGROUND_TEXT_COLOR = [207, 221, 192, 0.9];
+
+function hexToRGB(hex) {
+ if (!hex) {
+ return null;
+ }
+ hex = parseInt(hex.indexOf("#") > -1 ? hex.substring(1) : hex, 16);
+ return [hex >> 16, (hex & 0x00ff00) >> 8, hex & 0x0000ff];
+}
+
+function rgbToCSS(rgb) {
+ return `rgb(${rgb.join(", ")})`;
+}
+
+function hexToCSS(hex) {
+ if (!hex) {
+ return null;
+ }
+ return rgbToCSS(hexToRGB(hex));
+}
+
+function imageBufferFromDataURI(encodedImageData) {
+ let decodedImageData = atob(encodedImageData);
+ return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+}
+
+function waitForTransition(element, propertyName) {
+ return BrowserTestUtils.waitForEvent(
+ element,
+ "transitionend",
+ false,
+ event => {
+ return event.target == element && event.propertyName == propertyName;
+ }
+ );
+}
+
+function testBorderColor(element, expected) {
+ let computedStyle = window.getComputedStyle(element);
+ Assert.equal(
+ computedStyle.borderLeftColor,
+ hexToCSS(expected),
+ "Element left border color should be set."
+ );
+ Assert.equal(
+ computedStyle.borderRightColor,
+ hexToCSS(expected),
+ "Element right border color should be set."
+ );
+ Assert.equal(
+ computedStyle.borderTopColor,
+ hexToCSS(expected),
+ "Element top border color should be set."
+ );
+ Assert.equal(
+ computedStyle.borderBottomColor,
+ hexToCSS(expected),
+ "Element bottom border color should be set."
+ );
+}
+
+function loadTestSubscript(filePath) {
+ Services.scriptloader.loadSubScript(new URL(filePath, gTestPath).href, this);
+}
diff --git a/toolkit/components/extensions/test/browser/head_serviceworker.js b/toolkit/components/extensions/test/browser/head_serviceworker.js
new file mode 100644
index 0000000000..012dcfe284
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/head_serviceworker.js
@@ -0,0 +1,123 @@
+"use strict";
+
+/* exported assert_background_serviceworker_pref_enabled,
+ * getBackgroundServiceWorkerRegistration,
+ * getServiceWorkerInfo, getServiceWorkerState,
+ * waitForServiceWorkerRegistrationsRemoved, waitForServiceWorkerTerminated
+ */
+
+async function assert_background_serviceworker_pref_enabled() {
+ is(
+ WebExtensionPolicy.backgroundServiceWorkerEnabled,
+ true,
+ "Expect extensions.backgroundServiceWorker.enabled to be true"
+ );
+}
+
+// Return the name of the enum corresponding to the worker's state (ex: "STATE_ACTIVATED")
+// because nsIServiceWorkerInfo doesn't currently provide a comparable string-returning getter.
+function getServiceWorkerState(workerInfo) {
+ const map = Object.keys(workerInfo)
+ .filter(k => k.startsWith("STATE_"))
+ .reduce((map, name) => {
+ map.set(workerInfo[name], name);
+ return map;
+ }, new Map());
+ return map.has(workerInfo.state)
+ ? map.get(workerInfo.state)
+ : "state: ${workerInfo.state}";
+}
+
+function getServiceWorkerInfo(swRegInfo) {
+ const {
+ evaluatingWorker,
+ installingWorker,
+ waitingWorker,
+ activeWorker,
+ } = swRegInfo;
+ return evaluatingWorker || installingWorker || waitingWorker || activeWorker;
+}
+
+async function waitForServiceWorkerTerminated(swRegInfo) {
+ info(`Wait all ${swRegInfo.scope} workers to be terminated`);
+
+ try {
+ await BrowserTestUtils.waitForCondition(
+ () => !getServiceWorkerInfo(swRegInfo)
+ );
+ } catch (err) {
+ const workerInfo = getServiceWorkerInfo(swRegInfo);
+ if (workerInfo) {
+ ok(
+ false,
+ `Error while waiting for workers for scope ${swRegInfo.scope} to be terminated. ` +
+ `Found a worker in state: ${getServiceWorkerState(workerInfo)}`
+ );
+ return;
+ }
+
+ throw err;
+ }
+}
+
+function getBackgroundServiceWorkerRegistration(extension) {
+ const policy = WebExtensionPolicy.getByHostname(extension.uuid);
+ const expectedSWScope = policy.getURL("/");
+ const expectedScriptURL = policy.extension.backgroundWorkerScript || "";
+
+ ok(
+ expectedScriptURL.startsWith(expectedSWScope),
+ `Extension does include a valid background.service_worker: ${expectedScriptURL}`
+ );
+
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+
+ let swReg;
+ let regs = swm.getAllRegistrations();
+
+ for (let i = 0; i < regs.length; i++) {
+ let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ if (reg.scriptSpec === expectedScriptURL) {
+ swReg = reg;
+ break;
+ }
+ }
+
+ ok(swReg, `Found service worker registration for ${expectedScriptURL}`);
+
+ is(
+ swReg.scope,
+ expectedSWScope,
+ "The extension background worker registration has the expected scope URL"
+ );
+
+ return swReg;
+}
+
+async function waitForServiceWorkerRegistrationsRemoved(extension) {
+ info(`Wait ${extension.id} service worker registration to be deleted`);
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+ let baseURI = Services.io.newURI(`moz-extension://${extension.uuid}/`);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ baseURI,
+ {}
+ );
+
+ await BrowserTestUtils.waitForCondition(() => {
+ let regs = swm.getAllRegistrations();
+
+ for (let i = 0; i < regs.length; i++) {
+ let reg = regs.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ if (principal.equals(reg.principal)) {
+ return false;
+ }
+ }
+
+ info(`All ${extension.id} service worker registrations are gone`);
+ return true;
+ }, `All ${extension.id} service worker registrations should be deleted`);
+}