diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d1a34f82..458ea483 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -499,19 +499,30 @@ class Backend { return Promise.resolve({frameId}); } - _onApiInjectStylesheet({css}, sender) { + _onApiInjectStylesheet({type, value}, sender) { if (!sender.tab) { return Promise.reject(new Error('Invalid tab')); } const tabId = sender.tab.id; const frameId = sender.frameId; - const details = { - code: css, - runAt: 'document_start', - cssOrigin: 'user', - allFrames: false - }; + const details = ( + type === 'file' ? + { + file: value, + runAt: 'document_start', + cssOrigin: 'author', + allFrames: false, + matchAboutBlank: true + } : + { + code: value, + runAt: 'document_start', + cssOrigin: 'user', + allFrames: false, + matchAboutBlank: true + } + ); if (typeof frameId === 'number') { details.frameId = frameId; } diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 8fd06222..e900d4e2 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -22,7 +22,8 @@ class SettingsPopupPreview { constructor() { this.frontend = null; this.apiOptionsGetOld = apiOptionsGet; - this.popupInjectOuterStylesheetOld = Popup.injectOuterStylesheet; + this.popup = null; + this.popupSetCustomOuterCssOld = null; this.popupShown = false; this.themeChangeTimeout = null; this.textSource = null; @@ -50,19 +51,19 @@ class SettingsPopupPreview { const popupHost = new PopupProxyHost(); await popupHost.prepare(); - const popup = popupHost.getOrCreatePopup(); - popup.setChildrenSupported(false); + this.popup = popupHost.getOrCreatePopup(); + this.popup.setChildrenSupported(false); - this.frontend = new Frontend(popup); + this.popupSetCustomOuterCssOld = this.popup.setCustomOuterCss; + this.popup.setCustomOuterCss = (...args) => this.popupSetCustomOuterCss(...args); + + this.frontend = new Frontend(this.popup); this.frontend.setEnabled = function () {}; this.frontend.searchClear = function () {}; await this.frontend.prepare(); - // Overwrite popup - Popup.injectOuterStylesheet = (...args) => this.popupInjectOuterStylesheet(...args); - // Update search this.updateSearch(); } @@ -83,14 +84,13 @@ class SettingsPopupPreview { return options; } - popupInjectOuterStylesheet(...args) { + async popupSetCustomOuterCss(...args) { // This simulates the stylesheet priorities when injecting using the web extension API. - const result = this.popupInjectOuterStylesheetOld(...args); + const result = await this.popupSetCustomOuterCssOld.call(this.popup, ...args); - const outerStylesheet = Popup.outerStylesheet; const node = document.querySelector('#client-css'); - if (node !== null && outerStylesheet !== null) { - node.parentNode.insertBefore(outerStylesheet, node); + if (node !== null && result !== null) { + node.parentNode.insertBefore(result, node); } return result; @@ -137,7 +137,7 @@ class SettingsPopupPreview { setCustomOuterCss(css) { if (this.frontend === null) { return; } - this.frontend.popup.setCustomOuterCss(css, true); + this.frontend.popup.setCustomOuterCss(css, false); } async updateSearch() { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 45203c03..59c46ab8 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -31,7 +31,6 @@ class Popup { this._visible = false; this._visibleOverride = null; this._options = null; - this._stylesheetInjectedViaApi = false; this._contentScale = 1.0; this._containerSizeContentScale = null; @@ -158,21 +157,13 @@ class Popup { this._container.dataset.yomichanSiteColor = this._getSiteColor(); } - async setCustomOuterCss(css, injectDirectly) { - // Cannot repeatedly inject stylesheets using web extension APIs since there is no way to remove them. - if (this._stylesheetInjectedViaApi) { return; } - - if (injectDirectly || Popup._isOnExtensionPage()) { - Popup.injectOuterStylesheet(css); - } else { - if (!css) { return; } - try { - await apiInjectStylesheet(css); - this._stylesheetInjectedViaApi = true; - } catch (e) { - // NOP - } - } + async setCustomOuterCss(css, useWebExtensionApi) { + return await Popup._injectStylesheet( + 'yomichan-popup-outer-user-stylesheet', + 'code', + css, + useWebExtensionApi + ); } setChildrenSupported(value) { @@ -187,26 +178,6 @@ class Popup { return this._container.getBoundingClientRect(); } - static injectOuterStylesheet(css) { - if (Popup.outerStylesheet === null) { - if (!css) { return; } - Popup.outerStylesheet = document.createElement('style'); - Popup.outerStylesheet.id = 'yomichan-popup-outer-stylesheet'; - } - - const outerStylesheet = Popup.outerStylesheet; - if (css) { - outerStylesheet.textContent = css; - - const par = document.head; - if (par && outerStylesheet.parentNode !== par) { - par.appendChild(outerStylesheet); - } - } else { - outerStylesheet.textContent = ''; - } - } - // Private functions _inject() { @@ -248,10 +219,24 @@ class Popup { }); this._observeFullscreen(true); this._onFullscreenChanged(); - this.setCustomOuterCss(this._options.general.customPopupOuterCss, false); + this._injectStyles(); }); } + async _injectStyles() { + try { + await Popup._injectStylesheet('yomichan-popup-outer-stylesheet', 'file', '/fg/css/client.css', true); + } catch (e) { + // NOP + } + + try { + await this.setCustomOuterCss(this._options.general.customPopupOuterCss, true); + } catch (e) { + // NOP + } + } + _observeFullscreen(observe) { if (!observe) { this._fullscreenEventListeners.removeAllEventListeners(); @@ -526,15 +511,6 @@ class Popup { ]; } - static _isOnExtensionPage() { - try { - const url = chrome.runtime.getURL('/'); - return window.location.href.substring(0, url.length) === url; - } catch (e) { - // NOP - } - } - static _getViewport(useVisualViewport) { const visualViewport = window.visualViewport; if (visualViewport !== null && typeof visualViewport === 'object') { @@ -567,6 +543,80 @@ class Popup { bottom: window.innerHeight }; } + + static _isOnExtensionPage() { + try { + const url = chrome.runtime.getURL('/'); + return window.location.href.substring(0, url.length) === url; + } catch (e) { + // NOP + } + } + + static async _injectStylesheet(id, type, value, useWebExtensionApi) { + const injectedStylesheets = Popup._injectedStylesheets; + + if (Popup._isOnExtensionPage()) { + // Permissions error will occur if trying to use the WebExtension API to inject + // into an extension page. + useWebExtensionApi = false; + } + + let styleNode = injectedStylesheets.get(id); + if (typeof styleNode !== 'undefined') { + if (styleNode === null) { + // Previously injected via WebExtension API + throw new Error(`Stylesheet with id ${id} has already been injected using the WebExtension API`); + } + } else { + styleNode = null; + } + + if (useWebExtensionApi) { + // Inject via WebExtension API + if (styleNode !== null && styleNode.parentNode !== null) { + styleNode.parentNode.removeChild(styleNode); + } + + await apiInjectStylesheet(type, value); + + injectedStylesheets.set(id, null); + return null; + } + + // Create node in document + const parentNode = document.head; + if (parentNode === null) { + throw new Error('No parent node'); + } + + // Create or reuse node + const isFile = (type === 'file'); + const tagName = isFile ? 'link' : 'style'; + if (styleNode === null || styleNode.nodeName.toLowerCase() !== tagName) { + if (styleNode !== null && styleNode.parentNode !== null) { + styleNode.parentNode.removeChild(styleNode); + } + styleNode = document.createElement(tagName); + styleNode.id = id; + } + + // Update node style + if (isFile) { + styleNode.rel = value; + } else { + styleNode.textContent = value; + } + + // Update parent + if (styleNode.parentNode !== parentNode) { + parentNode.appendChild(styleNode); + } + + // Add to map + injectedStylesheets.set(id, styleNode); + return styleNode; + } } -Popup.outerStylesheet = null; +Popup._injectedStylesheets = new Map(); diff --git a/ext/manifest.json b/ext/manifest.json index 68a8adb4..b86459f9 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -30,7 +30,6 @@ "fg/js/frontend.js", "fg/js/frontend-initialize.js" ], - "css": ["fg/css/client.css"], "match_about_blank": true, "all_frames": true }], diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 86bdc73c..14900ecf 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -89,8 +89,8 @@ function apiFrameInformationGet() { return _apiInvoke('frameInformationGet'); } -function apiInjectStylesheet(css) { - return _apiInvoke('injectStylesheet', {css}); +function apiInjectStylesheet(type, value) { + return _apiInvoke('injectStylesheet', {type, value}); } function apiGetEnvironmentInfo() {