Skip to content

Commit 6de9959

Browse files
committed
Re-implement favicon support on MV3 dev (resolves stale PR merge conflict)
The original favicon PR (Authenticator-Extension#1118) targeted an MV2 base and had been open ~2 years; dev is now MV3 and the branch no longer merges. This re-implements the feature against current dev conventions and fixes the original bugs: - Built for MV3: uses the favicon permission + the chrome.runtime.getURL ('/_favicon/') endpoint instead of the dead MV2 chrome://favicon/ URL. Only the 'favicon' optional permission is requested (on toggle); the invalid MV2 'chrome://favicon/' host permission and CSP scheme are dropped. The extension's own _favicon resource is already covered by img-src 'self', so no CSP relaxation is needed. - EntryComponent.shouldShowFavicon is a proper reactive computed returning !isFirefox && !isSafari && menu.showFavicon (uses browser.ts constants), fixing the original && short-circuit bug that discarded the guard. - Wires showFavicon into the new UserSettings model (interface, BooleanOption, isBooleanOption) and the menu store, so the setting persists and syncs. - Favicons are fetched lazily per entry at render time; nothing bundled or pre-downloaded. medal.svg is a UI placeholder glyph, not a site favicon. Maintainer constraints honored: no mandatory permissions added, no pre-installed or pre-downloaded favicons.
1 parent 9d9660b commit 6de9959

12 files changed

Lines changed: 87 additions & 5 deletions

File tree

_locales/en/messages.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@
301301
"message": "Use High Contrast",
302302
"description": "Use High Contrast"
303303
},
304+
"show_favicon": {
305+
"message": "Show Website Icon",
306+
"description": "Show Issuer Icon"
307+
},
304308
"theme": {
305309
"message": "Theme",
306310
"description": "Theme"
@@ -512,6 +516,9 @@
512516
"permission_onedrive_cannot_revoke": {
513517
"message": "You must disable OneDrive backup first."
514518
},
519+
"permission_favicon": {
520+
"message": "Allows fetching website icons."
521+
},
515522
"permission_unknown_permission": {
516523
"message": "Unknown permission. If see this message, please send a bug report."
517524
},

manifests/manifest-chrome-testing.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
],
5858
"optional_permissions": [
5959
"clipboardWrite",
60-
"contextMenus"
60+
"contextMenus",
61+
"favicon"
6162
],
6263
"optional_host_permissions": [
6364
"https://www.google.com/",

manifests/manifest-chrome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
],
5858
"optional_permissions": [
5959
"clipboardWrite",
60-
"contextMenus"
60+
"contextMenus",
61+
"favicon"
6162
],
6263
"optional_host_permissions": [
6364
"https://www.google.com/",

manifests/manifest-edge.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
],
5757
"optional_permissions": [
5858
"clipboardWrite",
59-
"contextMenus"
59+
"contextMenus",
60+
"favicon"
6061
],
6162
"optional_host_permissions": [
6263
"https://www.google.com/",

sass/popup.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,20 @@ svg {
317317
width: 80%;
318318
text-overflow: ellipsis;
319319
overflow: hidden;
320+
321+
.issuerFavicon {
322+
vertical-align: bottom;
323+
margin-right: 5px;
324+
height: 16px;
325+
width: 16px;
326+
border-radius: 3px;
327+
border: 1px solid transparent;
328+
}
329+
}
330+
331+
.issuerFavicon {
332+
background-color: #fff;
333+
border: 1px solid #fff;
320334
}
321335

322336
.code {

src/components/Popup/EntryComponent.vue

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
<IconRedo />
4040
</div>
4141
<div class="issuer">
42+
<img
43+
class="issuerFavicon"
44+
v-if="shouldShowFavicon && entry.issuer.split('::')[1]"
45+
v-bind:src="getFaviconUrl(entry.issuer.split('::')[1])"
46+
/><IconMedal
47+
class="issuerFavicon"
48+
v-if="shouldShowFavicon && !entry.issuer.split('::')[1]"
49+
/>
4250
{{
4351
entry.issuer.split("::")[0] +
4452
(theme === "compact" ? ` (${entry.account})` : "")
@@ -90,13 +98,15 @@ import { mapState } from "vuex";
9098
import * as QRGen from "qrcode-generator";
9199
import { OTPEntry, OTPType, CodeState, OTPAlgorithm } from "../../models/otp";
92100
import { EntryStorage } from "../../models/storage";
101+
import { isFirefox, isSafari } from "../../browser";
93102
import { getCurrentTab, okToInjectContentScript } from "../../utils";
94103
95104
import IconMinusCircle from "../../../svg/minus-circle.svg";
96105
import IconRedo from "../../../svg/redo.svg";
97106
import IconQr from "../../../svg/qrcode.svg";
98107
import IconBars from "../../../svg/bars.svg";
99108
import IconPin from "../../../svg/pin.svg";
109+
import IconMedal from "../../../svg/medal.svg";
100110
101111
const computedPrototype = [
102112
mapState("accounts", [
@@ -110,7 +120,11 @@ const computedPrototype = [
110120
mapState("menu", ["theme"]),
111121
];
112122
113-
let computed = {};
123+
let computed = {
124+
shouldShowFavicon(this: any) {
125+
return !isFirefox && !isSafari && this.$store.state.menu.showFavicon;
126+
},
127+
};
114128
115129
for (const module of computedPrototype) {
116130
Object.assign(computed, module);
@@ -138,6 +152,12 @@ export default Vue.extend({
138152
entry.type !== OTPType.steam
139153
);
140154
},
155+
getFaviconUrl(u: string) {
156+
const url = new URL(chrome.runtime.getURL("/_favicon/"));
157+
url.searchParams.set("pageUrl", "https://" + u);
158+
url.searchParams.set("size", "16");
159+
return url.toString();
160+
},
141161
showCode(code: string) {
142162
if (code === CodeState.Encrypted) {
143163
return this.i18n.encrypted;
@@ -254,6 +274,7 @@ export default Vue.extend({
254274
IconQr,
255275
IconBars,
256276
IconPin,
277+
IconMedal,
257278
},
258279
});
259280

src/components/Popup/PreferencesPage.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
@change="requireContextMenuPermission()"
4444
v-if="isSupported"
4545
/>
46+
<a-toggle-input
47+
:label="i18n.show_favicon"
48+
v-model="showFavicon"
49+
v-if="isSupported"
50+
/>
4651
<div class="control-group" v-show="!!defaultEncryption">
4752
<label class="combo-label">{{ i18n.autolock }}</label>
4853
<input
@@ -103,6 +108,22 @@ export default Vue.extend({
103108
this.$store.commit("menu/setEnableContextMenu", enableContextMenu);
104109
},
105110
},
111+
showFavicon: {
112+
get(): boolean {
113+
return this.$store.state.menu.showFavicon;
114+
},
115+
set(showFavicon: boolean) {
116+
chrome.permissions.request(
117+
{ permissions: ["favicon"] },
118+
(granted) => {
119+
this.$store.commit(
120+
"menu/setShowFavicon",
121+
granted ? showFavicon : false
122+
);
123+
}
124+
);
125+
},
126+
},
106127
theme: {
107128
get(): string {
108129
return this.$store.state.menu.theme;

src/definitions/module-interface.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface MenuState {
3636
useAutofill: boolean;
3737
smartFilter: boolean;
3838
enableContextMenu: boolean;
39+
showFavicon: boolean;
3940
theme: string;
4041
backupDisabled: boolean;
4142
storageArea: "sync" | "local";

src/models/settings.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface UserSettingsData {
2929
enableContextMenu?: boolean;
3030
encodedPhrase?: string;
3131
smartFilter?: boolean;
32+
showFavicon?: boolean;
3233
theme?: string;
3334
zoom?: number;
3435
}
@@ -184,7 +185,8 @@ type BooleanOption =
184185
| "oneDriveBusiness"
185186
| "oneDriveEncrypted"
186187
| "oneDriveRevoked"
187-
| "smartFilter";
188+
| "smartFilter"
189+
| "showFavicon";
188190

189191
type NumberOption = "autolock" | "lastRemindingBackupTime" | "offset" | "zoom";
190192

@@ -202,6 +204,7 @@ function isBooleanOption(key: string): key is BooleanOption {
202204
"oneDriveEncrypted",
203205
"oneDriveRevoked",
204206
"smartFilter",
207+
"showFavicon",
205208
].includes(key);
206209
}
207210

src/store/Menu.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class Menu implements Module {
1313
useAutofill: UserSettings.items.autofill === true,
1414
smartFilter: UserSettings.items.smartFilter === true,
1515
enableContextMenu: UserSettings.items.enableContextMenu === true,
16+
showFavicon: UserSettings.items.showFavicon === true,
1617
theme: UserSettings.items.theme || (isSafari ? "flat" : "normal"),
1718
autolock: Number(UserSettings.items.autolock) || 30,
1819
backupDisabled: await ManagedStorage.get("disableBackup", false),
@@ -48,6 +49,11 @@ export class Menu implements Module {
4849
UserSettings.items.enableContextMenu = enableContextMenu;
4950
UserSettings.commitItems();
5051
},
52+
setShowFavicon(state: MenuState, showFavicon: boolean) {
53+
state.showFavicon = showFavicon;
54+
UserSettings.items.showFavicon = showFavicon;
55+
UserSettings.commitItems();
56+
},
5157
setTheme(state: MenuState, theme: string) {
5258
state.theme = theme;
5359
UserSettings.items.theme = theme;

0 commit comments

Comments
 (0)