diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c7477122e..47194fc28 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -289,6 +289,10 @@ "message": "Use Autofill", "description": "Use Autofill" }, + "show_favicon": { + "message": "Show Website Icon", + "description": "Show Issuer Icon" + }, "use_high_contrast": { "message": "Use High Contrast", "description": "Use High Contrast" @@ -501,6 +505,9 @@ "permission_onedrive_cannot_revoke": { "message": "You must disable OneDrive backup first." }, + "permission_favicon": { + "message": "Allows fetching website icons." + }, "permission_unknown_permission": { "message": "Unknown permission. If see this message, please send a bug report." } diff --git a/manifests/manifest-chrome-testing.json b/manifests/manifest-chrome-testing.json index 5f3da5880..d2087999e 100644 --- a/manifests/manifest-chrome-testing.json +++ b/manifests/manifest-chrome-testing.json @@ -57,6 +57,8 @@ "optional_permissions": [ "clipboardWrite", "contextMenus", + "favicon", + "chrome://favicon/", "https://www.google.com/", "https://*.dropboxapi.com/*", "https://www.googleapis.com/*", @@ -66,5 +68,5 @@ ], "offline_enabled": true, "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo5++7m6mlJGqKOnlYehr9tjIqahMZBJUG7PLa7dSRk6bDUu2pVodO1TQWviHlrDTLP+zfoVbDBS8v8cjloK5Tn90nzC6a957dPzOfyC1WUNYNDlGM0BCmZKVP/MWB3d0ffOmTwaxh0L47aLH5nTW0AUmuwCWCBEEl4Acuyp7rwLNGlazBpaom1Qb5ckn29gCJVVVIZ6wudmcrG/FPTNJXQbg8N6wObGrgGOaxmowbkzJmIfKTyHlYOKLAjZ7aJi0W6jsy47/aV+ojvn4gO+ka6BcRhUeWgoQxqEky119f3OWiVP46SJVbAi0pkknThUjDvX11lATGjB5EvJZGyotwIDAQAB", - "content_security_policy": "script-src 'self' 'unsafe-eval'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/ ws://localhost:9090; default-src 'none'" + "content_security_policy": "script-src 'self' 'unsafe-eval'; font-src 'self'; img-src 'self' data: chrome://favicon/; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/ ws://localhost:9090; default-src 'none'" } diff --git a/manifests/manifest-chrome.json b/manifests/manifest-chrome.json index 17f97aec4..4bee1a5fa 100644 --- a/manifests/manifest-chrome.json +++ b/manifests/manifest-chrome.json @@ -60,6 +60,8 @@ "optional_permissions": [ "clipboardWrite", "contextMenus", + "favicon", + "chrome://favicon/", "https://www.google.com/", "https://*.dropboxapi.com/*", "https://www.googleapis.com/*", @@ -68,5 +70,5 @@ "https://login.microsoftonline.com/common/oauth2/v2.0/token" ], "offline_enabled": true, - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data: chrome://favicon/; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/; default-src 'none'" } diff --git a/manifests/manifest-edge.json b/manifests/manifest-edge.json index c2df891ef..72217e0cb 100644 --- a/manifests/manifest-edge.json +++ b/manifests/manifest-edge.json @@ -60,6 +60,8 @@ "optional_permissions": [ "clipboardWrite", "contextMenus", + "favicon", + "chrome://favicon/", "https://www.google.com/", "https://*.dropboxapi.com/*", "https://www.googleapis.com/*", @@ -68,5 +70,5 @@ "https://login.microsoftonline.com/common/oauth2/v2.0/token" ], "offline_enabled": true, - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data: chrome://favicon/; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/; default-src 'none'" } diff --git a/sass/popup.scss b/sass/popup.scss index 6849dd8c3..89e9a5b09 100644 --- a/sass/popup.scss +++ b/sass/popup.scss @@ -311,12 +311,23 @@ svg { .issuer { font-size: 12px; + line-height: 16px; + height: 16px; @include themify($themes) { color: themed("black-1"); } width: 80%; text-overflow: ellipsis; overflow: hidden; + + .issuerFavicon { + vertical-align: bottom; + margin-right: 5px; + height: 16px; + width: 16px; + border-radius: 3px; + border: 1px solid transparent; + } } .code { @@ -1068,6 +1079,11 @@ svg { color: #ccc; } + .issuerFavicon { + background: white; + border: 1px solid white !important; + } + ::-webkit-scrollbar { background: #1e1e1e !important; } @@ -1200,6 +1216,11 @@ svg { } } + .issuerFavicon { + background: white; + border: 1px solid white !important; + } + .showqr, .pin { svg { diff --git a/src/components/Popup/EntryComponent.vue b/src/components/Popup/EntryComponent.vue index 904feeae5..37cdac4ee 100644 --- a/src/components/Popup/EntryComponent.vue +++ b/src/components/Popup/EntryComponent.vue @@ -39,6 +39,14 @@
+ {{ entry.issuer.split("::")[0] + (theme === "compact" ? ` (${entry.account})` : "") @@ -90,12 +98,14 @@ import { mapState } from "vuex"; import * as QRGen from "qrcode-generator"; import { OTPEntry, OTPType, CodeState, OTPAlgorithm } from "../../models/otp"; import { EntryStorage } from "../../models/storage"; +import { isFirefox, isSafari } from "../../browser"; import IconMinusCircle from "../../../svg/minus-circle.svg"; import IconRedo from "../../../svg/redo.svg"; import IconQr from "../../../svg/qrcode.svg"; import IconBars from "../../../svg/bars.svg"; import IconPin from "../../../svg/pin.svg"; +import IconMedal from "../../../svg/medal.svg"; const computedPrototype = [ mapState("accounts", [ @@ -109,7 +119,10 @@ const computedPrototype = [ mapState("menu", ["theme"]), ]; -let computed = {}; +let computed: {} = { + shouldShowFavicon: + !isFirefox && !isSafari && mapState("menu", ["showFavicon"]).showFavicon, +}; for (const module of computedPrototype) { Object.assign(computed, module); @@ -137,6 +150,14 @@ export default Vue.extend({ entry.type !== OTPType.steam ); }, + getFaviconUrl(u: string) { + // TODO: Switch to commented out implementation when MV3 + // const url = new URL(chrome.runtime.getURL("/_favicon/")); + // url.searchParams.set("pageUrl", "https://" + u); + // url.searchParams.set("size", "16"); + // return url.toString(); + return "chrome://favicon/https://" + u; + }, showCode(code: string) { if (code === CodeState.Encrypted) { return this.i18n.encrypted; @@ -261,6 +282,7 @@ export default Vue.extend({ IconQr, IconBars, IconPin, + IconMedal, }, }); diff --git a/src/components/Popup/PreferencesPage.vue b/src/components/Popup/PreferencesPage.vue index 7926e33b4..40b3e1e22 100644 --- a/src/components/Popup/PreferencesPage.vue +++ b/src/components/Popup/PreferencesPage.vue @@ -43,6 +43,11 @@ @change="requireContextMenuPermission()" v-if="isSupported" /> +
{ + this.$store.commit( + "menu/setShowFavicon", + granted ? showFavicon : false + ); + } + ); + }, + }, }, data() { return { newStorageLocation: this.$store.state.menu.storageArea || localStorage.storageLocation, + isNotFirefox: navigator.userAgent.indexOf("Firefox") === -1, }; }, methods: { diff --git a/src/definitions/module-interface.d.ts b/src/definitions/module-interface.d.ts index 3dc038637..a7663a6d4 100644 --- a/src/definitions/module-interface.d.ts +++ b/src/definitions/module-interface.d.ts @@ -38,6 +38,7 @@ interface MenuState { theme: string; backupDisabled: boolean; storageArea: "sync" | "local"; + showFavicon: boolean; } interface StyleState { diff --git a/src/store/Menu.ts b/src/store/Menu.ts index e6f1428b9..15a0d0f1b 100644 --- a/src/store/Menu.ts +++ b/src/store/Menu.ts @@ -10,6 +10,7 @@ export class Menu implements Module { useAutofill: localStorage.autofill === "true", smartFilter: localStorage.smartFilter !== "false", enableContextMenu: localStorage.enableContextMenu === "true", + showFavicon: localStorage.showFavicon === "true", theme: localStorage.theme || (localStorage.highContrast === "true" @@ -47,6 +48,10 @@ export class Menu implements Module { state.enableContextMenu = enableContextMenu; localStorage.enableContextMenu = enableContextMenu; }, + setShowFavicon(state: MenuState, showFavicon: boolean) { + state.showFavicon = showFavicon; + localStorage.showFavicon = showFavicon; + }, setTheme(state: MenuState, theme: string) { state.theme = theme; localStorage.theme = theme; diff --git a/src/store/Permissions.ts b/src/store/Permissions.ts index 142fdf864..ce9ebaade 100644 --- a/src/store/Permissions.ts +++ b/src/store/Permissions.ts @@ -130,6 +130,16 @@ const permissions: Permission[] = [ }, ], }, + { + id: "favicon", + description: chrome.i18n.getMessage("permission_favicon"), + revocable: true, + }, + { + id: "chrome://favicon/*", + description: chrome.i18n.getMessage("permission_favicon"), + revocable: true, + }, ]; export class Permissions implements Module { diff --git a/svg/medal.svg b/svg/medal.svg new file mode 100644 index 000000000..48c83d328 --- /dev/null +++ b/svg/medal.svg @@ -0,0 +1 @@ + \ No newline at end of file