expose style name (#1403)

This commit is contained in:
tophf 2022-02-10 21:28:47 +03:00 committed by GitHub
parent b86cb6a36c
commit 10de02f04d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 17 deletions

View File

@ -1089,6 +1089,12 @@
"message": "Exposes the top site domain in each iframe.\nEnables writing iframe-specific CSS like this:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }", "message": "Exposes the top site domain in each iframe.\nEnables writing iframe-specific CSS like this:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }",
"description": "Add attribute to iframe; make sure to include the double $$ in the css example, or the `$=` will be omitted in the displayed text." "description": "Add attribute to iframe; make sure to include the double $$ in the css example, or the `$=` will be omitted in the displayed text."
}, },
"optionsAdvancedExposeStyleName": {
"message": "Expose style name"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Exposes the style name in the page to facilitate debugging of styles in devtools. Please reload the tab(s) to apply the new setting."
},
"optionsAdvancedNewStyleAsUsercss": { "optionsAdvancedNewStyleAsUsercss": {
"message": "Write new style as usercss" "message": "Write new style as usercss"
}, },

View File

@ -184,6 +184,8 @@ const styleMan = (() => {
}, },
}; };
} }
// TODO: enable in FF when it supports sourceURL comment in style elements (also options.html)
const {exposeStyleName} = CHROME && prefs.__values;
const sender = CHROME && this && this.sender || {}; const sender = CHROME && this && this.sender || {};
if (sender.frameId === 0) { if (sender.frameId === 0) {
/* Chrome hides text frament from location.href of the page e.g. #:~:text=foo /* Chrome hides text frament from location.href of the page e.g. #:~:text=foo
@ -202,9 +204,9 @@ const styleMan = (() => {
} else if (cache.maybeMatch.size) { } else if (cache.maybeMatch.size) {
buildCache(cache, url, Array.from(cache.maybeMatch, id2data).filter(Boolean)); buildCache(cache, url, Array.from(cache.maybeMatch, id2data).filter(Boolean));
} }
return id return Object.assign({cfg: {exposeStyleName, order}},
? cache.sections[id] ? {[id]: cache.sections[id]} : {} id ? mapObj(cache.sections, null, [id])
: Object.assign({cfg: {order}}, cache.sections); : cache.sections);
}, },
/** @returns {Promise<StyleObj>} */ /** @returns {Promise<StyleObj>} */
@ -429,7 +431,7 @@ const styleMan = (() => {
const code = getAppliedCode(createMatchQuery(url), style); const code = getAppliedCode(createMatchQuery(url), style);
if (code) { if (code) {
updated.add(url); updated.add(url);
cache.sections[id] = {id, code}; buildCacheEntry(cache, style, code);
} else { } else {
excluded.add(url); excluded.add(url);
delete cache.sections[id]; delete cache.sections[id];
@ -703,13 +705,20 @@ const styleMan = (() => {
for (const {style, appliesTo, preview} of styleList) { for (const {style, appliesTo, preview} of styleList) {
const code = getAppliedCode(query, preview || style); const code = getAppliedCode(query, preview || style);
if (code) { if (code) {
const id = style.id; buildCacheEntry(cache, style, code);
cache.sections[id] = {id, code};
appliesTo.add(url); appliesTo.add(url);
} }
} }
} }
function buildCacheEntry(cache, style, code = style.code) {
cache.sections[style.id] = {
code,
id: style.id,
name: style.customName || style.name,
};
}
/** @returns {StyleObj[]} */ /** @returns {StyleObj[]} */
function getAllAsArray() { function getAllAsArray() {
return Array.from(dataMap.values(), v => v.style); return Array.from(dataMap.values(), v => v.style);

View File

@ -1,5 +1,5 @@
/* global API */// msg.js /* global API */// msg.js
/* global CHROME ignoreChromeError */// toolbox.js /* global CHROME URLS ignoreChromeError */// toolbox.js
/* global prefs */ /* global prefs */
'use strict'; 'use strict';
@ -49,6 +49,12 @@
if (CHROME && !off) { if (CHROME && !off) {
chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]}); chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]});
} }
if (CHROME) {
chrome.webRequest.onBeforeRequest.addListener(openNamedStyle, {
urls: [URLS.ownOrigin + 'virtual/*.css'],
types: ['main_frame'],
}, ['blocking']);
}
state.csp = csp; state.csp = csp;
state.off = off; state.off = off;
state.xhr = xhr; state.xhr = xhr;
@ -146,6 +152,12 @@
} }
} }
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
function openNamedStyle(req) {
chrome.tabs.update(req.tabId, {url: 'edit.html?id=' + req.url.split('#')[1]});
return {cancel: true};
}
function req2key(req) { function req2key(req) {
return req.tabId + ':' + req.frameId; return req.tabId + ':' + req.frameId;
} }

View File

@ -105,7 +105,6 @@
if (styles.cfg) { if (styles.cfg) {
isDisabled = styles.cfg.disableAll; isDisabled = styles.cfg.disableAll;
Object.assign(order, styles.cfg.order); Object.assign(order, styles.cfg.order);
delete styles.cfg;
} }
hasStyles = !isDisabled; hasStyles = !isDisabled;
if (hasStyles) { if (hasStyles) {
@ -181,7 +180,6 @@
if (!hasStyles && isDisabled || matchUrl === request.url) break; if (!hasStyles && isDisabled || matchUrl === request.url) break;
matchUrl = request.url; matchUrl = request.url;
API.styles.getSectionsByUrl(matchUrl).then(sections => { API.styles.getSectionsByUrl(matchUrl).then(sections => {
delete sections.cfg;
hasStyles = true; hasStyles = true;
styleInjector.replace(sections); styleInjector.replace(sections);
}); });

View File

@ -11,10 +11,12 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
const ORDERED_TAGS = new Set(['head', 'body', 'frameset', 'style', 'link']); const ORDERED_TAGS = new Set(['head', 'body', 'frameset', 'style', 'link']);
const docRewriteObserver = RewriteObserver(_sort); const docRewriteObserver = RewriteObserver(_sort);
const docRootObserver = RootObserver(_sortIfNeeded); const docRootObserver = RootObserver(_sortIfNeeded);
const toSafeChar = c => String.fromCharCode(0xFF00 + c.charCodeAt(0) - 0x20);
const list = []; const list = [];
const table = new Map(); const table = new Map();
let isEnabled = true; let isEnabled = true;
let isTransitionPatched; let isTransitionPatched;
let exposeStyleName;
// 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;
@ -83,7 +85,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
}; };
function _add(style) { function _add(style) {
const el = style.el = _createStyle(style.id, style.code); const el = style.el = _createStyle(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) {
@ -116,18 +118,18 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
!styles.some(s => s.code.includes('transition'))) { !styles.some(s => s.code.includes('transition'))) {
return; return;
} }
const el = _createStyle(PATCH_ID, ` const el = _createStyle({id: PATCH_ID, code: `
:root:not(#\\0):not(#\\0) * { :root:not(#\\0):not(#\\0) * {
transition: none !important; transition: none !important;
} }
`); `});
document.documentElement.appendChild(el); document.documentElement.appendChild(el);
// 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(() => el.remove()));
} }
function _createStyle(id, code = '') { function _createStyle({id, code = '', name} = {}) {
if (!creationDoc) _initCreationDoc(); if (!creationDoc) _initCreationDoc();
let el; let el;
if (document.documentElement instanceof SVGSVGElement) { if (document.documentElement instanceof SVGSVGElement) {
@ -145,6 +147,11 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
const oldEl = document.getElementById(el.id); const oldEl = document.getElementById(el.id);
if (oldEl) oldEl.id += '-superseded-by-Stylus'; if (oldEl) oldEl.id += '-superseded-by-Stylus';
} }
if (exposeStyleName && name) {
el.dataset.name = name;
name = encodeURIComponent(name.replace(/[#%/@:']/g, toSafeChar));
code += `\n/*# sourceURL=${chrome.runtime.getURL('')}virtual/${name}.css#${id} */`;
}
el.type = 'text/css'; el.type = 'text/css';
// 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');
@ -224,9 +231,12 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
} }
function _styleMapToArray(styleMap) { function _styleMapToArray(styleMap) {
return Object.values(styleMap).map(s => ({ ({exposeStyleName} = styleMap.cfg || {});
id: s.id, delete styleMap.cfg;
code: s.code.join(''), return Object.values(styleMap).map(({id, code, name}) => ({
id,
name,
code: code.join(''),
})); }));
} }

View File

@ -24,6 +24,7 @@
'show-badge': true, // display text on popup menu icon 'show-badge': true, // display text on popup menu icon
'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
'exposeStyleName': false, // Add style name to the style for better devtools experience
'newStyleAsUsercss': false, // create new style in usercss format 'newStyleAsUsercss': false, // create new style in usercss format
'styleViaXhr': false, // early style injection to avoid FOUC 'styleViaXhr': false, // early style injection to avoid FOUC
'patchCsp': false, // add data: and popular image hosting sites to strict CSP 'patchCsp': false, // add data: and popular image hosting sites to strict CSP

View File

@ -269,6 +269,18 @@
<span></span> <span></span>
</span> </span>
</label> </label>
<label class="chromium-only">
<span i18n-text="optionsAdvancedExposeStyleName">
<a i18n-title="optionsAdvancedExposeStyleNameNote"
data-cmd="note" class="svg-inline-wrapper" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</span>
<span class="onoffswitch">
<input type="checkbox" id="exposeStyleName" class="slider">
<span></span>
</span>
</label>
<label class="chromium-only"> <label class="chromium-only">
<span i18n-text="optionsAdvancedContextDelete"></span> <span i18n-text="optionsAdvancedContextDelete"></span>
<span class="onoffswitch"> <span class="onoffswitch">

View File

@ -105,6 +105,11 @@ a:hover .svg-icon,
.chromium-only.chrome-no-popup-border { .chromium-only.chrome-no-popup-border {
display: none; display: none;
} }
label.chromium-only > :first-child::after {
content: '(Chrome)';
color: #888;
margin-left: .5ex;
}
.block { .block {
display: flex; display: flex;
@ -228,7 +233,7 @@ input[type="color"] {
position: relative; position: relative;
} }
[data-cmd="note"] { [data-cmd="note"] {
padding: .5em 1em .5em 0; padding: .5em 0 .5em 0;
cursor: pointer; cursor: pointer;
} }
.update-in-progress [data-cmd="check-updates"] { .update-in-progress [data-cmd="check-updates"] {