use executeScript for early data injection
This commit is contained in:
parent
ac66095ee0
commit
5d306150b0
|
@ -11,96 +11,91 @@ define(async require => {
|
||||||
const rxHOST = /^('none'|(https?:\/\/)?[^']+?[^:'])$/; // strips CSP sources covered by *
|
const rxHOST = /^('none'|(https?:\/\/)?[^']+?[^:'])$/; // strips CSP sources covered by *
|
||||||
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
|
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
|
||||||
const stylesToPass = {};
|
const stylesToPass = {};
|
||||||
const enabled = {};
|
const state = {};
|
||||||
|
|
||||||
await prefs.initializing;
|
await prefs.initializing;
|
||||||
prefs.subscribe([idXHR, idOFF, idCSP], toggle, {runNow: true});
|
prefs.subscribe([idXHR, idOFF, idCSP], toggle);
|
||||||
|
toggle();
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
const csp = prefs.get(idCSP) && !prefs.get(idOFF);
|
const off = prefs.get(idOFF);
|
||||||
const xhr = prefs.get(idXHR) && !prefs.get(idOFF) && Boolean(chrome.declarativeContent);
|
const csp = prefs.get(idCSP) && !off;
|
||||||
if (xhr === enabled.xhr && csp === enabled.csp) {
|
const xhr = prefs.get(idXHR) && !off;
|
||||||
|
if (xhr === state.xhr && csp === state.csp && off === state.off) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Need to unregister first so that the optional EXTRA_HEADERS is properly registered
|
const reqFilter = {
|
||||||
|
urls: ['*://*/*'],
|
||||||
|
types: ['main_frame', 'sub_frame'],
|
||||||
|
};
|
||||||
|
chrome.webNavigation.onCommitted.removeListener(injectData);
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
|
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(modifyHeaders);
|
chrome.webRequest.onHeadersReceived.removeListener(modifyHeaders);
|
||||||
if (xhr || csp) {
|
if (xhr || csp) {
|
||||||
const reqFilter = {
|
// We unregistered it above so that the optional EXTRA_HEADERS is properly re-registered
|
||||||
urls: ['<all_urls>'],
|
|
||||||
types: ['main_frame', 'sub_frame'],
|
|
||||||
};
|
|
||||||
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
|
|
||||||
chrome.webRequest.onHeadersReceived.addListener(modifyHeaders, reqFilter, [
|
chrome.webRequest.onHeadersReceived.addListener(modifyHeaders, reqFilter, [
|
||||||
'blocking',
|
'blocking',
|
||||||
'responseHeaders',
|
'responseHeaders',
|
||||||
xhr && chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
|
xhr && chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
|
||||||
].filter(Boolean));
|
].filter(Boolean));
|
||||||
}
|
}
|
||||||
if (enabled.xhr !== xhr) {
|
if (!off) {
|
||||||
enabled.xhr = xhr;
|
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
|
||||||
toggleEarlyInjection();
|
chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]});
|
||||||
}
|
}
|
||||||
enabled.csp = csp;
|
state.csp = csp;
|
||||||
}
|
state.off = off;
|
||||||
|
state.xhr = xhr;
|
||||||
/** Runs content scripts earlier than document_start */
|
|
||||||
function toggleEarlyInjection() {
|
|
||||||
const api = chrome.declarativeContent;
|
|
||||||
if (!api) return;
|
|
||||||
api.onPageChanged.removeRules([idXHR], async () => {
|
|
||||||
if (enabled.xhr) {
|
|
||||||
api.onPageChanged.addRules([{
|
|
||||||
id: idXHR,
|
|
||||||
conditions: [
|
|
||||||
new api.PageStateMatcher({
|
|
||||||
pageUrl: {urlContains: '://'},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
new api.RequestContentScript({
|
|
||||||
js: chrome.runtime.getManifest().content_scripts[0].js,
|
|
||||||
allFrames: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
||||||
async function prepareStyles(req) {
|
async function prepareStyles(req) {
|
||||||
const sections = await API.styles.getSectionsByUrl(req.url);
|
const sections = await API.styles.getSectionsByUrl(req.url);
|
||||||
if (!isEmptyObj(sections)) {
|
if (!isEmptyObj(sections)) {
|
||||||
stylesToPass[req.requestId] = !enabled.xhr || makeObjectUrl(sections);
|
stylesToPass[req.url] = JSON.stringify(sections);
|
||||||
setTimeout(cleanUp, 600e3, req.requestId);
|
setTimeout(cleanUp, 600e3, req.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeObjectUrl(sections) {
|
function injectData(req) {
|
||||||
const blob = new Blob([JSON.stringify(sections)]);
|
const str = stylesToPass[req.url];
|
||||||
|
if (str) {
|
||||||
|
chrome.tabs.executeScript(req.tabId, {
|
||||||
|
frameId: req.frameId,
|
||||||
|
runAt: 'document_start',
|
||||||
|
code: `(${data => {
|
||||||
|
if (self.INJECTED !== 1) { // storing data only if apply.js hasn't run yet
|
||||||
|
window[Symbol.for('styles')] = data;
|
||||||
|
}
|
||||||
|
}})(${str})`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeObjectUrl(data) {
|
||||||
|
const blob = new Blob([data]);
|
||||||
return URL.createObjectURL(blob).slice(blobUrlPrefix.length);
|
return URL.createObjectURL(blob).slice(blobUrlPrefix.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
|
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
|
||||||
function modifyHeaders(req) {
|
function modifyHeaders(req) {
|
||||||
const {responseHeaders} = req;
|
const {responseHeaders} = req;
|
||||||
const id = stylesToPass[req.requestId];
|
const str = stylesToPass[req.url];
|
||||||
if (!id) {
|
if (!str) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (enabled.xhr) {
|
if (state.xhr) {
|
||||||
responseHeaders.push({
|
responseHeaders.push({
|
||||||
name: 'Set-Cookie',
|
name: 'Set-Cookie',
|
||||||
value: `${chrome.runtime.id}=${id}`,
|
value: `${chrome.runtime.id}=${makeObjectUrl(str)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const csp = enabled.csp &&
|
const csp = state.csp &&
|
||||||
responseHeaders.find(h => h.name.toLowerCase() === 'content-security-policy');
|
responseHeaders.find(h => h.name.toLowerCase() === 'content-security-policy');
|
||||||
if (csp) {
|
if (csp) {
|
||||||
patchCsp(csp);
|
patchCsp(csp);
|
||||||
}
|
}
|
||||||
if (enabled.xhr || csp) {
|
if (state.xhr || csp) {
|
||||||
return {responseHeaders};
|
return {responseHeaders};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,12 @@ define(require => {
|
||||||
if (STYLE_VIA_API) {
|
if (STYLE_VIA_API) {
|
||||||
await API.styleViaAPI({method: 'styleApply'});
|
await API.styleViaAPI({method: 'styleApply'});
|
||||||
} else {
|
} else {
|
||||||
const styles = chrome.app && !chrome.tabs && getStylesViaXhr() ||
|
const SYM = Symbol.for('styles');
|
||||||
|
const styles =
|
||||||
|
window[SYM] ||
|
||||||
|
chrome.app && !chrome.tabs && getStylesViaXhr() ||
|
||||||
await API.styles.getSectionsByUrl(getMatchUrl(), null, true);
|
await API.styles.getSectionsByUrl(getMatchUrl(), null, true);
|
||||||
|
delete window[SYM];
|
||||||
if (styles.disableAll) {
|
if (styles.disableAll) {
|
||||||
delete styles.disableAll;
|
delete styles.disableAll;
|
||||||
styleInjector.toggle(false);
|
styleInjector.toggle(false);
|
||||||
|
|
|
@ -6,11 +6,7 @@ const STYLUS_BACKUP_FILE_EXT = '.json';
|
||||||
define(require => {
|
define(require => {
|
||||||
const {API} = require('/js/msg');
|
const {API} = require('/js/msg');
|
||||||
const {isEmptyObj} = require('/js/polyfill');
|
const {isEmptyObj} = require('/js/polyfill');
|
||||||
const {
|
const {deepEqual, tryJSONparse} = require('/js/toolbox');
|
||||||
CHROME,
|
|
||||||
deepEqual,
|
|
||||||
tryJSONparse,
|
|
||||||
} = require('/js/toolbox');
|
|
||||||
const t = require('/js/localization');
|
const t = require('/js/localization');
|
||||||
const prefs = require('/js/prefs');
|
const prefs = require('/js/prefs');
|
||||||
const {
|
const {
|
||||||
|
@ -260,11 +256,6 @@ define(require => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importOptions() {
|
async function importOptions() {
|
||||||
// Must acquire the permission before setting the pref
|
|
||||||
if (CHROME && !chrome.declarativeContent &&
|
|
||||||
stats.options.names.find(_ => _.name === 'styleViaXhr' && _.isValid && _.val)) {
|
|
||||||
await browser.permissions.request({permissions: ['declarativeContent']});
|
|
||||||
}
|
|
||||||
const oldStorage = await chromeSync.get();
|
const oldStorage = await chromeSync.get();
|
||||||
for (const {name, val, isValid, isPref} of stats.options.names) {
|
for (const {name, val, isValid, isPref} of stats.options.names) {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
|
|
|
@ -23,9 +23,6 @@
|
||||||
"identity",
|
"identity",
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
],
|
],
|
||||||
"optional_permissions": [
|
|
||||||
"declarativeContent"
|
|
||||||
],
|
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"js/polyfill.js",
|
"js/polyfill.js",
|
||||||
|
|
|
@ -53,19 +53,6 @@ define(require => {
|
||||||
$('[data-cmd="open-keyboard"]').classList.remove('chromium-only');
|
$('[data-cmd="open-keyboard"]').classList.remove('chromium-only');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CHROME && !chrome.declarativeContent) {
|
|
||||||
// Show the option as disabled until the permission is actually granted
|
|
||||||
const el = $('#styleViaXhr');
|
|
||||||
prefs.initializing.then(() => {
|
|
||||||
el.checked = false;
|
|
||||||
});
|
|
||||||
el.on('click', () => {
|
|
||||||
if (el.checked) {
|
|
||||||
chrome.permissions.request({permissions: ['declarativeContent']}, ignoreChromeError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
$('#options-close-icon').onclick = () => {
|
$('#options-close-icon').onclick = () => {
|
||||||
top.dispatchEvent(new CustomEvent('closeOptions'));
|
top.dispatchEvent(new CustomEvent('closeOptions'));
|
||||||
|
|
19
tools/zip.js
19
tools/zip.js
|
@ -3,13 +3,11 @@
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
const manifest = require('../manifest.json');
|
|
||||||
|
|
||||||
function createZip({isFirefox} = {}) {
|
function createZip() {
|
||||||
const fileName = `stylus${isFirefox ? '-firefox' : ''}.zip`;
|
const fileName = 'stylus.zip';
|
||||||
const ignore = [
|
const ignore = [
|
||||||
'.*', // dot files/folders (glob, not regexp)
|
'.*', // dot files/folders (glob, not regexp)
|
||||||
'vendor/codemirror/lib/**', // get unmodified copy from node_modules
|
|
||||||
'node_modules/**',
|
'node_modules/**',
|
||||||
'tools/**',
|
'tools/**',
|
||||||
'package.json',
|
'package.json',
|
||||||
|
@ -39,19 +37,7 @@ function createZip({isFirefox} = {}) {
|
||||||
});
|
});
|
||||||
|
|
||||||
archive.pipe(file);
|
archive.pipe(file);
|
||||||
if (isFirefox) {
|
|
||||||
const name = 'manifest.json';
|
|
||||||
const keyOpt = 'optional_permissions';
|
|
||||||
ignore.unshift(name);
|
|
||||||
manifest[keyOpt] = manifest[keyOpt].filter(p => p !== 'declarativeContent');
|
|
||||||
if (!manifest[keyOpt].length) {
|
|
||||||
delete manifest[keyOpt];
|
|
||||||
}
|
|
||||||
archive.append(JSON.stringify(manifest, null, ' '), {name, stats: fs.lstatSync(name)});
|
|
||||||
}
|
|
||||||
archive.glob('**', {ignore});
|
archive.glob('**', {ignore});
|
||||||
// Don't use modified codemirror.js (see "update-libraries.js")
|
|
||||||
archive.directory('node_modules/codemirror/lib', 'vendor/codemirror/lib');
|
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,7 +45,6 @@ function createZip({isFirefox} = {}) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
await createZip();
|
await createZip();
|
||||||
await createZip({isFirefox: true});
|
|
||||||
console.log('\x1b[32m%s\x1b[0m', 'Stylus zip complete');
|
console.log('\x1b[32m%s\x1b[0m', 'Stylus zip complete');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user