summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js')
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js653
1 files changed, 653 insertions, 0 deletions
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
new file mode 100644
index 0000000000..0337da3a00
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -0,0 +1,653 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Test that various combinations of icon details specs, for both paths
+// and ImageData objects, result in the correct image being displayed in
+// all display resolutions.
+add_task(async function testDetailsObjects() {
+ function background() {
+ function getImageData(color) {
+ let canvas = document.createElement("canvas");
+ canvas.width = 2;
+ canvas.height = 2;
+ let canvasContext = canvas.getContext("2d");
+
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+ canvasContext.fillStyle = color;
+ canvasContext.fillRect(0, 0, 1, 1);
+
+ return {
+ url: canvas.toDataURL("image/png"),
+ imageData: canvasContext.getImageData(
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ ),
+ };
+ }
+
+ let imageData = {
+ red: getImageData("red"),
+ green: getImageData("green"),
+ };
+
+ // eslint-disable indent, indent-legacy
+ let iconDetails = [
+ // Only paths.
+ {
+ details: { path: "a.png" },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ },
+ },
+ {
+ details: { path: "/a.png" },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("a.png"),
+ pageActionImageURL: browser.runtime.getURL("a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("a.png"),
+ pageActionImageURL: browser.runtime.getURL("a.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 19: "a.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 38: "a.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 19: "a.png", 38: "a-x2.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ },
+ },
+ },
+ {
+ details: {
+ path: { 16: "a-16.png", 32: "a-32.png", 64: "a-64.png" },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a-16.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a-16.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a-32.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a-32.png"),
+ },
+ },
+ },
+
+ // Test that CSS strings are escaped properly.
+ {
+ details: { path: 'a.png#" \\' },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL(
+ "data/a.png#%22%20%5C"
+ ),
+ pageActionImageURL: browser.runtime.getURL("data/a.png#%22%20%5C"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL(
+ "data/a.png#%22%20%5C"
+ ),
+ pageActionImageURL: browser.runtime.getURL("data/a.png#%22%20%5C"),
+ },
+ },
+ },
+
+ // Only ImageData objects.
+ {
+ details: { imageData: imageData.red.imageData },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ },
+ },
+ {
+ details: { imageData: { 19: imageData.red.imageData } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ },
+ },
+ {
+ details: { imageData: { 38: imageData.red.imageData } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ },
+ },
+ {
+ details: {
+ imageData: {
+ 19: imageData.red.imageData,
+ 38: imageData.green.imageData,
+ },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: imageData.green.url,
+ pageActionImageURL: imageData.green.url,
+ },
+ },
+ },
+
+ // Mixed path and imageData objects.
+ //
+ // The behavior is currently undefined if both |path| and
+ // |imageData| specify icons of the same size.
+ {
+ details: {
+ path: { 19: "a.png" },
+ imageData: { 38: imageData.red.imageData },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ },
+ },
+ {
+ details: {
+ path: { 38: "a.png" },
+ imageData: { 19: imageData.red.imageData },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ },
+ },
+
+ // A path or ImageData object by itself is treated as a 19px icon.
+ {
+ details: {
+ path: "a.png",
+ imageData: { 38: imageData.red.imageData },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ },
+ },
+ {
+ details: {
+ path: { 38: "a.png" },
+ imageData: imageData.red.imageData,
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: imageData.red.url,
+ pageActionImageURL: imageData.red.url,
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ },
+ },
+
+ // Various resolutions
+ {
+ details: { path: { 18: "a.png", 36: "a-x2.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 16: "a.png", 30: "a-x2.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/a.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ pageActionImageURL: browser.runtime.getURL("data/a-x2.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 16: "16.png", 100: "100.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/16.png"),
+ pageActionImageURL: browser.runtime.getURL("data/16.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/100.png"),
+ pageActionImageURL: browser.runtime.getURL("data/100.png"),
+ },
+ },
+ },
+ {
+ details: { path: { 2: "2.png" } },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/2.png"),
+ pageActionImageURL: browser.runtime.getURL("data/2.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/2.png"),
+ pageActionImageURL: browser.runtime.getURL("data/2.png"),
+ },
+ },
+ },
+ {
+ details: {
+ path: {
+ 16: "16.svg",
+ 18: "18.svg",
+ },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/16.svg"),
+ pageActionImageURL: browser.runtime.getURL("data/16.svg"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/18.svg"),
+ pageActionImageURL: browser.runtime.getURL("data/18.svg"),
+ },
+ },
+ },
+ {
+ details: {
+ path: {
+ 6: "6.png",
+ 18: "18.png",
+ 36: "36.png",
+ 48: "48.png",
+ 128: "128.png",
+ },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/18.png"),
+ pageActionImageURL: browser.runtime.getURL("data/18.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/36.png"),
+ pageActionImageURL: browser.runtime.getURL("data/36.png"),
+ },
+ },
+ menuResolutions: {
+ 1: browser.runtime.getURL("data/36.png"),
+ 2: browser.runtime.getURL("data/128.png"),
+ },
+ },
+ {
+ details: {
+ path: {
+ 16: "16.png",
+ 18: "18.png",
+ 32: "32.png",
+ 48: "48.png",
+ 64: "64.png",
+ 128: "128.png",
+ },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/16.png"),
+ pageActionImageURL: browser.runtime.getURL("data/16.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/32.png"),
+ pageActionImageURL: browser.runtime.getURL("data/32.png"),
+ },
+ },
+ menuResolutions: {
+ 1: browser.runtime.getURL("data/32.png"),
+ 2: browser.runtime.getURL("data/64.png"),
+ },
+ },
+ {
+ details: {
+ path: {
+ 18: "18.png",
+ 32: "32.png",
+ 48: "48.png",
+ 128: "128.png",
+ },
+ },
+ resolutions: {
+ 1: {
+ browserActionImageURL: browser.runtime.getURL("data/32.png"),
+ pageActionImageURL: browser.runtime.getURL("data/32.png"),
+ },
+ 2: {
+ browserActionImageURL: browser.runtime.getURL("data/32.png"),
+ pageActionImageURL: browser.runtime.getURL("data/32.png"),
+ },
+ },
+ },
+ ];
+ /* eslint-enable indent, indent-legacy */
+
+ // Allow serializing ImageData objects for logging.
+ ImageData.prototype.toJSON = () => "<ImageData>";
+
+ let tabId;
+
+ browser.test.onMessage.addListener((msg, test) => {
+ if (msg != "setIcon") {
+ browser.test.fail("expecting 'setIcon' message");
+ }
+
+ let details = iconDetails[test.index];
+
+ let detailString = JSON.stringify(details);
+ browser.test.log(
+ `Setting browerAction/pageAction to ${detailString} expecting URLs ${JSON.stringify(
+ details.resolutions
+ )}`
+ );
+
+ Promise.all([
+ browser.browserAction.setIcon(
+ Object.assign({ tabId }, details.details)
+ ),
+ browser.pageAction.setIcon(Object.assign({ tabId }, details.details)),
+ ]).then(() => {
+ browser.test.sendMessage("iconSet");
+ });
+ });
+
+ // Generate a list of tests and resolutions to send back to the test
+ // context.
+ //
+ // This process is a bit convoluted, because the outer test context needs
+ // to handle checking the button nodes and changing the screen resolution,
+ // but it can't pass us icon definitions with ImageData objects. This
+ // shouldn't be a problem, since structured clones should handle ImageData
+ // objects without issue. Unfortunately, |cloneInto| implements a slightly
+ // different algorithm than we use in web APIs, and does not handle them
+ // correctly.
+ let tests = [];
+ for (let [idx, icon] of iconDetails.entries()) {
+ tests.push({
+ index: idx,
+ menuResolutions: icon.menuResolutions,
+ resolutions: icon.resolutions,
+ });
+ }
+
+ // Sort by resolution, so we don't needlessly switch back and forth
+ // between each test.
+ tests.sort(test => test.resolution);
+
+ browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+ tabId = tabs[0].id;
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("ready", tests);
+ });
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ browser_action: {
+ default_area: "navbar",
+ },
+ page_action: {},
+ background: {
+ page: "data/background.html",
+ },
+ },
+
+ files: {
+ "data/background.html": `<script src="background.js"></script>`,
+ "data/background.js": background,
+
+ "data/16.svg": imageBuffer,
+ "data/18.svg": imageBuffer,
+
+ "data/16.png": imageBuffer,
+ "data/18.png": imageBuffer,
+ "data/32.png": imageBuffer,
+ "data/36.png": imageBuffer,
+ "data/48.png": imageBuffer,
+ "data/64.png": imageBuffer,
+ "data/128.png": imageBuffer,
+
+ "a.png": imageBuffer,
+ "data/2.png": imageBuffer,
+ "data/100.png": imageBuffer,
+ "data/a.png": imageBuffer,
+ "data/a-x2.png": imageBuffer,
+ },
+ });
+
+ const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
+
+ await extension.startup();
+
+ let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(
+ makeWidgetId(extension.id)
+ );
+ let browserActionWidget = getBrowserActionWidget(extension);
+
+ let tests = await extension.awaitMessage("ready");
+ await promiseAnimationFrame();
+
+ // The initial icon should be the default icon since no icon is in the manifest.
+ const DEFAULT_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+ let browserActionButton =
+ browserActionWidget.forWindow(window).node.firstElementChild;
+ let pageActionImage = document.getElementById(pageActionId);
+ is(
+ getListStyleImage(browserActionButton),
+ DEFAULT_ICON,
+ `browser action has the correct default image`
+ );
+ is(
+ getListStyleImage(pageActionImage),
+ DEFAULT_ICON,
+ `page action has the correct default image`
+ );
+
+ for (let test of tests) {
+ extension.sendMessage("setIcon", test);
+ await extension.awaitMessage("iconSet");
+
+ await promiseAnimationFrame();
+
+ // Test icon sizes in the toolbar/urlbar.
+ for (let resolution of Object.keys(test.resolutions)) {
+ await SpecialPowers.pushPrefEnv({ set: [[RESOLUTION_PREF, resolution]] });
+
+ is(
+ window.devicePixelRatio,
+ +resolution,
+ "window has the required resolution"
+ );
+
+ let { browserActionImageURL, pageActionImageURL } =
+ test.resolutions[resolution];
+ is(
+ getListStyleImage(browserActionButton),
+ browserActionImageURL,
+ `browser action has the correct image at ${resolution}x resolution`
+ );
+ is(
+ getListStyleImage(pageActionImage),
+ pageActionImageURL,
+ `page action has the correct image at ${resolution}x resolution`
+ );
+
+ await SpecialPowers.popPrefEnv();
+ }
+
+ if (!test.menuResolutions) {
+ continue;
+ }
+ }
+
+ await extension.unload();
+});
+
+// NOTE: The current goal of this test is ensuring that Bug 1397196 has been fixed,
+// and so this test extension manifest has a browser action which specify
+// a theme based icon and a pageAction, both the pageAction and the browserAction
+// have a common default_icon.
+//
+// Once Bug 1398156 will be fixed, this test should be converted into testing that
+// the browserAction and pageAction themed icons (as well as any other cached icon,
+// e.g. the sidebar and devtools panel icons) can be specified in the same extension
+// and do not conflict with each other.
+//
+// This test currently fails without the related fix, but only if the browserAction
+// API has been already loaded before the pageAction, otherwise the icons will be cached
+// in the opposite order and the test is not able to reproduce the issue anymore.
+add_task(async function testPageActionIconLoadingOnBrowserActionThemedIcon() {
+ async function background() {
+ const tabs = await browser.tabs.query({ active: true });
+ await browser.pageAction.show(tabs[0].id);
+
+ browser.test.sendMessage("ready");
+ }
+
+ const extension = ExtensionTestUtils.loadExtension({
+ background,
+ manifest: {
+ name: "Foo Extension",
+
+ browser_action: {
+ default_icon: "common_cached_icon.png",
+ default_popup: "default_popup.html",
+ default_title: "BrowserAction title",
+ default_area: "navbar",
+ theme_icons: [
+ {
+ dark: "1.png",
+ light: "2.png",
+ size: 16,
+ },
+ ],
+ },
+
+ page_action: {
+ default_icon: "common_cached_icon.png",
+ default_popup: "default_popup.html",
+ default_title: "PageAction title",
+ },
+
+ permissions: ["tabs"],
+ },
+
+ files: {
+ "common_cached_icon.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
+ "default_popup.html": "<!DOCTYPE html><html><body>popup</body></html>",
+ },
+ });
+
+ await extension.startup();
+
+ await extension.awaitMessage("ready");
+
+ await promiseAnimationFrame();
+
+ let pageActionId = BrowserPageActions.urlbarButtonNodeIDForActionID(
+ makeWidgetId(extension.id)
+ );
+ let pageActionImage = document.getElementById(pageActionId);
+
+ const iconURL = new URL(getListStyleImage(pageActionImage));
+
+ is(
+ iconURL.pathname,
+ "/common_cached_icon.png",
+ "Got the expected pageAction icon url"
+ );
+
+ await extension.unload();
+});