2017-03-26 02:30:59 +00:00
|
|
|
'use strict';
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
document.addEventListener('stylishUpdateChrome', onUpdateClicked);
|
|
|
|
document.addEventListener('stylishInstallChrome', onInstallClicked);
|
|
|
|
|
|
|
|
new MutationObserver(waitForBody)
|
|
|
|
.observe(document.documentElement, {childList: true});
|
|
|
|
|
|
|
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
|
|
// orphaned content script check
|
|
|
|
if (msg.method == 'ping') {
|
|
|
|
sendResponse(true);
|
|
|
|
}
|
2012-04-16 01:56:12 +00:00
|
|
|
});
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function waitForBody() {
|
|
|
|
if (!document.body) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.disconnect();
|
|
|
|
rebrand([{addedNodes: [document.body]}]);
|
|
|
|
const rebrandObserver = new MutationObserver(rebrand);
|
|
|
|
rebrandObserver.observe(document.body, {childList: true, subtree: true});
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function _() {
|
|
|
|
document.removeEventListener('DOMContentLoaded', _);
|
|
|
|
rebrandObserver.disconnect();
|
|
|
|
chrome.runtime.sendMessage({
|
|
|
|
method: 'getStyles',
|
|
|
|
url: getMeta('stylish-id-url') || location.href
|
|
|
|
}, checkUpdatability);
|
|
|
|
});
|
2013-05-09 03:10:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function checkUpdatability([installedStyle]) {
|
|
|
|
if (!installedStyle) {
|
|
|
|
sendEvent('styleCanBeInstalledChrome');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const md5Url = getMeta('stylish-md5-url');
|
|
|
|
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
|
|
|
|
getResource(md5Url).then(md5 => {
|
|
|
|
reportUpdatable(md5 != installedStyle.originalMd5);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
getResource(getMeta('stylish-code-chrome')).then(code => {
|
|
|
|
reportUpdatable(code === null ||
|
|
|
|
!styleSectionsEqual(JSON.parse(code), installedStyle));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function reportUpdatable(isUpdatable) {
|
|
|
|
sendEvent(
|
|
|
|
isUpdatable
|
|
|
|
? 'styleCanBeUpdatedChrome'
|
|
|
|
: 'styleAlreadyInstalledChrome',
|
|
|
|
{
|
|
|
|
updateUrl: installedStyle.updateUrl
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2013-05-09 03:10:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function sendEvent(type, detail = null) {
|
|
|
|
detail = {detail};
|
|
|
|
if (typeof cloneInto != 'undefined') {
|
|
|
|
// Firefox requires explicit cloning, however USO can't process our messages anyway
|
|
|
|
// because USO tries to use a global "event" variable deprecated in Firefox
|
|
|
|
detail = cloneInto(detail, document); // eslint-disable-line no-undef
|
|
|
|
}
|
|
|
|
document.dispatchEvent(new CustomEvent(type, detail));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onInstallClicked() {
|
|
|
|
if (!orphanCheck()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
getResource(getMeta('stylish-description'))
|
|
|
|
.then(name => saveStyleCode('styleInstall', name))
|
|
|
|
.then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
|
2012-04-16 01:56:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function onUpdateClicked() {
|
|
|
|
if (!orphanCheck()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chrome.runtime.sendMessage({
|
|
|
|
method: 'getStyles',
|
|
|
|
url: getMeta('stylish-id-url') || location.href,
|
|
|
|
}, ([style]) => {
|
|
|
|
saveStyleCode('styleUpdate', style.name, {id: style.id});
|
|
|
|
});
|
2017-03-19 01:58:16 +00:00
|
|
|
}
|
2012-04-16 01:56:12 +00:00
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function saveStyleCode(message, name, addProps) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
if (!confirm(chrome.i18n.getMessage(message, [name]))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
getResource(getMeta('stylish-code-chrome')).then(code => {
|
|
|
|
chrome.runtime.sendMessage(
|
|
|
|
Object.assign(JSON.parse(code), addProps, {method: 'saveStyle'}),
|
|
|
|
() => sendEvent('styleInstalledChrome')
|
|
|
|
);
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
2017-03-19 01:58:16 +00:00
|
|
|
}
|
2013-05-09 03:10:08 +00:00
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
2012-04-16 01:56:12 +00:00
|
|
|
function getMeta(name) {
|
2017-04-15 09:24:14 +00:00
|
|
|
const e = document.querySelector(`link[rel="${name}"]`);
|
|
|
|
return e ? e.getAttribute('href') : null;
|
2012-04-16 01:56:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
|
|
|
|
function getResource(url) {
|
|
|
|
if (url.startsWith('#')) {
|
|
|
|
return Promise.resolve(document.getElementById(url.slice(1)).textContent);
|
|
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.onloadend = () => resolve(xhr.status < 400 ? xhr.responseText : null);
|
|
|
|
if (url.length > 2000) {
|
|
|
|
const [mainUrl, query] = url.split('?');
|
|
|
|
xhr.open('POST', mainUrl, true);
|
|
|
|
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
|
|
xhr.send(query);
|
|
|
|
} else {
|
|
|
|
xhr.open('GET', url);
|
|
|
|
xhr.send();
|
|
|
|
}
|
|
|
|
});
|
2012-04-16 01:56:12 +00:00
|
|
|
}
|
2017-02-03 12:12:23 +00:00
|
|
|
|
2017-03-19 01:58:16 +00:00
|
|
|
|
2017-04-15 09:24:14 +00:00
|
|
|
function rebrand(mutations) {
|
|
|
|
/* stylish to stylus; https://github.com/schomery/stylish-chrome/issues/12 */
|
2017-04-15 10:25:48 +00:00
|
|
|
for (let m = mutations.length; --m >= 0;) {
|
|
|
|
const added = mutations[m].addedNodes;
|
|
|
|
for (let n = added.length; --n >= 0;) {
|
|
|
|
const addedNode = added[n];
|
2017-04-15 09:24:14 +00:00
|
|
|
if (addedNode.nodeType != Node.ELEMENT_NODE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const elementsToCheck = addedNode.matches('.install-status') ? [addedNode]
|
2017-04-15 10:25:48 +00:00
|
|
|
: addedNode.getElementsByClassName('install-status');
|
|
|
|
for (let i = elementsToCheck.length; --i >= 0;) {
|
|
|
|
const el = elementsToCheck[i];
|
2017-04-15 09:24:14 +00:00
|
|
|
if (!el.textContent.includes('Stylish')) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
|
|
while (walker.nextNode()) {
|
|
|
|
const node = walker.currentNode;
|
|
|
|
const text = node.nodeValue;
|
|
|
|
if (text.includes('Stylish') && node.parentNode.localName != 'a') {
|
|
|
|
node.nodeValue = text.replace(/Stylish/g, 'Stylus');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function styleSectionsEqual({sections: a}, {sections: b}) {
|
|
|
|
if (!a || !b) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
if (a.length != b.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const checkedInB = [];
|
|
|
|
return a.every(sectionA => b.some(sectionB => {
|
|
|
|
if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) {
|
|
|
|
checkedInB.push(sectionB);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
function propertiesEqual(secA, secB) {
|
|
|
|
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
|
|
|
|
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b);
|
|
|
|
}
|
|
|
|
|
|
|
|
function equalOrEmpty(a, b, telltale, comparator) {
|
|
|
|
const typeA = a && typeof a[telltale] == 'function';
|
|
|
|
const typeB = b && typeof b[telltale] == 'function';
|
|
|
|
return (
|
|
|
|
(a === null || a === undefined || (typeA && !a.length)) &&
|
|
|
|
(b === null || b === undefined || (typeB && !b.length))
|
|
|
|
) || typeA && typeB && a.length == b.length && comparator(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
function arrayMirrors(array1, array2) {
|
|
|
|
for (const el of array1) {
|
|
|
|
if (array2.indexOf(el) < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const el of array2) {
|
|
|
|
if (array1.indexOf(el) < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2017-03-19 01:58:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
function orphanCheck() {
|
2017-04-15 09:24:14 +00:00
|
|
|
const port = chrome.runtime.connect();
|
|
|
|
if (port) {
|
|
|
|
port.disconnect();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// we're orphaned due to an extension update
|
|
|
|
// we can detach event listeners
|
|
|
|
document.removeEventListener('stylishUpdateChrome', onUpdateClicked);
|
|
|
|
document.removeEventListener('stylishInstallChrome', onInstallClicked);
|
|
|
|
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
|
|
|
|
// we can destroy global functions in this context to free up memory
|
|
|
|
[
|
|
|
|
'checkUpdatability',
|
|
|
|
'getMeta',
|
|
|
|
'getResource',
|
|
|
|
'onInstallClicked',
|
|
|
|
'onUpdateClicked',
|
|
|
|
'orphanCheck',
|
|
|
|
'rebrand',
|
|
|
|
'saveStyleCode',
|
|
|
|
'sendEvent',
|
|
|
|
'styleSectionsEqual',
|
|
|
|
'waitForBody',
|
|
|
|
].forEach(fn => (window[fn] = null));
|
2017-03-19 01:58:16 +00:00
|
|
|
}
|