From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_ext_browserAction_pageAction_icon.js | 653 +++++++++++++++++++++ 1 file changed, 653 insertions(+) create mode 100644 browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js (limited to 'browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js') 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 = () => ""; + + 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": ``, + "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": "popup", + }, + }); + + 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(); +}); -- cgit v1.2.3