From 2c3f510010ca2910b8c227a9888e2c3584840402 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:13:04 -0500 Subject: [PATCH 1/7] Allow apiInjectStylesheet to inject a URL --- ext/bg/js/backend.js | 23 ++++++++++++++++------- ext/fg/js/popup.js | 2 +- ext/mixed/js/api.js | 4 ++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d1a34f82..eeed841c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -499,19 +499,28 @@ 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 + } : + { + code: value, + runAt: 'document_start', + cssOrigin: 'user', + allFrames: false + } + ); if (typeof frameId === 'number') { details.frameId = frameId; } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 45203c03..970c5343 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -167,7 +167,7 @@ class Popup { } else { if (!css) { return; } try { - await apiInjectStylesheet(css); + await apiInjectStylesheet('code', css); this._stylesheetInjectedViaApi = true; } catch (e) { // NOP 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() { From b6a50e234cf4fc6179725082f9f53ddd7325ba01 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:29:55 -0500 Subject: [PATCH 2/7] Change parameter name --- ext/bg/js/settings/popup-preview-frame.js | 2 +- ext/fg/js/popup.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 8fd06222..a7863b3a 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -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 970c5343..6fbab62b 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -158,11 +158,11 @@ class Popup { this._container.dataset.yomichanSiteColor = this._getSiteColor(); } - async setCustomOuterCss(css, injectDirectly) { + async setCustomOuterCss(css, useWebExtensionApi) { // Cannot repeatedly inject stylesheets using web extension APIs since there is no way to remove them. if (this._stylesheetInjectedViaApi) { return; } - if (injectDirectly || Popup._isOnExtensionPage()) { + if (!useWebExtensionApi || Popup._isOnExtensionPage()) { Popup.injectOuterStylesheet(css); } else { if (!css) { return; } From d3aefdc4e3f78b87b99bff576137f530ac7ac163 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:37:03 -0500 Subject: [PATCH 3/7] Override setCustomOuterCss instead of Popup.injectOuterStylesheet --- ext/bg/js/settings/popup-preview-frame.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index a7863b3a..8c354bf7 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,9 +84,9 @@ 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'); From 9fd6ee382d35fb5fcfc3e6d0f4fab711b37b693e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:48:06 -0500 Subject: [PATCH 4/7] Create more generic function for injecting stylesheets --- ext/bg/js/settings/popup-preview-frame.js | 5 +- ext/fg/js/popup.js | 132 ++++++++++++++-------- 2 files changed, 88 insertions(+), 49 deletions(-) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 8c354bf7..e900d4e2 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -88,10 +88,9 @@ class SettingsPopupPreview { // This simulates the stylesheet priorities when injecting using the web extension API. 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; diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 6fbab62b..d799e371 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; @@ -159,20 +158,12 @@ class Popup { } async setCustomOuterCss(css, useWebExtensionApi) { - // Cannot repeatedly inject stylesheets using web extension APIs since there is no way to remove them. - if (this._stylesheetInjectedViaApi) { return; } - - if (!useWebExtensionApi || Popup._isOnExtensionPage()) { - Popup.injectOuterStylesheet(css); - } else { - if (!css) { return; } - try { - await apiInjectStylesheet('code', css); - this._stylesheetInjectedViaApi = true; - } catch (e) { - // NOP - } - } + 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,7 +219,11 @@ class Popup { }); this._observeFullscreen(true); this._onFullscreenChanged(); - this.setCustomOuterCss(this._options.general.customPopupOuterCss, false); + try { + this.setCustomOuterCss(this._options.general.customPopupOuterCss, true); + } catch (e) { + // NOP + } }); } @@ -526,15 +501,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 +533,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(); From 3d27e80ae646bcc9c421cbc8aa281008f47c8992 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:53:19 -0500 Subject: [PATCH 5/7] Delay CSS injection until a popup is created --- ext/fg/js/popup.js | 1 + ext/manifest.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index d799e371..dc538b02 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -220,6 +220,7 @@ class Popup { this._observeFullscreen(true); this._onFullscreenChanged(); try { + Popup._injectStylesheet('yomichan-popup-outer-stylesheet', 'file', '/fg/css/client.css', true); this.setCustomOuterCss(this._options.general.customPopupOuterCss, true); } catch (e) { // NOP 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 }], From e173a71ba631533a2a0a64ffe4d8883d7300802e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 14:34:49 -0500 Subject: [PATCH 6/7] Fix CSS injection on about:blank pages --- ext/bg/js/backend.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index eeed841c..458ea483 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -512,13 +512,15 @@ class Backend { file: value, runAt: 'document_start', cssOrigin: 'author', - allFrames: false + allFrames: false, + matchAboutBlank: true } : { code: value, runAt: 'document_start', cssOrigin: 'user', - allFrames: false + allFrames: false, + matchAboutBlank: true } ); if (typeof frameId === 'number') { From ae4ee9ddee0b791c1039595250db6106e66709fa Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 21:49:28 -0500 Subject: [PATCH 7/7] Fix error handling on style injection --- ext/fg/js/popup.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index dc538b02..59c46ab8 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -219,15 +219,24 @@ class Popup { }); this._observeFullscreen(true); this._onFullscreenChanged(); - try { - Popup._injectStylesheet('yomichan-popup-outer-stylesheet', 'file', '/fg/css/client.css', true); - this.setCustomOuterCss(this._options.general.customPopupOuterCss, true); - } catch (e) { - // NOP - } + 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();