Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,11 @@
"javascript_apis": ["management.getAll", "management.setEnabled"],
"name": "theme-switcher"
},
{
"description": "Demonstration of how to use the <code>prefers-color-scheme</code> media query to adapt an SVG icon to dark and light themes.",
"javascript_apis": ["runtime.getURL","tabs.query", "tabs.create", "tabs.update", "management.getAll","management.setEnabled","management.onEnabled", "pageAction.onClicked","action.onClicked"],
"name": "themed-icons"
},
{
"description": "A collection of themes illustrating:<ul><li>weta_fade: a basic theme employing a single image specified in <code>theme_frame:</code>.</li><li>weta_fade_chrome: the weta_fade theme implemented with Chrome compatible manifest keys.</li><li>weta_tiled: a theme using a tiled image.</li><li>weta_mirror: a theme using multiple images and aligning those images in the header.</li><li>animated: use of an animated PNG.</li></ul>",
"javascript_apis": [],
Expand Down
27 changes: 27 additions & 0 deletions themed-icons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# themed-icons

This example demonstrates how to use the prefers-color-scheme media query to adapt an SVG icon to dark and light themes.

## What it does

This extension includes:

* a background script, `background.js`.
* page (address bar) and browser (toolbar) action icons.

The extension displays the [page action](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/pageAction) on any webpage, and the extension test page, which the extension opens when it starts, and the [browser action](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browserAction) is pinned to the toolbar when the extension is installed.

Clicking either icon opens the test extension page and switches to the next available theme.

When the active theme is enabled, the page and browser action change color based on the dark or light background color used in the UI.

For example:
- When the built-in Firefox Alpenglow or dark themes are active, the icons are black with a white outline.
- When the built-in Firefox light theme is active, the icons are red with a black outline.

## NOTE: Implicit CSS filter applied to pageAction SVG icons in dark themes in Firefox Desktop 151 and earlier

In builds where the `about:config` preference `extensions.webextensions.pageActionIconDarkModeFilter.enabled` is set to `true` or not defined, a greyscale and brightness CSS filter is applied to page action icons for dark themes. This filter can reduce the contrast of icons that use multiple colors. (See [Bug 2001318](https://bugzilla.mozilla.org/2001318).)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/thought: not an actual issue and I'm actually not 100% sure if this is just my own habit and convention, but I would have put the (See Bug 2001318) before the period that ends the previous phrase and moved the period after the close parenthesis, I mean something like ... that use multiple colors (see Bug 2001318)..


This implicit CSS filter is turned off in Firefox Desktop Nightly in release 149 and later, and on the release channel
as part of [Bug 2016509](https://bugzilla.mozilla.org/20

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: the url associated to Bug 2016509 link got cut out

32 changes: 32 additions & 0 deletions themed-icons/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
async function openOrSelectTab(reloadExistingTab = false) {
const tabUrl = browser.runtime.getURL("/extpage.html");
let [tab] = await browser.tabs.query({ url: tabUrl });
if (!tab) {
await browser.tabs.create({ url: tabUrl, active: true });
} else {
await browser.tabs.update(tab.id, {
active: true,
...(reloadExistingTab ? { url: tabUrl } : {})
});
}
}

async function switchToNextTheme() {
openOrSelectTab();
const themes = await browser.management.getAll().then(extensions => {
return extensions.filter(ext => ext.type === "theme");
});
const activeThemeIndex = themes.findIndex(theme => theme.enabled);
const nextThemeIndex = activeThemeIndex < themes.length - 1
? activeThemeIndex + 1
: 0
const nextTheme = themes[nextThemeIndex];
await browser.management.setEnabled(nextTheme.id, true);
}

// Switch to the next theme available on pageAction or action icons clicks.
browser.pageAction.onClicked.addListener(switchToNextTheme);
browser.action.onClicked.addListener(switchToNextTheme);

// Open or select the extension test page on addon startup.
openOrSelectTab(true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue(kind of minor, but still incorrect):

  • Now that the extension is an MV3 extension, the background script are going to be non-persistent (a.k.a. "event page"), and so the behavior described in this inline comment doesn't actually match the actual behavior anymore.
  • There is also a race that is likely to happen when the event page will get terminated and the user triggers it to be respawned by clicking on the action/pageAction buttons, in that case this call will be triggered (as a side effect of the previously terminated event page being respawned to handle the action button click event) around the same time we will be triggering the switchToNextTheme event handler to be handling the action click event itself. Due to this race it is not unlikely we would end up opening the extension page twice in two tabs

You should be able to trigger the race with the following STR:

  • install the add-on
  • close any tab that has the add-on extension page url (e.g. the one opened when the add-on is installed, and any other ones if there are more than one already)
  • make sure to not have any Add-on Debugging Developer Toolbox attached to this test extension (because that would prevent the event page from being terminated on idle)
  • open about:debugging and click "Terminate background script" and wait for the "Background script" state to be "stopped" (unless it is already stopped due to getting past the idle timeout that will be auto-terminating the background script)
  • once the "Background script" state to be "stopped", click on the add-on action button (either the pageAction or the action button, it doesn't matter for this STR)
    • Expected behavior: only one tab to be opened on the extension page
    • Actual behavior: two tabs gets opened on the extension page

Even if the race doesn't happen consistently (it was fairly consistent for me when I tried the STR described above on one of my dev machines though), I feel we should still adjust this part to account for the event page scripts to be executed again when the event page is respawned and adjust the inline comment.

e.g. I think that a reasonable approach would be to use browser.runtime.onInstalled to restrict this call to the first startup after being installed and adjust the inline comment accordingly:

diff --git a/themed-icons/background.js b/themed-icons/background.js
index 630ec8a..b717005 100644
--- a/themed-icons/background.js
+++ b/themed-icons/background.js
@@ -28,5 +28,7 @@ async function switchToNextTheme() {
 browser.pageAction.onClicked.addListener(switchToNextTheme);
 browser.action.onClicked.addListener(switchToNextTheme);
 
-// Open or select the extension test page on addon startup.
-openOrSelectTab(true);
+// Open the the extension page in a new tab after the add-on is installed.
+browser.runtime.onInstalled.addListener(() => {
+  openOrSelectTab(true);
+});

32 changes: 32 additions & 0 deletions themed-icons/extpage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>SVG themed icon using prefer-color-scheme media query</title>
<script defer="true" src="extpage.js"></script>
<style>
dt { font-weight: bold; }

body {
background-color: white;
color: black;
}

@media (prefers-color-scheme: dark) {
body {
background-color: black;
color: white;
}
}
</style>
</head>
<body>
<h1>SVG themed icon using prefer-color-scheme media query</h1>
<dl>
<dt>Active theme:</dt>
<dd id="active-theme">ACTIVE THEME INFO</dd>
</dl>
<h2>README.md</h2>
<pre id="read-me">README.md content</pre>
</body>
</html>

29 changes: 29 additions & 0 deletions themed-icons/extpage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
let activeThemeEl = document.querySelector("#active-theme");
let readmeEl = document.querySelector("#read-me");

function updateThemeInfo(info) {
activeThemeEl.textContent = `${info.name} (${info.id})`;
}

// Include the README.md file content into the test page.
fetch("/README.md").then(r => r.text()).then(text => {
readmeEl.textContent = text;
});

// Set active theme info element content on page load.
browser.management.getAll().then(addons => {
updateThemeInfo(addons.find(addon => addon.type == "theme" && addon.enabled));
});

// Show pageAction icon on the extension page.
browser.tabs.getCurrent().then(tabInfo => {
browser.pageAction.show(tabInfo.id);
});

// Update active theme info when a theme is enabled.
browser.management.onEnabled.addListener(info => {
if (info.type !== "theme") {
return;
}
updateThemeInfo(info);
});
37 changes: 37 additions & 0 deletions themed-icons/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{

"description": "Adds page action and action SVG icons that adapt to dark and light themes using prefers-color-scheme media query.",
"manifest_version": 3,
"name": "themed-icons",
"version": "1.0",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/themed-icons",
"browser_specific_settings":
{ "gecko":
{
"id": "themed-icons@mozilla.org",
"data_collection_permissions": {
"required": ["none"]
},
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: this part of the manifest isn't currently valid JSON (it looks like browser_specific_settings isn't closed and the commas at the end of line 14 isn't allowed in valid JSON), let's also format it so that it is easier to see how the open and closed curly brackets match each other:

diff --git a/themed-icons/manifest.json b/themed-icons/manifest.json
index b7a90b8..2af23e4 100644
--- a/themed-icons/manifest.json
+++ b/themed-icons/manifest.json
@@ -5,14 +5,14 @@
   "name": "themed-icons",
   "version": "1.0",
   "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/themed-icons",
-  "browser_specific_settings": 
-    { "gecko": 
-      {
-        "id": "themed-icons@mozilla.org",
-        "data_collection_permissions": {
-          "required": ["none"]
-       },
-     },
+  "browser_specific_settings": {
+    "gecko": {
+      "id": "themed-icons@mozilla.org",
+      "data_collection_permissions": {
+        "required": ["none"]
+      }
+    }
+  },
 
   "icons": { "32": "prefers-color-scheme-icon.svg" },


"icons": { "32": "prefers-color-scheme-icon.svg" },

"background": {
"scripts": ["background.js"],
"type": "module"
},

"page_action": {
"default_icon": "prefers-color-scheme-icon.svg",
"show_matches": ["<all_urls>"]
},

"action": {
"default_icon": "prefers-color-scheme-icon.svg",
"default_area": "navbar"
},

"permissions": [
"management"
]
}
13 changes: 13 additions & 0 deletions themed-icons/prefers-color-scheme-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading