678 lines
35 KiB
JavaScript
678 lines
35 KiB
JavaScript
/*
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
/*
|
|
original:
|
|
http://github.com/piroor/webextensions-lib-tab-favicon-helper
|
|
*/
|
|
'use strict';
|
|
|
|
const TabFavIconHelper = {
|
|
LAST_EFFECTIVE_FAVICON: 'last-effective-favIcon',
|
|
VALID_FAVICON_PATTERN: /^(about|app|chrome|data|file|ftp|https?|moz-extension|resource):/,
|
|
DRAWABLE_FAVICON_PATTERN: /^(https?|moz-extension|resource):/,
|
|
|
|
// original: chrome://browser/content/aboutlogins/icons/favicon.svg
|
|
FAVICON_LOCKWISE: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32">
|
|
<defs>
|
|
<linearGradient id="b" x1="24.684" y1="5.756" x2="6.966" y2="26.663" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0" stop-color="#ff9640"/>
|
|
<stop offset=".6" stop-color="#fc4055"/>
|
|
<stop offset="1" stop-color="#e31587"/>
|
|
</linearGradient>
|
|
<linearGradient id="c" x1="26.362" y1="4.459" x2="10.705" y2="21.897" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0" stop-color="#fff36e" stop-opacity=".8"/>
|
|
<stop offset=".094" stop-color="#fff36e" stop-opacity=".699"/>
|
|
<stop offset=".752" stop-color="#fff36e" stop-opacity="0"/>
|
|
</linearGradient>
|
|
<linearGradient id="d" x1="7.175" y1="27.275" x2="23.418" y2="11.454" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0" stop-color="#0090ed"/>
|
|
<stop offset=".386" stop-color="#5b6df8"/>
|
|
<stop offset=".629" stop-color="#9059ff"/>
|
|
<stop offset="1" stop-color="#b833e1"/>
|
|
</linearGradient>
|
|
<linearGradient id="a" x1="29.104" y1="15.901" x2="26.135" y2="21.045" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0" stop-color="#722291" stop-opacity=".5"/>
|
|
<stop offset=".5" stop-color="#722291" stop-opacity="0"/>
|
|
</linearGradient>
|
|
<linearGradient id="e" x1="20.659" y1="21.192" x2="13.347" y2="28.399" xlink:href="#a"/>
|
|
<linearGradient id="f" x1="2.97" y1="19.36" x2="10.224" y2="21.105" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0" stop-color="#054096" stop-opacity=".5"/>
|
|
<stop offset=".054" stop-color="#0f3d9c" stop-opacity=".441"/>
|
|
<stop offset=".261" stop-color="#2f35b1" stop-opacity=".249"/>
|
|
<stop offset=".466" stop-color="#462fbf" stop-opacity=".111"/>
|
|
<stop offset=".669" stop-color="#542bc8" stop-opacity=".028"/>
|
|
<stop offset=".864" stop-color="#592acb" stop-opacity="0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<path d="M15.986 31.076A5.635 5.635 0 0 1 12.3 29.76 103.855 103.855 0 0 1 2.249 19.7a5.841 5.841 0 0 1-.006-7.4A103.792 103.792 0 0 1 12.3 2.247a5.837 5.837 0 0 1 7.4-.006A104.1 104.1 0 0 1 29.751 12.3a5.842 5.842 0 0 1 .006 7.405c-.423.484-.584.661-.917 1.025l-.234.255a1.749 1.749 0 1 1-2.585-2.357l.234-.258c.314-.344.467-.511.86-.961a2.352 2.352 0 0 0-.008-2.814 100.308 100.308 0 0 0-9.7-9.714 2.352 2.352 0 0 0-2.814.007 100.323 100.323 0 0 0-9.7 9.7 2.354 2.354 0 0 0 .006 2.815 100.375 100.375 0 0 0 9.7 9.708 2.352 2.352 0 0 0 2.815-.006c1.311-1.145 2.326-2.031 3.434-3.086l-3.4-3.609a2.834 2.834 0 0 1-.776-2.008 2.675 2.675 0 0 1 .834-1.9l.194-.184a2.493 2.493 0 0 0 1.124-2.333 2.81 2.81 0 0 0-5.6 0 2.559 2.559 0 0 0 1.092 2.279l.127.118a2.735 2.735 0 0 1 .324 3.7 1.846 1.846 0 0 1-.253.262l-1.578 1.326a1.75 1.75 0 0 1-2.252-2.68l.755-.634a5.758 5.758 0 0 1-1.715-4.366 6.305 6.305 0 0 1 12.6 0 5.642 5.642 0 0 1-1.854 4.528l3.84 4.082a2.071 2.071 0 0 1 .59 1.446 2.128 2.128 0 0 1-.628 1.526c-1.6 1.592-2.895 2.72-4.533 4.15a5.745 5.745 0 0 1-3.753 1.354z" fill="url(#b)"/>
|
|
<path d="M15.986 31.076A5.635 5.635 0 0 1 12.3 29.76 103.855 103.855 0 0 1 2.249 19.7a5.841 5.841 0 0 1-.006-7.4A103.792 103.792 0 0 1 12.3 2.247a5.837 5.837 0 0 1 7.4-.006A104.1 104.1 0 0 1 29.751 12.3a5.842 5.842 0 0 1 .006 7.405c-.423.484-.584.661-.917 1.025l-.234.255a1.749 1.749 0 1 1-2.585-2.357l.234-.258c.314-.344.467-.511.86-.961a2.352 2.352 0 0 0-.008-2.814 100.308 100.308 0 0 0-9.7-9.714 2.352 2.352 0 0 0-2.814.007 100.323 100.323 0 0 0-9.7 9.7 2.354 2.354 0 0 0 .006 2.815 100.375 100.375 0 0 0 9.7 9.708 2.352 2.352 0 0 0 2.815-.006c1.311-1.145 2.326-2.031 3.434-3.086l-3.4-3.609a2.834 2.834 0 0 1-.776-2.008 2.675 2.675 0 0 1 .834-1.9l.194-.184a2.493 2.493 0 0 0 1.124-2.333 2.81 2.81 0 0 0-5.6 0 2.559 2.559 0 0 0 1.092 2.279l.127.118a2.735 2.735 0 0 1 .324 3.7 1.846 1.846 0 0 1-.253.262l-1.578 1.326a1.75 1.75 0 0 1-2.252-2.68l.755-.634a5.758 5.758 0 0 1-1.715-4.366 6.305 6.305 0 0 1 12.6 0 5.642 5.642 0 0 1-1.854 4.528l3.84 4.082a2.071 2.071 0 0 1 .59 1.446 2.128 2.128 0 0 1-.628 1.526c-1.6 1.592-2.895 2.72-4.533 4.15a5.745 5.745 0 0 1-3.753 1.354z" fill="url(#c)"/>
|
|
<path d="M29.58 17.75a34.267 34.267 0 0 0-2.473-3.15 2.352 2.352 0 0 1 .008 2.814c-.393.45-.546.617-.86.961l-.234.258a1.749 1.749 0 1 0 2.585 2.357l.234-.255c.231-.253.379-.415.59-.653a2.161 2.161 0 0 0 .15-2.332zm-8.734 6.275c-1.108 1.055-2.123 1.941-3.434 3.086a2.352 2.352 0 0 1-2.815.006 100.375 100.375 0 0 1-9.7-9.708 2.354 2.354 0 0 1-.006-2.815s-2.131 1.984-2.4 3.424a2.956 2.956 0 0 0 .724 2.782 103.772 103.772 0 0 0 9.085 8.96 5.635 5.635 0 0 0 3.683 1.316 5.745 5.745 0 0 0 3.753-1.351c.926-.808 1.741-1.52 2.565-2.273a1.558 1.558 0 0 0 0-1.476 11.55 11.55 0 0 0-1.455-1.951z" fill="url(#d)"/>
|
|
<path d="M29.43 20.079c-.211.238-.359.4-.59.653l-.234.255a1.749 1.749 0 1 1-2.585-2.357l.234-.258c.314-.344.467-.511.86-.961a2.352 2.352 0 0 0-.008-2.814 34.267 34.267 0 0 1 2.473 3.153 2.161 2.161 0 0 1-.15 2.329z" fill="url(#a)"/>
|
|
<path d="M22.3 25.976a11.55 11.55 0 0 0-1.458-1.951c-1.108 1.055-2.123 1.941-3.434 3.086a2.352 2.352 0 0 1-2.815.006c-1.414-1.234-2.768-2.5-4.095-3.8L8.023 25.8c1.384 1.35 2.8 2.668 4.28 3.958a5.635 5.635 0 0 0 3.683 1.316 5.745 5.745 0 0 0 3.753-1.351c.926-.808 1.741-1.52 2.565-2.273a1.558 1.558 0 0 0-.004-1.474z" fill="url(#e)"/>
|
|
<path d="M4.892 17.409a2.354 2.354 0 0 1-.006-2.815s-2.131 1.984-2.4 3.424a2.956 2.956 0 0 0 .729 2.782c1.56 1.739 3.158 3.4 4.808 5.007l2.479-2.48c-1.935-1.891-3.802-3.844-5.61-5.918z" opacity=".9" fill="url(#f)"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://browser/content/robot.ico
|
|
FAVICON_ROBOT: `
|
|
data:image/x-icon;base64,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
|
|
`.trim(),
|
|
// original: chrome://browser/skin/controlcenter/dashboard.svg
|
|
FAVICON_DASHBOARD: `
|
|
<svg data-name="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15 12H4a2 2 0 0 1-2-2V3a1 1 0 0 0-2 0v7a4 4 0 0 0 4 4h11a1 1 0 0 0 0-2z"/>
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M4 11a1 1 0 0 0 1-1V8a1 1 0 0 0-2 0v2a1 1 0 0 0 1 1zM7 11a1 1 0 0 0 1-1V4a1 1 0 0 0-2 0v6a1 1 0 0 0 1 1zM10 11a1 1 0 0 0 1-1V6a1 1 0 0 0-2 0v4a1 1 0 0 0 1 1zM13 11a1 1 0 0 0 1-1V7a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://browser/skin/developer.svg
|
|
FAVICON_DEVELOPER: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.555 3.2l-2.434 2.436a1.243 1.243 0 1 1-1.757-1.757L12.8 1.445A3.956 3.956 0 0 0 11 1a3.976 3.976 0 0 0-3.434 6.02l-6.273 6.273a1 1 0 1 0 1.414 1.414L8.98 8.434A3.96 3.96 0 0 0 11 9a4 4 0 0 0 4-4 3.956 3.956 0 0 0-.445-1.8z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://browser/skin/privatebrowsing/favicon.svg
|
|
FAVICON_PRIVATE_BROWSING: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<circle cx="8" cy="8" r="8" fill="#8d20ae"/>
|
|
<circle cx="8" cy="8" r="7.5" stroke="#7b149a" stroke-width="1" fill="none"/>
|
|
<path d="M11.309,10.995C10.061,10.995,9.2,9.5,8,9.5s-2.135,1.5-3.309,1.5c-1.541,0-2.678-1.455-2.7-3.948C1.983,5.5,2.446,5.005,4.446,5.005S7.031,5.822,8,5.822s1.555-.817,3.555-0.817S14.017,5.5,14.006,7.047C13.987,9.54,12.85,10.995,11.309,10.995ZM5.426,6.911a1.739,1.739,0,0,0-1.716.953A2.049,2.049,0,0,0,5.3,8.544c0.788,0,1.716-.288,1.716-0.544A1.428,1.428,0,0,0,5.426,6.911Zm5.148,0A1.429,1.429,0,0,0,8.981,8c0,0.257.928,0.544,1.716,0.544a2.049,2.049,0,0,0,1.593-.681A1.739,1.739,0,0,0,10.574,6.911Z" stroke="#670c83" stroke-width="2" fill="none"/>
|
|
<path d="M11.309,10.995C10.061,10.995,9.2,9.5,8,9.5s-2.135,1.5-3.309,1.5c-1.541,0-2.678-1.455-2.7-3.948C1.983,5.5,2.446,5.005,4.446,5.005S7.031,5.822,8,5.822s1.555-.817,3.555-0.817S14.017,5.5,14.006,7.047C13.987,9.54,12.85,10.995,11.309,10.995ZM5.426,6.911a1.739,1.739,0,0,0-1.716.953A2.049,2.049,0,0,0,5.3,8.544c0.788,0,1.716-.288,1.716-0.544A1.428,1.428,0,0,0,5.426,6.911Zm5.148,0A1.429,1.429,0,0,0,8.981,8c0,0.257.928,0.544,1.716,0.544a2.049,2.049,0,0,0,1.593-.681A1.739,1.739,0,0,0,10.574,6.911Z" fill="#fff"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://browser/skin/settings.svg
|
|
FAVICON_SETTINGS: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15 7h-2.1a4.967 4.967 0 0 0-.732-1.753l1.49-1.49a1 1 0 0 0-1.414-1.414l-1.49 1.49A4.968 4.968 0 0 0 9 3.1V1a1 1 0 0 0-2 0v2.1a4.968 4.968 0 0 0-1.753.732l-1.49-1.49a1 1 0 0 0-1.414 1.415l1.49 1.49A4.967 4.967 0 0 0 3.1 7H1a1 1 0 0 0 0 2h2.1a4.968 4.968 0 0 0 .737 1.763c-.014.013-.032.017-.045.03l-1.45 1.45a1 1 0 1 0 1.414 1.414l1.45-1.45c.013-.013.018-.031.03-.045A4.968 4.968 0 0 0 7 12.9V15a1 1 0 0 0 2 0v-2.1a4.968 4.968 0 0 0 1.753-.732l1.49 1.49a1 1 0 0 0 1.414-1.414l-1.49-1.49A4.967 4.967 0 0 0 12.9 9H15a1 1 0 0 0 0-2zM5 8a3 3 0 1 1 3 3 3 3 0 0 1-3-3z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://browser/skin/window.svg
|
|
FAVICON_WINDOW: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h11a2 2 0 0 0 2-2V4a3 3 0 0 0-3-3zm1 11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h12zm0-7H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://devtools/skin/images/profiler-stopwatch.svg
|
|
FAVICON_PROFILER: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M10.85 6.85a.5.5 0 0 0-.7-.7l-2.5 2.5.7.7 2.5-2.5zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1zM8 4a5 5 0 1 0 0 10A5 5 0 0 0 8 4zM1 9a7 7 0 1 1 14 0A7 7 0 0 1 1 9z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://global/skin/icons/performance.svg
|
|
FAVICON_PERFORMANCE: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
|
<path fill="context-fill" d="M8 1a8 8 0 0 0-8 8 7.89 7.89 0 0 0 .78 3.43 1 1 0 0 0 .9.57.94.94 0 0 0 .43-.1 1 1 0 0 0 .47-1.33A6.07 6.07 0 0 1 2 9a6 6 0 0 1 12 0 5.93 5.93 0 0 1-.59 2.57 1 1 0 0 0 1.81.86A7.89 7.89 0 0 0 16 9a8 8 0 0 0-8-8z"/>
|
|
<path fill="context-fill" d="M11.77 7.08a.5.5 0 0 0-.69.15L8.62 11.1A2.12 2.12 0 0 0 8 11a2 2 0 0 0 0 4 2.05 2.05 0 0 0 1.12-.34 2.31 2.31 0 0 0 .54-.54 2 2 0 0 0 0-2.24 2.31 2.31 0 0 0-.2-.24l2.46-3.87a.5.5 0 0 0-.15-.69z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://global/skin/icons/warning.svg
|
|
FAVICON_WARNING: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.742 12.106L9.789 2.2a2 2 0 0 0-3.578 0l-4.953 9.91A2 2 0 0 0 3.047 15h9.905a2 2 0 0 0 1.79-2.894zM7 5a1 1 0 0 1 2 0v4a1 1 0 0 1-2 0zm1 8.25A1.25 1.25 0 1 1 9.25 12 1.25 1.25 0 0 1 8 13.25z"/>
|
|
</svg>
|
|
`,
|
|
// original: chrome://mozapps/skin/extensions/extensionGeneric-16.svg
|
|
FAVICON_EXTENSION: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
|
|
<path d="M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z"/>
|
|
</svg>
|
|
`,
|
|
// original: globe-16.svg
|
|
FAVICON_GLOBE: `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
<path fill="context-fill" d="M8 0a8 8 0 1 0 8 8 8.009 8.009 0 0 0-8-8zm5.163 4.958h-1.552a7.7 7.7 0 0 0-1.051-2.376 6.03 6.03 0 0 1 2.603 2.376zM14 8a5.963 5.963 0 0 1-.335 1.958h-1.821A12.327 12.327 0 0 0 12 8a12.327 12.327 0 0 0-.156-1.958h1.821A5.963 5.963 0 0 1 14 8zm-6 6c-1.075 0-2.037-1.2-2.567-2.958h5.135C10.037 12.8 9.075 14 8 14zM5.174 9.958a11.084 11.084 0 0 1 0-3.916h5.651A11.114 11.114 0 0 1 11 8a11.114 11.114 0 0 1-.174 1.958zM2 8a5.963 5.963 0 0 1 .335-1.958h1.821a12.361 12.361 0 0 0 0 3.916H2.335A5.963 5.963 0 0 1 2 8zm6-6c1.075 0 2.037 1.2 2.567 2.958H5.433C5.963 3.2 6.925 2 8 2zm-2.56.582a7.7 7.7 0 0 0-1.051 2.376H2.837A6.03 6.03 0 0 1 5.44 2.582zm-2.6 8.46h1.549a7.7 7.7 0 0 0 1.051 2.376 6.03 6.03 0 0 1-2.603-2.376zm7.723 2.376a7.7 7.7 0 0 0 1.051-2.376h1.552a6.03 6.03 0 0 1-2.606 2.376z"/>
|
|
</svg>
|
|
`,
|
|
|
|
async _urlToKey(url) { // sha1 hash
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(url);
|
|
|
|
const hashBuffer = await crypto.subtle.digest('SHA-1', data);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
|
|
return hashHex;
|
|
},
|
|
|
|
DB_NAME: 'TabFavIconHelper',
|
|
DB_VERSION: 2,
|
|
STORE_FAVICONS: 'favIcons',
|
|
STORE_EFFECTIVE_FAVICONS: 'effectiveFavIcons',
|
|
STORE_UNEFFECTIVE_FAVICONS: 'uneffectiveFavIcons',
|
|
EXPIRATION_TIME_IN_MSEC: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
|
|
async _openDB() {
|
|
if (this._openedDB)
|
|
return this._openedDB;
|
|
return new Promise((resolve, _reject) => {
|
|
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
|
|
|
|
request.onerror = () => {
|
|
// This can fail if this is in a private window.
|
|
// See: https://github.com/piroor/treestyletab/issues/3387
|
|
//reject(new Error('Failed to open database'));
|
|
resolve(null);
|
|
};
|
|
|
|
request.onsuccess = () => {
|
|
const db = request.result;
|
|
this._openedDB = db;
|
|
resolve(db);
|
|
};
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result;
|
|
const objectStores = db.objectStoreNames;
|
|
|
|
const needToUpgrade = event.oldVersion < this.DB_VERSION;
|
|
if (needToUpgrade) {
|
|
if (objectStores.contains(this.STORE_FAVICONS))
|
|
db.deleteObjectStore(this.STORE_FAVICONS);
|
|
if (objectStores.contains(this.STORE_EFFECTIVE_FAVICONS))
|
|
db.deleteObjectStore(this.STORE_EFFECTIVE_FAVICONS);
|
|
if (objectStores.contains(this.STORE_UNEFFECTIVE_FAVICONS))
|
|
db.deleteObjectStore(this.STORE_UNEFFECTIVE_FAVICONS);
|
|
}
|
|
|
|
if (needToUpgrade ||
|
|
!objectStores.contains(this.STORE_FAVICONS)) {
|
|
const favIconsStore = db.createObjectStore(this.STORE_FAVICONS, { keyPath: 'key', unique: true });
|
|
favIconsStore.createIndex('urlKey', 'urlKey', { unique: false });
|
|
favIconsStore.createIndex('timestamp', 'timestamp');
|
|
}
|
|
|
|
if (needToUpgrade ||
|
|
!objectStores.contains(this.STORE_EFFECTIVE_FAVICONS)) {
|
|
const effectiveFavIconsStore = db.createObjectStore(this.STORE_EFFECTIVE_FAVICONS, { keyPath: 'urlKey', unique: true });
|
|
effectiveFavIconsStore.createIndex('timestamp', 'timestamp');
|
|
effectiveFavIconsStore.createIndex('favIconKey', 'favIconKey', { unique: false });
|
|
}
|
|
|
|
if (needToUpgrade ||
|
|
!objectStores.contains(this.STORE_UNEFFECTIVE_FAVICONS)) {
|
|
const uneffectiveFavIconsStore = db.createObjectStore(this.STORE_UNEFFECTIVE_FAVICONS, { keyPath: 'urlKey', unique: true });
|
|
uneffectiveFavIconsStore.createIndex('timestamp', 'timestamp');
|
|
uneffectiveFavIconsStore.createIndex('favIconKey', 'favIconKey', { unique: false });
|
|
}
|
|
};
|
|
});
|
|
},
|
|
|
|
async _associateFavIconUrlToTabUrl({ favIconUrl, tabUrl, store } = {}) {
|
|
const [db, tabUrlKey, favIconKey] = await Promise.all([
|
|
this._openDB(),
|
|
this._urlToKey(tabUrl),
|
|
this._urlToKey(favIconUrl),
|
|
]);
|
|
if (!db)
|
|
return;
|
|
|
|
try {
|
|
const transaction = db.transaction([store, this.STORE_FAVICONS], 'readwrite');
|
|
const associationStore = transaction.objectStore(store);
|
|
const favIconStore = transaction.objectStore(this.STORE_FAVICONS);
|
|
const timestamp = Date.now();
|
|
|
|
const associationRequest = associationStore.put({ urlKey: tabUrlKey, favIconKey, timestamp });
|
|
const favIconRequest = favIconStore.put({ key: favIconKey, url: favIconUrl, timestamp });
|
|
|
|
transaction.oncomplete = () => {
|
|
//db.close();
|
|
this._reserveToExpireOldEntries();
|
|
favIconUrl = undefined;
|
|
tabUrl = undefined;
|
|
store = undefined;
|
|
};
|
|
|
|
associationRequest.onerror = event => {
|
|
console.error(`Failed to associate favIconUrl ${favIconUrl} to tabUrl ${tabUrl} in the store ${store}`, event);
|
|
};
|
|
|
|
favIconRequest.onerror = event => {
|
|
console.error(`Failed to store favIconUrl ${favIconUrl} to tabUrl ${tabUrl} in the store ${store}`, event);
|
|
};
|
|
}
|
|
catch(error) {
|
|
console.error(`Failed to associate favIconUrl ${favIconUrl} to tabUrl ${tabUrl} in the store ${store}`, error);
|
|
}
|
|
},
|
|
|
|
async _unassociateFavIconUrlFromTabUrl({ tabUrl, store } = {}) {
|
|
const [db, tabUrlKey] = await Promise.all([
|
|
this._openDB(),
|
|
this._urlToKey(tabUrl),
|
|
]);
|
|
if (!db)
|
|
return;
|
|
|
|
try {
|
|
const transaction = db.transaction([store], 'readwrite');
|
|
const associationStore = transaction.objectStore(store);
|
|
const unassociationRequest = associationStore.delete(tabUrlKey);
|
|
|
|
transaction.oncomplete = () => {
|
|
//db.close();
|
|
this._reserveToExpireOldEntries();
|
|
tabUrl = undefined;
|
|
store = undefined;
|
|
};
|
|
|
|
unassociationRequest.onerror = event => {
|
|
console.error(`Failed to unassociate favIconUrl from tabUrl ${tabUrl} in the store ${store}`, event);
|
|
};
|
|
}
|
|
catch(error) {
|
|
console.error(`Failed to unassociate favIconUrl from tabUrl ${tabUrl} in the store ${store}`, error);
|
|
}
|
|
},
|
|
|
|
async _getAssociatedFavIconUrlFromTabUrl({ tabUrl, store } = {}) {
|
|
return new Promise(async (resolve, _reject) => {
|
|
const [db, tabUrlKey] = await Promise.all([
|
|
this._openDB(),
|
|
this._urlToKey(tabUrl),
|
|
]);
|
|
if (!db) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const transaction = db.transaction([store, this.STORE_FAVICONS], 'readonly');
|
|
const associationStore = transaction.objectStore(store);
|
|
const favIconStore = transaction.objectStore(this.STORE_FAVICONS);
|
|
|
|
const associationRequest = associationStore.get(tabUrlKey);
|
|
|
|
associationRequest.onsuccess = () => {
|
|
const association = associationRequest.result;
|
|
if (!association) {
|
|
//console.log(`No associated favIconUrl for the tabUrl ${tabUrl} in the store ${store}`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
const favIconRequest = favIconStore.get(association.favIconKey);
|
|
|
|
favIconRequest.onsuccess = () => {
|
|
let favIcon = favIconRequest.result;
|
|
if (!favIcon) {
|
|
//console.log(`FavIcon data not found for the tabUrl ${tabUrl} in the store ${store}`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
if (favIcon.timestamp < Date.now() - this.EXPIRATION_TIME_IN_MSEC) {
|
|
//console.log(`FavIcon data is expired for the tabUrl ${tabUrl} in the store ${store}`);
|
|
this._reserveToExpireOldEntries();
|
|
resolve(null);
|
|
return;
|
|
}
|
|
resolve(favIcon.url);
|
|
favIcon.url = undefined;
|
|
favIcon = undefined;
|
|
};
|
|
|
|
favIconRequest.onerror = event => {
|
|
console.error(`Failed to get favIconUrl from tabUrl ${tabUrl}`, event);
|
|
resolve(null);
|
|
};
|
|
};
|
|
|
|
associationRequest.onerror = event => {
|
|
console.error(`Failed to get favIcon association from tabUrl ${tabUrl}`, event);
|
|
resolve(null);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
//db.close();
|
|
tabUrl = undefined;
|
|
store = undefined;
|
|
};
|
|
}
|
|
catch(error) {
|
|
console.error('Failed to get from cache:', error);
|
|
resolve(null);
|
|
}
|
|
});
|
|
},
|
|
|
|
async _reserveToExpireOldEntries() {
|
|
if (this._reservedExpiration)
|
|
clearTimeout(this._reservedExpiration);
|
|
this._reservedExpiration = setTimeout(() => {
|
|
this._reservedExpiration = null;
|
|
this._expireOldEntries();
|
|
}, 500);
|
|
},
|
|
async _expireOldEntries() {
|
|
return new Promise(async (resolve, reject) => {
|
|
const db = await this._openDB();
|
|
if (!db) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const transaction = db.transaction([this.STORE_FAVICONS, this.STORE_EFFECTIVE_FAVICONS, this.STORE_UNEFFECTIVE_FAVICONS], 'readwrite');
|
|
const favIconsStore = transaction.objectStore(this.STORE_FAVICONS);
|
|
const effectiveFavIconsStore = transaction.objectStore(this.STORE_EFFECTIVE_FAVICONS);
|
|
const uneffectiveFavIconsStore = transaction.objectStore(this.STORE_UNEFFECTIVE_FAVICONS);
|
|
|
|
const favIconIndex = favIconsStore.index('timestamp');
|
|
const effectiveFavIconIndex = effectiveFavIconsStore.index('timestamp');
|
|
const uneffectiveFavIconIndex = uneffectiveFavIconsStore.index('timestamp');
|
|
|
|
const expirationTimestamp = Date.now() - this.EXPIRATION_TIME_IN_MSEC;
|
|
|
|
const favIconRequest = favIconIndex.openCursor(IDBKeyRange.upperBound(expirationTimestamp));
|
|
favIconRequest.onsuccess = (event) => {
|
|
const cursor = event.target.result;
|
|
if (!cursor)
|
|
return;
|
|
const key = cursor.primaryKey;
|
|
cursor.continue();
|
|
const deleteRequest = favIconsStore.delete(key);
|
|
deleteRequest.onerror = event => {
|
|
console.error(`Failed to clear favicon index`, event);
|
|
};
|
|
};
|
|
favIconRequest.onerror = event => {
|
|
console.error(`Failed to retrieve favicon index`, event);
|
|
};
|
|
|
|
const effectiveFavIconRequest = effectiveFavIconIndex.openCursor(IDBKeyRange.upperBound(expirationTimestamp));
|
|
effectiveFavIconRequest.onsuccess = (event) => {
|
|
const cursor = event.target.result;
|
|
if (!cursor)
|
|
return;
|
|
const url = cursor.primaryKey;
|
|
cursor.continue();
|
|
const deleteRequest = effectiveFavIconsStore.delete(url);
|
|
deleteRequest.onerror = event => {
|
|
console.error(`Failed to clear effective favicon index`, event);
|
|
};
|
|
};
|
|
effectiveFavIconRequest.onerror = event => {
|
|
console.error(`Failed to retrieve effective favicon index`, event);
|
|
};
|
|
|
|
const uneffectiveFavIconRequest = uneffectiveFavIconIndex.openCursor(IDBKeyRange.upperBound(expirationTimestamp));
|
|
uneffectiveFavIconRequest.onsuccess = (event) => {
|
|
const cursor = event.target.result;
|
|
if (!cursor)
|
|
return;
|
|
const url = cursor.primaryKey;
|
|
cursor.continue();
|
|
const deleteRequest = uneffectiveFavIconsStore.delete(url);
|
|
deleteRequest.onerror = event => {
|
|
console.error(`Failed to clear uneffective favicon index`, event);
|
|
};
|
|
};
|
|
uneffectiveFavIconRequest.onerror = event => {
|
|
console.error(`Failed to retrieve uneffective favicon index`, event);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
//db.close();
|
|
resolve();
|
|
};
|
|
}
|
|
catch(error) {
|
|
console.error('Failed to expire old entries:', error);
|
|
reject(error);
|
|
}
|
|
});
|
|
},
|
|
|
|
_tasks: [],
|
|
_processStep: 5,
|
|
FAVICON_SIZE: 16,
|
|
|
|
_init() {
|
|
this._onTabUpdated = this._onTabUpdated.bind(this);
|
|
browser.tabs.onUpdated.addListener(this._onTabUpdated);
|
|
|
|
this.canvas = document.createElement('canvas');
|
|
this.canvas.width = this.canvas.height = this.FAVICON_SIZE;
|
|
this.canvas.setAttribute('style', `
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
position: fixed
|
|
`);
|
|
document.body.appendChild(this.canvas);
|
|
|
|
window.addEventListener('unload', () => {
|
|
browser.tabs.onUpdated.removeListener(this._onTabUpdated);
|
|
}, { once: true });
|
|
},
|
|
|
|
_sessionAPIAvailable: (
|
|
browser.sessions &&
|
|
browser.sessions.getTabValue &&
|
|
browser.sessions.setTabValue &&
|
|
browser.sessions.removeTabValue
|
|
),
|
|
|
|
_addTask(task) {
|
|
this._tasks.push(task);
|
|
this._run();
|
|
},
|
|
|
|
_run() {
|
|
if (this._running)
|
|
return;
|
|
this._running = true;
|
|
const processOneTask = () => {
|
|
if (this._tasks.length == 0) {
|
|
this._running = false;
|
|
}
|
|
else {
|
|
const tasks = this._tasks.splice(0, this._processStep);
|
|
while (tasks.length > 0) {
|
|
tasks.shift()();
|
|
}
|
|
window.requestAnimationFrame(processOneTask);
|
|
}
|
|
};
|
|
processOneTask();
|
|
},
|
|
|
|
// public
|
|
loadToImage(params = {}) {
|
|
this._addTask(() => {
|
|
this._getEffectiveFavIconURL(params.tab, params.url)
|
|
.then(url => {
|
|
params.image.src = url;
|
|
params.image.classList.remove('error');
|
|
url = undefined;
|
|
},
|
|
_error => {
|
|
params.image.src = '';
|
|
params.image.classList.add('error');
|
|
});
|
|
});
|
|
},
|
|
|
|
// public
|
|
maybeImageTab(_tab) { // for backward compatibility
|
|
return false;
|
|
},
|
|
|
|
_getSafeFaviconUrl(url) {
|
|
switch (url) {
|
|
case 'chrome://browser/content/aboutlogins/icons/favicon.svg':
|
|
return this._getSVGDataURI(this.FAVICON_LOCKWISE);
|
|
case 'chrome://browser/content/robot.ico':
|
|
return this.FAVICON_ROBOT;
|
|
case 'chrome://browser/skin/controlcenter/dashboard.svg':
|
|
return this._getSVGDataURI(this.FAVICON_DASHBOARD);
|
|
case 'chrome://browser/skin/developer.svg':
|
|
return this._getSVGDataURI(this.FAVICON_DEVELOPER);
|
|
case 'chrome://browser/skin/privatebrowsing/favicon.svg':
|
|
return this._getSVGDataURI(this.FAVICON_PRIVATE_BROWSING);
|
|
case 'chrome://browser/skin/settings.svg':
|
|
return this._getSVGDataURI(this.FAVICON_SETTINGS);
|
|
case 'chrome://browser/skin/window.svg':
|
|
return this._getSVGDataURI(this.FAVICON_WINDOW);
|
|
case 'chrome://devtools/skin/images/profiler-stopwatch.svg':
|
|
return this._getSVGDataURI(this.FAVICON_PROFILER);
|
|
case 'chrome://global/skin/icons/performance.svg':
|
|
return this._getSVGDataURI(this.FAVICON_PERFORMANCE);
|
|
case 'chrome://global/skin/icons/warning.svg':
|
|
return this._getSVGDataURI(this.FAVICON_WARNING);
|
|
case 'chrome://mozapps/skin/extensions/extensionGeneric-16.svg':
|
|
return this._getSVGDataURI(this.FAVICON_EXTENSION);
|
|
default:
|
|
if (/^chrome:\/\//.test(url) &&
|
|
!/^chrome:\/\/branding\//.test(url))
|
|
return this._getSVGDataURI(this.FAVICON_GLOBE);
|
|
break;
|
|
}
|
|
return url;
|
|
},
|
|
_getSVGDataURI(svg) {
|
|
return `data:image/svg+xml,${encodeURIComponent(svg.trim())}`;
|
|
},
|
|
|
|
// public
|
|
async getLastEffectiveFavIconURL(tab) {
|
|
if (tab.favIconUrl?.startsWith('data:'))
|
|
return tab.favIconUrl;
|
|
|
|
const uneffectiveFavIconUrl = await this._getAssociatedFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_UNEFFECTIVE_FAVICONS });
|
|
if (uneffectiveFavIconUrl)
|
|
return null;
|
|
|
|
const favIconUrl = await this._getAssociatedFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
if (favIconUrl)
|
|
return favIconUrl;
|
|
|
|
if (!this._sessionAPIAvailable)
|
|
return null;
|
|
|
|
const lastData = await browser.sessions.getTabValue(tab.id, this.LAST_EFFECTIVE_FAVICON);
|
|
return lastData && lastData.url == tab.url && lastData.favIconUrl;
|
|
},
|
|
|
|
async _getEffectiveFavIconURL(tab, favIconUrl = null) {
|
|
if (tab.favIconUrl?.startsWith('data:')) {
|
|
browser.sessions.removeTabValue(tab.id, this.LAST_EFFECTIVE_FAVICON);
|
|
this._unassociateFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_UNEFFECTIVE_FAVICONS });
|
|
return tab.favIconUrl;
|
|
}
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
favIconUrl = this._getSafeFaviconUrl(favIconUrl || tab.favIconUrl);
|
|
let storedFavIconUrl;
|
|
if (!favIconUrl && tab.discarded) {
|
|
// discarded tab doesn't have favIconUrl, so we should use cached data.
|
|
storedFavIconUrl = favIconUrl = await this.getLastEffectiveFavIconURL(tab);
|
|
}
|
|
|
|
let loader, onLoad, onError;
|
|
const clear = (() => {
|
|
if (loader) {
|
|
loader.removeEventListener('load', onLoad, { once: true });
|
|
loader.removeEventListener('error', onError, { once: true });
|
|
}
|
|
loader = onLoad = onError = favIconUrl = storedFavIconUrl = undefined;
|
|
});
|
|
|
|
onLoad = async foundFavIconUrl => {
|
|
let dataURL = null;
|
|
if (this.DRAWABLE_FAVICON_PATTERN.test(favIconUrl)) {
|
|
const context = this.canvas.getContext('2d');
|
|
context.clearRect(0, 0, this.FAVICON_SIZE, this.FAVICON_SIZE);
|
|
context.drawImage(loader, 0, 0, this.FAVICON_SIZE, this.FAVICON_SIZE);
|
|
try {
|
|
dataURL = this.canvas.toDataURL('image/png');
|
|
}
|
|
catch(_error) {
|
|
// it can fail due to security reasons
|
|
}
|
|
}
|
|
const oldFavIconUrl = foundFavIconUrl || await this._getAssociatedFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
if (!oldFavIconUrl ||
|
|
oldFavIconUrl != favIconUrl) {
|
|
if (this._sessionAPIAvailable)
|
|
browser.sessions.setTabValue(tab.id, this.LAST_EFFECTIVE_FAVICON, {
|
|
url: tab.url,
|
|
favIconUrl,
|
|
});
|
|
}
|
|
this._associateFavIconUrlToTabUrl({ tabUrl: tab.url, favIconUrl, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
this._unassociateFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_UNEFFECTIVE_FAVICONS });
|
|
resolve(dataURL || favIconUrl);
|
|
clear();
|
|
};
|
|
onError = async error => {
|
|
this._unassociateFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
this._associateFavIconUrlToTabUrl({ tabUrl: tab.url, favIconUrl, store: this.STORE_UNEFFECTIVE_FAVICONS });
|
|
if (this._sessionAPIAvailable)
|
|
browser.sessions.removeTabValue(tab.id, this.LAST_EFFECTIVE_FAVICON);
|
|
clear();
|
|
reject(error || new Error('No effective icon'));
|
|
};
|
|
storedFavIconUrl = storedFavIconUrl || await this._getAssociatedFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
if (storedFavIconUrl)
|
|
return onLoad(storedFavIconUrl);
|
|
if (!favIconUrl ||
|
|
!this.VALID_FAVICON_PATTERN.test(favIconUrl)) {
|
|
onError();
|
|
return;
|
|
}
|
|
loader = new Image();
|
|
if (/^https?:/.test(favIconUrl))
|
|
loader.crossOrigin = 'anonymous';
|
|
loader.addEventListener('load', () => onLoad(), { once: true });
|
|
loader.addEventListener('error', onError, { once: true });
|
|
try {
|
|
loader.src = favIconUrl;
|
|
}
|
|
catch(error) {
|
|
this._unassociateFavIconUrlFromTabUrl({ tabUrl: tab.url, store: this.STORE_EFFECTIVE_FAVICONS });
|
|
this._associateFavIconUrlToTabUrl({ tabUrl: tab.url, favIconUrl, store: this.STORE_UNEFFECTIVE_FAVICONS });
|
|
onError(error);
|
|
}
|
|
});
|
|
},
|
|
|
|
_onTabUpdated(tabId, changeInfo, _tab) {
|
|
if (!this._hasFavIconInfo(changeInfo))
|
|
return;
|
|
let timer = this._updatingTabs.get(tabId);
|
|
if (timer)
|
|
clearTimeout(timer);
|
|
// Updating of last effective favicon must be done after the loading
|
|
// of the tab itself is correctly done, to avoid cookie problems on
|
|
// some websites.
|
|
// See also: https://github.com/piroor/treestyletab/issues/2064
|
|
timer = setTimeout(async () => {
|
|
this._updatingTabs.delete(tabId);
|
|
const tab = await browser.tabs.get(tabId);
|
|
if (!tab ||
|
|
(changeInfo.favIconUrl &&
|
|
tab.favIconUrl != changeInfo.favIconUrl) ||
|
|
(changeInfo.url &&
|
|
tab.url != changeInfo.url) ||
|
|
!this._hasFavIconInfo(tab))
|
|
return; // expired
|
|
|
|
await this._getEffectiveFavIconURL(
|
|
tab,
|
|
changeInfo.favIconUrl
|
|
).catch(_error => {});
|
|
}, 5000);
|
|
this._updatingTabs.set(tabId, timer);
|
|
},
|
|
_hasFavIconInfo(tabOrChangeInfo) {
|
|
return 'favIconUrl' in tabOrChangeInfo;
|
|
},
|
|
_updatingTabs: new Map(),
|
|
};
|
|
TabFavIconHelper._init(); // eslint-disable-line no-underscore-dangle
|
|
export default TabFavIconHelper;
|