|
| 1 | +# Dark Mode Extension Icons |
| 2 | + |
| 3 | +**Summary** |
| 4 | + |
| 5 | +Feature to enable developers to enhance extension icon visibility in dark mode. |
| 6 | + |
| 7 | +**Document Metadata** |
| 8 | + |
| 9 | +**Author:** solomonkinard |
| 10 | + |
| 11 | +**Sponsoring Browser:** Chromium |
| 12 | + |
| 13 | +**Contributors:** oliverdunk, xeenon, carlosjeurissen, hanguokai, dotproto |
| 14 | + |
| 15 | +**Created:** 2024-04-05 |
| 16 | + |
| 17 | +**Related Issues:** |
| 18 | +* https://crbug.com/893175 |
| 19 | +* https://github.com/w3c/webextensions/issues/229 |
| 20 | + |
| 21 | +## Motivation |
| 22 | + |
| 23 | +### Objective |
| 24 | + |
| 25 | +Extension developers will be able to supply and define a set of icons to be used |
| 26 | +in the event that the user has expressed the preference for a page that has a |
| 27 | +dark theme. |
| 28 | + |
| 29 | +#### Use Cases |
| 30 | + |
| 31 | +1. Improved icon visibility in the extension toolbar. |
| 32 | +1. Improved icon visibility on the management and shortcuts pages. |
| 33 | +1. Dark mode icon declarations made possible through the extension manifest. |
| 34 | +1. `setIcon()` will allow setting of dark and/or light mode specific icons. |
| 35 | +1. Improved icon visibility on context menu (Chrome relies on default |
| 36 | +icons, only Firefox and Safari can specify menu icons). |
| 37 | +1. Improved icon visibility on side panel (Chrome relies on default icons, |
| 38 | +Firefox can specify icons). |
| 39 | + |
| 40 | +### Known Consumers |
| 41 | + |
| 42 | +The Chromium bug has a significant amount of developer interest. |
| 43 | + |
| 44 | +## Specification |
| 45 | + |
| 46 | +### Examples |
| 47 | + |
| 48 | +manifest.json |
| 49 | +``` |
| 50 | +"icon_variants": [ |
| 51 | + { |
| 52 | + "any": "any.svg", |
| 53 | + }, |
| 54 | + { |
| 55 | + "16": "16.png", |
| 56 | + "32": "32.png", |
| 57 | + }, |
| 58 | + { |
| 59 | + "16": "dark16.png", |
| 60 | + "32": "dark32.png", |
| 61 | + "color_scheme": "dark", |
| 62 | + }, |
| 63 | + { |
| 64 | + "16": "light16.png", |
| 65 | + "32": "light32.png", |
| 66 | + "color_scheme": ["dark", "light"] |
| 67 | + } |
| 68 | +], |
| 69 | +"action": { |
| 70 | + "icon_variants": [...] |
| 71 | +} |
| 72 | +``` |
| 73 | + `icon_variants` requires an array with a set of icon groups objects. Each icon |
| 74 | +group consists of a set of icon paths and icon group matching criteria. |
| 75 | + |
| 76 | + The icon paths are set using the size as key and/or the keyword `"any"`. |
| 77 | + |
| 78 | + Optionally the `color_scheme` matching criterium which can either be a string or |
| 79 | +array of color schemes. |
| 80 | + |
| 81 | +Setting `icon_variants` dynamically: |
| 82 | +``` |
| 83 | +const exampleProperties: {string: string | ImageData}[] = [ |
| 84 | + { |
| 85 | + "any": "any.svg", |
| 86 | + }, |
| 87 | + { |
| 88 | + "16": "16.png", |
| 89 | + "32": "32.png" |
| 90 | + }, |
| 91 | + { |
| 92 | + "16": darkImageData16, |
| 93 | + "32": darkImageData32, |
| 94 | + "color_scheme": "dark" |
| 95 | + }, |
| 96 | + { |
| 97 | + "16": lightImageData16, |
| 98 | + "32": lightImageData32, |
| 99 | + "color_scheme": "light" |
| 100 | + } |
| 101 | +]; |
| 102 | +
|
| 103 | +// action.setIcon(). |
| 104 | +const createProperties = {variants: exampleProperties}; |
| 105 | +action.setIcon(createProperties); |
| 106 | +
|
| 107 | +// menus.*(), for supporting browsers. |
| 108 | +const menusProperties = {icon_variants: exampleProperties}; |
| 109 | +menus.create(menusProperties); |
| 110 | +menus.update(id, menusProperties); |
| 111 | +``` |
| 112 | + |
| 113 | +A benefit of this new structure is that it's more resiliant to future changes, |
| 114 | +thus allowing for more keys such as density (e.g. 2dppx), purpose (e.g. |
| 115 | +monochrome), and etc. |
| 116 | + |
| 117 | +## setIcon() |
| 118 | +Incumbent `action.setIcon()`, for reference. |
| 119 | +``` |
| 120 | +action.setIcon({ |
| 121 | + path, |
| 122 | + tabId |
| 123 | + imageData, |
| 124 | +}); |
| 125 | +``` |
| 126 | + |
| 127 | +### Behavior |
| 128 | + |
| 129 | +Existing manifest key. The behavior of the icons manifest key will remain |
| 130 | +unchanged. |
| 131 | +``` |
| 132 | +{ |
| 133 | + "icons": { |
| 134 | + "64": "icon_64.png" |
| 135 | + }, |
| 136 | + "action": { |
| 137 | + "default_icon": { |
| 138 | + "64": "action_64.png" |
| 139 | + } |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +Order of precedence. The new `icon_variants` keys and subkeys will take |
| 145 | +precedent, followed by the incumbent `icons` key. |
| 146 | + |
| 147 | +The `dark` icon variant can be used by browsers to improve readability of the |
| 148 | +icon. Often this will be when the OS or browser color scheme is dark (often |
| 149 | +referred to as "dark mode") but also when the browser uses a darker shade theme |
| 150 | +which has a background on which the dark icon variant would be more readable. |
| 151 | +The `light` icon variant is used otherwise. |
| 152 | + |
| 153 | +### New Permissions |
| 154 | + |
| 155 | +N/A. |
| 156 | + |
| 157 | +### Manifest File Changes [And API] |
| 158 | + |
| 159 | +**Summary** |
| 160 | + |
| 161 | +`icon_variants` is offered as a replacement for `icons`. `icon_variants` aims |
| 162 | +to be more flexible than pre-existing keys, allowing for unexpected declarations |
| 163 | +without causing errors that prevents extension installation. Absent |
| 164 | +`color_scheme` declaration, any matching individual `icon_variants` group will |
| 165 | +effectively have a wild-card `color_scheme`. |
| 166 | + |
| 167 | +**Misc** |
| 168 | +1. If the top-level `icon_variants` key is provided, the top level `icons` key |
| 169 | +will be ignored. |
| 170 | +1. `icon_variants` will not cause an error in the event that it is invalid. |
| 171 | +Instead, only the faulty icon group(s) will be ignored, with an optional |
| 172 | +warning. Warnings are preferred over errors because they're more adaptable to |
| 173 | +changes in the future. |
| 174 | +1. `color-scheme`. Any icon group that does not contain a `color_scheme` key |
| 175 | +will apply to all available options, e.g. both "light" and "dark". |
| 176 | +1. **Group**. If only one icon group is supplied, it will be used as the |
| 177 | +default, i.e. any of its matching conditions will be ignored. |
| 178 | + * In any icon group (aka in any icon variant), all types must be the same. |
| 179 | + For example, if one .png is supplied in a group, all other images in that |
| 180 | + group must also be .png. If more than one type is present in an icon group, |
| 181 | + the group can be ignored, marked as invalid and optionally show a warning |
| 182 | + during installation. |
| 183 | + |
| 184 | +**Matching** |
| 185 | +1. If `icon_variants` contains an icon group with matching conditions, the icon |
| 186 | +(s) specified in the first matching icon group based on insertion order will be |
| 187 | +used. The other icon groups after that match will be ignored. |
| 188 | +1. If there is more than one matching icon object, any without `color_scheme` |
| 189 | +will be applied to every possible color scheme. Therefore, a subsequent matching |
| 190 | +icon object with a `color_scheme` will not be used. |
| 191 | +1. Incorrectly typed `color_scheme` values will be ignored, with an optional |
| 192 | +warning. For example, `color_scheme: ["unknown"]` will be treated as an empty |
| 193 | +array. An empty array means the browser should mark the icon group as invalid |
| 194 | +and ignore it. |
| 195 | +1. If only one icon object is defined with a specific color scheme, that icon |
| 196 | +object will be applied to all color schemes. |
| 197 | +1. **Fuzzy matching** Inexact size matches will return the icon closest in size, |
| 198 | +starting with smaller sizes if available, retreating to the nearest larger size. |
| 199 | + |
| 200 | +**Size** |
| 201 | +1. The `"16"` is a size in `{"16": "icon.png"}` and any number can be used as a |
| 202 | +size, per the browser’s icon requirements. The word `"any"` can also be used in |
| 203 | +place of a number to represent an icon that can be shown at any size (usually |
| 204 | +vector images). The icon size used by the browser will be determined as follows: |
| 205 | +1. **Raster images**: Sizes are in pixels. For high-density devices (2x, 3x, |
| 206 | +etc.), the browser looks for double or triple the desired point size (e.g., 32 |
| 207 | +or 48 pixels for a 16 point request). If the exact pixel size is unavailable, |
| 208 | +the next largest pixel size or `"any"` will be used. |
| 209 | +1. **Vector images**: Sizes are in points, ensuring device independence. If the |
| 210 | +exact point size is unavailable, an integer multiple (e.g. 32, 48, etc.) or |
| 211 | +`"any"` will be used. |
| 212 | +1. If none of the specified icon groups have matching criteria, browsers should |
| 213 | +drop matching criteria in a specified order until it finds a group which results |
| 214 | +in a match. It will start by dropping any matching criteria which are |
| 215 | +unsupported/unknown. If still no match could be made, it will drop known |
| 216 | +matching criteria in a future agreed upon order. |
| 217 | + |
| 218 | +**action** |
| 219 | +1. Any icons set programmatically using `action.setIcon()` would override |
| 220 | +manifest-defined icons. |
| 221 | +1. For `”action”` icons, the browser first checks for `icon_variants` in the |
| 222 | +action. If not specified, it falls back to default_icon, then to the top-level |
| 223 | +`icon_variants` or icons. |
| 224 | +1. `action.setIcon({variants})` will not throw when giving it icon groups with |
| 225 | +properties it does not understand. Those icon groups will simply be ignored for |
| 226 | +making matches. If however, none of the icon groups are supported, an exception |
| 227 | +will be thrown allowing both feature detection and specifying fallbacks without |
| 228 | +requiring feature detection. |
| 229 | + |
| 230 | +## Security and Privacy |
| 231 | + |
| 232 | +### Exposed Sensitive Data |
| 233 | + |
| 234 | +N/A. |
| 235 | + |
| 236 | +### Abuse Mitigations |
| 237 | + |
| 238 | +N/A. |
| 239 | + |
| 240 | +### Additional Security Considerations |
| 241 | + |
| 242 | +N/A. |
| 243 | + |
| 244 | +## Alternatives |
| 245 | + |
| 246 | +### Existing Workarounds |
| 247 | + |
| 248 | +1. A developer could ask the user to specify what mode they're in and then |
| 249 | +dynamically set the icon to a dark mode icon using `action.setIcon()`. That |
| 250 | +wouldn't change the management page icon. |
| 251 | +2. A developer could change icons dynamically to dark mode icons if this is |
| 252 | +true: `window.matchMedia('(prefers-color-scheme: dark)')`. |
| 253 | + |
| 254 | +### Open Web API |
| 255 | + |
| 256 | +As stated in the workarounds section, the following is already an option to |
| 257 | +consider: `window.matchMedia('(prefers-color-scheme: dark)')`. However, that |
| 258 | +wouldn't automatically set icons dynamically, as it would require subsequent |
| 259 | +calls to `action.setIcon()`. It also wouldn't update the icon on the management |
| 260 | +pages. |
| 261 | + |
| 262 | +## Implementation Notes |
| 263 | + |
| 264 | +N/A. |
| 265 | + |
| 266 | +## Future Work |
| 267 | + |
| 268 | +N/A. |
0 commit comments