Skip to content

Commit 0708d3f

Browse files
committed
fix USO hook
1 parent d8a0a3e commit 0708d3f

File tree

8 files changed

+176
-163
lines changed

8 files changed

+176
-163
lines changed

src/background/uso-api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export async function getUpdatability(usoId, asObject) {
2525
const md5Url = getMd5Url(usoId);
2626
const md5 = await fetchText(md5Url);
2727
const dup = await findStyle(usoId, md5Url);
28-
// see STATE_EVENTS in install-hook-userstyles.js
28+
// see STATE_EVENTS in hook-uso.js
2929
const state = !dup ? 0 : dup[UCD] || dup.originalMd5 === md5 ? 2 : 1;
3030
return asObject
3131
? {dup, md5, md5Url, state}

src/content/hook-uso-page-mv3.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import hookUsoPage from './hook-uso-page';
2+
3+
addEventListener('stylus-uso*', e => hookUsoPage(e.detail), {once: true});
4+
dispatchEvent(new Event('stylus-uso'));

src/content/hook-uso-page.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
export default function hookUsoPage(eventId) {
2+
let orphaned;
3+
// `chrome` may be empty if no extensions use externally_connectable but USO needs it
4+
if (!window.chrome) window.chrome = {};
5+
if (!chrome.runtime) chrome.runtime = {sendMessage: () => {}};
6+
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
7+
const {call, defineProperty} = Object;
8+
const {dispatchEvent, CustomEvent, Promise, Response, removeEventListener} = window;
9+
const getDetail = call.bind(Object.getOwnPropertyDescriptor(CustomEvent.prototype, 'detail').get);
10+
const apply = call.bind(Object.apply);
11+
const mathRandom = Math.random;
12+
const promiseResolve = async val => val;
13+
const startsWith = call.bind(''.startsWith);
14+
const callbacks = {__proto__: null};
15+
const OVR = [
16+
[chrome.runtime, 'sendMessage', (fn, me, args) => {
17+
// id, msg, opts/cb, cb
18+
if (args[0] !== EXT_ID) return apply(fn, me, args);
19+
const msg = args[1];
20+
let cb = args[args.length - 1];
21+
let res;
22+
if (typeof cb !== 'function') res = new Promise(resolve => (cb = resolve));
23+
send('msg', msg, cb);
24+
return res;
25+
}],
26+
[window, 'fetch', (fn, me, args) =>
27+
startsWith(`${args[0]}`, `chrome-extension://${EXT_ID}/`)
28+
? promiseResolve(new Response('<!doctype html><html lang="en"></html>'))
29+
: apply(fn, me, args),
30+
],
31+
];
32+
for (let i = 0; i < OVR.length; i++) {
33+
const [obj, name, caller] = OVR[i];
34+
const orig = obj[name];
35+
const ovr = new Proxy(orig, {
36+
__proto__: null,
37+
apply(fn, me, args) {
38+
if (orphaned) restore(obj, name, ovr, fn);
39+
return (orphaned ? apply : caller)(fn, me, args);
40+
},
41+
});
42+
defineProperty(obj, name, {value: ovr});
43+
OVR[i] = [obj, name, ovr, orig]; // same args as restore()
44+
}
45+
addEventListener(eventId, onCommand, true);
46+
window.isInstalled = true; // for the old USO site (split_test_version=app50)
47+
function onCommand(e) {
48+
let v = getDetail(e);
49+
if (v.cmd === 'quit') {
50+
orphaned = true;
51+
removeEventListener(eventId, onCommand, true);
52+
for (v = 0; v < OVR.length; v++) restore(OVR[v]);
53+
} else {
54+
callbacks[v.id](v.data);
55+
delete callbacks[v.id];
56+
}
57+
}
58+
function restore(obj, name, ovr, orig) { // same order as OVR after patching
59+
if (obj[name] === ovr) {
60+
defineProperty(obj, name, {__proto__: null, value: orig});
61+
}
62+
}
63+
function send(cmd, data, cb) {
64+
let id;
65+
if (cb) callbacks[id = mathRandom()] = cb;
66+
dispatchEvent(new CustomEvent(eventId + '*', {__proto: null, detail: {id, cmd, data}}));
67+
}
68+
}

src/content/hook-uso.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* global API */// msg.js
2+
3+
import hookUsoPage from './hook-uso-page';
4+
5+
const pageId = `${performance.now()}${Math.random()}`;
6+
const STATE_EVENTS = [
7+
['uninstalled', 'styleCanBeInstalledChrome'],
8+
['canBeUpdate', 'styleCanBeUpdatedChrome'],
9+
['installed', 'styleAlreadyInstalledChrome'],
10+
];
11+
const getUsoId = () => Number(location.pathname.match(/^\/styles\/(\d+)|$/)[1]);
12+
let gesture = NaN;
13+
let pageLoading;
14+
15+
if (__.MV3) {
16+
addEventListener('stylus-uso',
17+
() => dispatchEvent(new CustomEvent('stylus-uso*', {detail: pageId})),
18+
{once: true});
19+
} else {
20+
runInPage(hookUsoPage, pageId);
21+
}
22+
addEventListener('click', onGesture, true);
23+
addEventListener('keydown', onGesture, true);
24+
addEventListener(pageId + '*', onPageEvent, true);
25+
addEventListener(chrome.runtime.id, function orphanCheck(e) {
26+
if (chrome.runtime.id) return true;
27+
removeEventListener(e.type, orphanCheck, true);
28+
removeEventListener(pageId + '*', onPageEvent, true);
29+
removeEventListener('click', onGesture, true);
30+
removeEventListener('keydown', onGesture, true);
31+
sendPageEvent({cmd: 'quit'});
32+
}, true);
33+
if ((pageLoading = !document.head && location.href)) {
34+
addEventListener('DOMContentLoaded', () => {
35+
postMessage({direction: 'from-content-script', message: 'StylishInstalled'}, '*');
36+
}, {once: true});
37+
addEventListener('load', () => {
38+
pageLoading = '';
39+
}, {once: true});
40+
}
41+
42+
function onGesture(e) {
43+
if (e.isTrusted) gesture = performance.now();
44+
}
45+
46+
function isTrusted(data) {
47+
return (pageLoading === location.href || performance.now() - gesture < 1000)
48+
|| console.warn('Stylus is ignoring request not initiated by the user:', data);
49+
}
50+
51+
async function onPageEvent({detail: {id, cmd, data}}) {
52+
if (cmd === 'msg') {
53+
let res = true;
54+
switch (data.type) {
55+
case 'stylishUpdateChrome':
56+
case 'stylishInstallChrome':
57+
if (isTrusted(data)) await API.uso.toUsercss(getUsoId(), data.customOptions || {});
58+
res = {success: true};
59+
gesture = NaN;
60+
break;
61+
case 'deleteStylishStyle': {
62+
if (isTrusted(data)) res = await API.uso.deleteStyle(getUsoId());
63+
gesture = NaN;
64+
break;
65+
}
66+
case 'getStyleInstallStatus':
67+
if (isTrusted(data)) res = (await getStyleState() || [])[0];
68+
break;
69+
case 'GET_OPEN_TABS':
70+
case 'GET_TOP_SITES':
71+
res = [];
72+
break;
73+
}
74+
sendPageEvent({id, data: res});
75+
}
76+
}
77+
78+
async function getStyleState(usoId = getUsoId()) {
79+
return STATE_EVENTS[usoId ? await API.uso.getUpdatability(usoId) : -1];
80+
}
81+
82+
function runInPage(fn, ...args) {
83+
const div = document.createElement('div');
84+
div.attachShadow({mode: 'closed'})
85+
.appendChild(document.createElement('script'))
86+
.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
87+
document.documentElement.appendChild(div).remove();
88+
}
89+
90+
function sendPageEvent(data) {
91+
/* global cloneInto */// WARNING! Firefox requires cloning of CustomEvent `detail` if it's an object
92+
if (typeof cloneInto === 'function') data = cloneInto(data, document);
93+
dispatchEvent(new CustomEvent(pageId, {detail: data}));
94+
}

src/content/install-hook-userstyles.js

Lines changed: 0 additions & 161 deletions
This file was deleted.

src/manifest-mv3.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
"matches": ["https://userstyles.world/*"],
2727
"js": ["js/install-hook-userstylesworld.js"],
2828
"run_at": "document_start"
29+
},
30+
{
31+
"matches": ["https://userstyles.org/*"],
32+
"js": ["js/hook-uso-page-mv3.js"],
33+
"run_at": "document_start",
34+
"world": "MAIN"
2935
}
3036
],
3137
"web_accessible_resources": [{

src/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
{
4040
"matches": ["https://userstyles.org/*"],
4141
"run_at": "document_start",
42-
"js": ["js/install-hook-userstyles.js"]
42+
"js": ["js/hook-uso.js"]
4343
}
4444
],
4545
"browser_action": {

webpack.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@ module.exports = [
531531
MV3 && makeLibrary('db-to-cloud/lib/drive/webdav', 'webdav'),
532532

533533
makeContentScript('apply.js'),
534+
makeContentScript('hook-uso.js'),
535+
MV3 && makeContentScript('hook-uso-page-mv3.js'),
534536
makeLibrary('@/js/worker.js', undefined, {
535537
plugins: [new RawEnvPlugin({ENTRY: 'worker'})],
536538
}),

0 commit comments

Comments
 (0)