use adoptedStyleSheets
This commit is contained in:
parent
60fc6f2456
commit
124dce44e8
|
@ -943,6 +943,9 @@
|
||||||
"optionsAdvanced": {
|
"optionsAdvanced": {
|
||||||
"message": "Advanced"
|
"message": "Advanced"
|
||||||
},
|
},
|
||||||
|
"optionsAdvancedAdoptedStyleSheets": {
|
||||||
|
"message": "Apply styles directly via <a href='https://developers.google.com/web/updates/2019/02/constructable-stylesheets'>AdoptedStyleSheets</a>"
|
||||||
|
},
|
||||||
"optionsAdvancedContextDelete": {
|
"optionsAdvancedContextDelete": {
|
||||||
"message": "Add 'Delete' in editor context menu"
|
"message": "Add 'Delete' in editor context menu"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint no-eq-null: 0, eqeqeq: [2, "smart"] */
|
/* eslint no-eq-null: 0, eqeqeq: [2, "smart"] */
|
||||||
/* global createCache db calcStyleDigest db tryRegExp styleCodeEmpty styleSectionGlobal
|
/* global createCache db calcStyleDigest db tryRegExp styleCodeEmpty styleSectionGlobal
|
||||||
getStyleWithNoCode msg sync uuidv4 URLS */
|
getStyleWithNoCode msg sync uuidv4 URLS prefs */
|
||||||
/* exported styleManager */
|
/* exported styleManager */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -332,8 +332,10 @@ const styleManager = (() => {
|
||||||
function ensurePrepared(methods) {
|
function ensurePrepared(methods) {
|
||||||
const prepared = {};
|
const prepared = {};
|
||||||
for (const [name, fn] of Object.entries(methods)) {
|
for (const [name, fn] of Object.entries(methods)) {
|
||||||
prepared[name] = (...args) =>
|
prepared[name] = async function () {
|
||||||
preparing.then(() => fn(...args));
|
await preparing;
|
||||||
|
return fn.apply(this, arguments);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return prepared;
|
return prepared;
|
||||||
}
|
}
|
||||||
|
@ -475,7 +477,7 @@ const styleManager = (() => {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSectionsByUrl(url, id) {
|
function getSectionsByUrl(url, id, checkASS) {
|
||||||
let cache = cachedStyleForUrl.get(url);
|
let cache = cachedStyleForUrl.get(url);
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
cache = {
|
cache = {
|
||||||
|
@ -491,13 +493,12 @@ const styleManager = (() => {
|
||||||
.map(i => styles.get(i))
|
.map(i => styles.get(i))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (id) {
|
const sections = id
|
||||||
if (cache.sections[id]) {
|
? cache.sections[id] && {[id]: cache.sections[id]} || {}
|
||||||
return {[id]: cache.sections[id]};
|
: cache.sections;
|
||||||
}
|
return checkASS
|
||||||
return {};
|
? Object.assign({ASS: prefs.get('adoptedStyleSheets')}, sections)
|
||||||
}
|
: sections;
|
||||||
return cache.sections;
|
|
||||||
|
|
||||||
function buildCache(styleList) {
|
function buildCache(styleList) {
|
||||||
const query = createMatchQuery(url);
|
const query = createMatchQuery(url);
|
||||||
|
|
|
@ -59,7 +59,7 @@ self.INJECTED !== 1 && (() => {
|
||||||
function init() {
|
function init() {
|
||||||
return STYLE_VIA_API ?
|
return STYLE_VIA_API ?
|
||||||
API.styleViaAPI({method: 'styleApply'}) :
|
API.styleViaAPI({method: 'styleApply'}) :
|
||||||
API.getSectionsByUrl(getMatchUrl()).then(styleInjector.apply);
|
API.getSectionsByUrl(getMatchUrl(), null, true).then(styleInjector.apply);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMatchUrl() {
|
function getMatchUrl() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* global prefs */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
|
@ -16,6 +17,7 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
const table = new Map();
|
const table = new Map();
|
||||||
let isEnabled = true;
|
let isEnabled = true;
|
||||||
let isTransitionPatched;
|
let isTransitionPatched;
|
||||||
|
let ASS;
|
||||||
// will store the original method refs because the page can override them
|
// will store the original method refs because the page can override them
|
||||||
let creationDoc, createElement, createElementNS;
|
let creationDoc, createElement, createElementNS;
|
||||||
|
|
||||||
|
@ -24,6 +26,7 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
list,
|
list,
|
||||||
|
|
||||||
apply(styleMap) {
|
apply(styleMap) {
|
||||||
|
if ('ASS' in styleMap) _init(styleMap);
|
||||||
const styles = _styleMapToArray(styleMap);
|
const styles = _styleMapToArray(styleMap);
|
||||||
return (
|
return (
|
||||||
!styles.length ?
|
!styles.length ?
|
||||||
|
@ -45,6 +48,7 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
},
|
},
|
||||||
|
|
||||||
clearOrphans() {
|
clearOrphans() {
|
||||||
|
if (ASS) ASS.list = ASS.wipedList;
|
||||||
for (const el of document.querySelectorAll(`style[id^="${PREFIX}"].stylus`)) {
|
for (const el of document.querySelectorAll(`style[id^="${PREFIX}"].stylus`)) {
|
||||||
const id = el.id.slice(PREFIX.length);
|
const id = el.id.slice(PREFIX.length);
|
||||||
if (/^\d+$/.test(id) || id === PATCH_ID) {
|
if (/^\d+$/.test(id) || id === PATCH_ID) {
|
||||||
|
@ -82,24 +86,28 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
};
|
};
|
||||||
|
|
||||||
function _add(style) {
|
function _add(style) {
|
||||||
const el = style.el = _createStyle(style.id, style.code);
|
_domCreate(style);
|
||||||
const i = list.findIndex(item => compare(item, style) > 0);
|
const i = list.findIndex(item => compare(item, style) > 0);
|
||||||
table.set(style.id, style);
|
table.set(style.id, style);
|
||||||
if (isEnabled) {
|
if (isEnabled) _domInsert(style, (list[i] || {}).el);
|
||||||
document.documentElement.insertBefore(el, i < 0 ? null : list[i].el);
|
|
||||||
}
|
|
||||||
list.splice(i < 0 ? list.length : i, 0, style);
|
list.splice(i < 0 ? list.length : i, 0, style);
|
||||||
return el;
|
return style.el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _addRemoveElements(add) {
|
function _addRemoveElements(add) {
|
||||||
for (const {el} of list) {
|
const asses = [];
|
||||||
if (add) {
|
for (const style of list) {
|
||||||
document.documentElement.appendChild(el);
|
if (style.ass) {
|
||||||
|
asses.push(style.el);
|
||||||
|
} else if (add) {
|
||||||
|
_domInsert(style);
|
||||||
} else {
|
} else {
|
||||||
el.remove();
|
_domDelete(style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ASS) {
|
||||||
|
ASS.list = ASS.wipedList.concat(add ? asses : []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _addUpdate(style) {
|
function _addUpdate(style) {
|
||||||
|
@ -115,20 +123,35 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
!styles.some(s => s.code.includes('transition'))) {
|
!styles.some(s => s.code.includes('transition'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const el = _createStyle(PATCH_ID, `
|
const style = {
|
||||||
:root:not(#\\0):not(#\\0) * {
|
id: PATCH_ID,
|
||||||
transition: none !important;
|
code: `
|
||||||
}
|
:root:not(#\\0):not(#\\0) * {
|
||||||
`);
|
transition: none !important;
|
||||||
document.documentElement.appendChild(el);
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
_domCreate(style);
|
||||||
|
_domInsert(style);
|
||||||
// wait for the next paint to complete
|
// wait for the next paint to complete
|
||||||
// note: requestAnimationFrame won't fire in inactive tabs
|
// note: requestAnimationFrame won't fire in inactive tabs
|
||||||
requestAnimationFrame(() => setTimeout(() => el.remove()));
|
requestAnimationFrame(() => setTimeout(_domDelete, 0, style));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _createStyle(id, code = '') {
|
/**
|
||||||
if (!creationDoc) _initCreationDoc();
|
* @returns {Element|CSSStyleSheet}
|
||||||
|
* @mutates style
|
||||||
|
*/
|
||||||
|
function _domCreate(style = {}) {
|
||||||
|
const {id, code = ''} = style;
|
||||||
let el;
|
let el;
|
||||||
|
if (ASS) {
|
||||||
|
el = style.el = new CSSStyleSheet(id ? {media: PREFIX + id} : {});
|
||||||
|
if (_setASSCode(style, code)) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!creationDoc) _initCreationDoc();
|
||||||
if (document.documentElement instanceof SVGSVGElement) {
|
if (document.documentElement instanceof SVGSVGElement) {
|
||||||
// SVG document style
|
// SVG document style
|
||||||
el = createElementNS.call(creationDoc, 'http://www.w3.org/2000/svg', 'style');
|
el = createElementNS.call(creationDoc, 'http://www.w3.org/2000/svg', 'style');
|
||||||
|
@ -148,9 +171,45 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
// SVG className is not a string, but an instance of SVGAnimatedString
|
// SVG className is not a string, but an instance of SVGAnimatedString
|
||||||
el.classList.add('stylus');
|
el.classList.add('stylus');
|
||||||
el.textContent = code;
|
el.textContent = code;
|
||||||
|
style.el = el;
|
||||||
|
style.ass = false;
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _domInsert({ass, el}, beforeEl) {
|
||||||
|
if (ass) {
|
||||||
|
const list = ASS.omit({el});
|
||||||
|
const i = list.indexOf(beforeEl);
|
||||||
|
list.splice(i >= 0 ? i : list.length, 0, el);
|
||||||
|
ASS.list = list;
|
||||||
|
} else {
|
||||||
|
document.documentElement.insertBefore(el, beforeEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _domDelete({ass, el}) {
|
||||||
|
if (ass) {
|
||||||
|
ASS.omit({el, commit: true});
|
||||||
|
} else {
|
||||||
|
el.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setASSCode(style, code) {
|
||||||
|
try {
|
||||||
|
style.el.replaceSync(code); // throws on @import per spec
|
||||||
|
style.ass = true;
|
||||||
|
} catch (err) {
|
||||||
|
style.ass = false;
|
||||||
|
}
|
||||||
|
const isTight = style.ass && list.every(s => s.ass);
|
||||||
|
if (ASS.isTight !== isTight) {
|
||||||
|
ASS.isTight = isTight;
|
||||||
|
docRootObserver[isTight ? 'stop' : 'start']();
|
||||||
|
}
|
||||||
|
return style.ass;
|
||||||
|
}
|
||||||
|
|
||||||
function _toggleObservers(shouldStart) {
|
function _toggleObservers(shouldStart) {
|
||||||
const onOff = shouldStart && isEnabled ? 'start' : 'stop';
|
const onOff = shouldStart && isEnabled ? 'start' : 'stop';
|
||||||
docRewriteObserver[onOff]();
|
docRewriteObserver[onOff]();
|
||||||
|
@ -163,6 +222,51 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _init(styleMap) {
|
||||||
|
if (ASS == null && Array.isArray(document.adoptedStyleSheets)) {
|
||||||
|
_initASS(styleMap.ASS);
|
||||||
|
prefs.subscribe(['adoptedStyleSheets'], (key, value) => {
|
||||||
|
_toggleObservers(false);
|
||||||
|
_addRemoveElements(false);
|
||||||
|
_initASS(value);
|
||||||
|
list.forEach(_domCreate);
|
||||||
|
if (isEnabled) _addRemoveElements(true);
|
||||||
|
_toggleObservers(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete styleMap.ASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _initASS(enable) {
|
||||||
|
if (ASS && !enable) {
|
||||||
|
ASS = false;
|
||||||
|
} else if (!ASS && enable) {
|
||||||
|
ASS = {
|
||||||
|
isTight: true,
|
||||||
|
/** @param {CSSStyleSheet[]} sheets */
|
||||||
|
set list(sheets) {
|
||||||
|
document.adoptedStyleSheets = sheets;
|
||||||
|
},
|
||||||
|
/** @returns {CSSStyleSheet[]} */
|
||||||
|
get list() {
|
||||||
|
return [...document.adoptedStyleSheets];
|
||||||
|
},
|
||||||
|
/** @returns {CSSStyleSheet[]} without our elements */
|
||||||
|
get wipedList() {
|
||||||
|
return ASS.list.filter(({media: {0: id = ''}}) =>
|
||||||
|
!(id.startsWith(PREFIX) && Number(id.slice(PREFIX.length)) || id.endsWith(PATCH_ID)));
|
||||||
|
},
|
||||||
|
omit({el, list = ASS.list, commit}) {
|
||||||
|
const i = list.indexOf(el);
|
||||||
|
if (i >= 0) list.splice(i, 1);
|
||||||
|
if (commit) ASS.list = list;
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FF59+ workaround: allow the page to read our sheets, https://github.com/openstyles/stylus/issues/461
|
FF59+ workaround: allow the page to read our sheets, https://github.com/openstyles/stylus/issues/461
|
||||||
First we're trying the page context document where inline styles may be forbidden by CSP
|
First we're trying the page context document where inline styles may be forbidden by CSP
|
||||||
|
@ -174,7 +278,7 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
creationDoc = !Event.prototype.getPreventDefault && document.wrappedJSObject;
|
creationDoc = !Event.prototype.getPreventDefault && document.wrappedJSObject;
|
||||||
if (creationDoc) {
|
if (creationDoc) {
|
||||||
({createElement, createElementNS} = creationDoc);
|
({createElement, createElementNS} = creationDoc);
|
||||||
const el = document.documentElement.appendChild(_createStyle());
|
const el = document.documentElement.appendChild(_domCreate());
|
||||||
const isApplied = el.sheet;
|
const isApplied = el.sheet;
|
||||||
el.remove();
|
el.remove();
|
||||||
if (isApplied) return;
|
if (isApplied) return;
|
||||||
|
@ -188,7 +292,7 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
if (!style) return;
|
if (!style) return;
|
||||||
table.delete(id);
|
table.delete(id);
|
||||||
list.splice(list.indexOf(style), 1);
|
list.splice(list.indexOf(style), 1);
|
||||||
style.el.remove();
|
_domDelete(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _sort() {
|
function _sort() {
|
||||||
|
@ -237,13 +341,16 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
// workaround for Chrome devtools bug fixed in v65
|
// workaround for Chrome devtools bug fixed in v65
|
||||||
if (isChromePre65) {
|
if (isChromePre65) {
|
||||||
const oldEl = style.el;
|
const oldEl = style.el;
|
||||||
style.el = _createStyle(id, code);
|
style.el = _domCreate({id, code});
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
oldEl.parentNode.insertBefore(style.el, oldEl.nextSibling);
|
oldEl.parentNode.insertBefore(style.el, oldEl.nextSibling);
|
||||||
oldEl.remove();
|
oldEl.remove();
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!style.ass) {
|
||||||
style.el.textContent = code;
|
style.el.textContent = code;
|
||||||
|
} else if (!_setASSCode(style, code)) {
|
||||||
|
_remove(id);
|
||||||
|
_add(style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,21 +390,25 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
let digest = 0;
|
let digest = 0;
|
||||||
let lastCalledTime = NaN;
|
let lastCalledTime = NaN;
|
||||||
let observing = false;
|
let observing = false;
|
||||||
const observer = new MutationObserver(() => {
|
let observer;
|
||||||
if (digest) {
|
|
||||||
if (performance.now() - lastCalledTime > 1000) {
|
|
||||||
digest = 0;
|
|
||||||
} else if (digest > 5) {
|
|
||||||
throw new Error('The page keeps generating mutations. Skip the event.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onChange()) {
|
|
||||||
digest++;
|
|
||||||
lastCalledTime = performance.now();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {evade, start, stop};
|
return {evade, start, stop};
|
||||||
|
|
||||||
|
function create() {
|
||||||
|
observer = new MutationObserver(() => {
|
||||||
|
if (digest) {
|
||||||
|
if (performance.now() - lastCalledTime > 1000) {
|
||||||
|
digest = 0;
|
||||||
|
} else if (digest > 5) {
|
||||||
|
throw new Error('The page keeps generating mutations. Skip the event.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (onChange()) {
|
||||||
|
digest++;
|
||||||
|
lastCalledTime = performance.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function evade(fn) {
|
function evade(fn) {
|
||||||
const restore = observing && start;
|
const restore = observing && start;
|
||||||
stop();
|
stop();
|
||||||
|
@ -306,7 +417,8 @@ self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (observing) return;
|
if (observing || ASS && ASS.isTight) return;
|
||||||
|
if (!observer) create();
|
||||||
observer.observe(document.documentElement, {childList: true});
|
observer.observe(document.documentElement, {childList: true});
|
||||||
observing = true;
|
observing = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
|
||||||
'disableAll': false, // boss key
|
'disableAll': false, // boss key
|
||||||
'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes
|
'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes
|
||||||
'newStyleAsUsercss': false, // create new style in usercss format
|
'newStyleAsUsercss': false, // create new style in usercss format
|
||||||
|
'adoptedStyleSheets': false, // try to apply styles via document.adoptedStyleSheets
|
||||||
|
|
||||||
// checkbox in style config dialog
|
// checkbox in style config dialog
|
||||||
'config.autosave': true,
|
'config.autosave': true,
|
||||||
|
|
|
@ -232,6 +232,13 @@
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
<label>
|
||||||
|
<span i18n-html="optionsAdvancedAdoptedStyleSheets"></span>
|
||||||
|
<span class="onoffswitch">
|
||||||
|
<input type="checkbox" id="adoptedStyleSheets" class="slider">
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="optionsAdvancedExposeIframes">
|
<span i18n-text="optionsAdvancedExposeIframes">
|
||||||
<a data-cmd="note"
|
<a data-cmd="note"
|
||||||
|
|
|
@ -38,6 +38,10 @@ if (FIREFOX && 'update' in (chrome.commands || {})) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(document.adoptedStyleSheets)) {
|
||||||
|
$('#adoptedStyleSheets').closest('label').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
$('#options-close-icon').onclick = () => {
|
$('#options-close-icon').onclick = () => {
|
||||||
top.dispatchEvent(new CustomEvent('closeOptions'));
|
top.dispatchEvent(new CustomEvent('closeOptions'));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user