Merge pull request #367 from toasted-nutbread/defer-content-script-css-injection

Defer content script css injection
This commit is contained in:
toasted-nutbread 2020-02-16 22:01:03 -05:00 committed by GitHub
commit 2ace8d4ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 70 deletions

View File

@ -499,19 +499,30 @@ class Backend {
return Promise.resolve({frameId}); return Promise.resolve({frameId});
} }
_onApiInjectStylesheet({css}, sender) { _onApiInjectStylesheet({type, value}, sender) {
if (!sender.tab) { if (!sender.tab) {
return Promise.reject(new Error('Invalid tab')); return Promise.reject(new Error('Invalid tab'));
} }
const tabId = sender.tab.id; const tabId = sender.tab.id;
const frameId = sender.frameId; const frameId = sender.frameId;
const details = { const details = (
code: css, type === 'file' ?
{
file: value,
runAt: 'document_start',
cssOrigin: 'author',
allFrames: false,
matchAboutBlank: true
} :
{
code: value,
runAt: 'document_start', runAt: 'document_start',
cssOrigin: 'user', cssOrigin: 'user',
allFrames: false allFrames: false,
}; matchAboutBlank: true
}
);
if (typeof frameId === 'number') { if (typeof frameId === 'number') {
details.frameId = frameId; details.frameId = frameId;
} }

View File

@ -22,7 +22,8 @@ class SettingsPopupPreview {
constructor() { constructor() {
this.frontend = null; this.frontend = null;
this.apiOptionsGetOld = apiOptionsGet; this.apiOptionsGetOld = apiOptionsGet;
this.popupInjectOuterStylesheetOld = Popup.injectOuterStylesheet; this.popup = null;
this.popupSetCustomOuterCssOld = null;
this.popupShown = false; this.popupShown = false;
this.themeChangeTimeout = null; this.themeChangeTimeout = null;
this.textSource = null; this.textSource = null;
@ -50,19 +51,19 @@ class SettingsPopupPreview {
const popupHost = new PopupProxyHost(); const popupHost = new PopupProxyHost();
await popupHost.prepare(); await popupHost.prepare();
const popup = popupHost.getOrCreatePopup(); this.popup = popupHost.getOrCreatePopup();
popup.setChildrenSupported(false); 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.setEnabled = function () {};
this.frontend.searchClear = function () {}; this.frontend.searchClear = function () {};
await this.frontend.prepare(); await this.frontend.prepare();
// Overwrite popup
Popup.injectOuterStylesheet = (...args) => this.popupInjectOuterStylesheet(...args);
// Update search // Update search
this.updateSearch(); this.updateSearch();
} }
@ -83,14 +84,13 @@ class SettingsPopupPreview {
return options; return options;
} }
popupInjectOuterStylesheet(...args) { async popupSetCustomOuterCss(...args) {
// This simulates the stylesheet priorities when injecting using the web extension API. // 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'); const node = document.querySelector('#client-css');
if (node !== null && outerStylesheet !== null) { if (node !== null && result !== null) {
node.parentNode.insertBefore(outerStylesheet, node); node.parentNode.insertBefore(result, node);
} }
return result; return result;
@ -137,7 +137,7 @@ class SettingsPopupPreview {
setCustomOuterCss(css) { setCustomOuterCss(css) {
if (this.frontend === null) { return; } if (this.frontend === null) { return; }
this.frontend.popup.setCustomOuterCss(css, true); this.frontend.popup.setCustomOuterCss(css, false);
} }
async updateSearch() { async updateSearch() {

View File

@ -31,7 +31,6 @@ class Popup {
this._visible = false; this._visible = false;
this._visibleOverride = null; this._visibleOverride = null;
this._options = null; this._options = null;
this._stylesheetInjectedViaApi = false;
this._contentScale = 1.0; this._contentScale = 1.0;
this._containerSizeContentScale = null; this._containerSizeContentScale = null;
@ -158,21 +157,13 @@ class Popup {
this._container.dataset.yomichanSiteColor = this._getSiteColor(); 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. return await Popup._injectStylesheet(
if (this._stylesheetInjectedViaApi) { return; } 'yomichan-popup-outer-user-stylesheet',
'code',
if (injectDirectly || Popup._isOnExtensionPage()) { css,
Popup.injectOuterStylesheet(css); useWebExtensionApi
} else { );
if (!css) { return; }
try {
await apiInjectStylesheet(css);
this._stylesheetInjectedViaApi = true;
} catch (e) {
// NOP
}
}
} }
setChildrenSupported(value) { setChildrenSupported(value) {
@ -187,26 +178,6 @@ class Popup {
return this._container.getBoundingClientRect(); 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 // Private functions
_inject() { _inject() {
@ -248,10 +219,24 @@ class Popup {
}); });
this._observeFullscreen(true); this._observeFullscreen(true);
this._onFullscreenChanged(); 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) { _observeFullscreen(observe) {
if (!observe) { if (!observe) {
this._fullscreenEventListeners.removeAllEventListeners(); 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) { static _getViewport(useVisualViewport) {
const visualViewport = window.visualViewport; const visualViewport = window.visualViewport;
if (visualViewport !== null && typeof visualViewport === 'object') { if (visualViewport !== null && typeof visualViewport === 'object') {
@ -567,6 +543,80 @@ class Popup {
bottom: window.innerHeight 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();

View File

@ -30,7 +30,6 @@
"fg/js/frontend.js", "fg/js/frontend.js",
"fg/js/frontend-initialize.js" "fg/js/frontend-initialize.js"
], ],
"css": ["fg/css/client.css"],
"match_about_blank": true, "match_about_blank": true,
"all_frames": true "all_frames": true
}], }],

View File

@ -89,8 +89,8 @@ function apiFrameInformationGet() {
return _apiInvoke('frameInformationGet'); return _apiInvoke('frameInformationGet');
} }
function apiInjectStylesheet(css) { function apiInjectStylesheet(type, value) {
return _apiInvoke('injectStylesheet', {css}); return _apiInvoke('injectStylesheet', {type, value});
} }
function apiGetEnvironmentInfo() { function apiGetEnvironmentInfo() {