simplify orphanCheck; use IIFE; fix comm issues

This commit is contained in:
tophf 2017-11-26 00:56:57 +03:00
parent babeb695c1
commit fb3554a351
2 changed files with 722 additions and 722 deletions

View File

@ -1,26 +1,28 @@
/* eslint no-var: 0 */
'use strict';
var ID_PREFIX = 'stylus-';
var ROOT = document.documentElement;
var isOwnPage = location.protocol.endsWith('-extension:');
var disableAll = false;
var exposeIframes = false;
var styleElements = new Map();
var disabledElements = new Map();
var retiredStyleTimers = new Map();
var docRewriteObserver;
var docRootObserver;
(() => {
var ID_PREFIX = 'stylus-';
var ROOT = document.documentElement;
var isOwnPage = location.protocol.endsWith('-extension:');
var disableAll = false;
var exposeIframes = false;
var styleElements = new Map();
var disabledElements = new Map();
var retiredStyleTimers = new Map();
var docRewriteObserver;
var docRootObserver;
requestStyles();
chrome.runtime.onMessage.addListener(applyOnMessage);
requestStyles();
chrome.runtime.onMessage.addListener(applyOnMessage);
window.applyOnMessage = applyOnMessage;
if (!isOwnPage) {
if (!isOwnPage) {
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
window.addEventListener(chrome.runtime.id, orphanCheck, true);
}
}
function requestStyles(options, callback = applyStyles) {
function requestStyles(options, callback = applyStyles) {
if (!chrome.app && document instanceof XMLDocument) {
chrome.runtime.sendMessage({method: 'styleViaAPI', action: 'styleApply'});
return;
@ -47,10 +49,10 @@ function requestStyles(options, callback = applyStyles) {
} else {
chrome.runtime.sendMessage(request, callback);
}
}
}
function applyOnMessage(request, sender, sendResponse) {
function applyOnMessage(request, sender, sendResponse) {
if (request.styles === 'DIY') {
// Do-It-Yourself tells our built-in pages to fetch the styles directly
// which is faster because IPC messaging JSON-ifies everything internally
@ -117,10 +119,10 @@ function applyOnMessage(request, sender, sendResponse) {
sendResponse(true);
break;
}
}
}
function doDisableAll(disable = disableAll) {
function doDisableAll(disable = disableAll) {
if (!disable === !disableAll) {
return;
}
@ -131,10 +133,10 @@ function doDisableAll(disable = disableAll) {
stylesheet.disabled = disable;
}
});
}
}
function doExposeIframes(state = exposeIframes) {
function doExposeIframes(state = exposeIframes) {
if (state === exposeIframes || window === parent) {
return;
}
@ -145,10 +147,10 @@ function doExposeIframes(state = exposeIframes) {
} else if (!state && attr === '') {
document.documentElement.removeAttribute('stylus-iframe');
}
}
}
function applyStyleState({id, enabled}) {
function applyStyleState({id, enabled}) {
const inCache = disabledElements.get(id) || styleElements.get(id);
const inDoc = document.getElementById(ID_PREFIX + id);
if (enabled) {
@ -168,10 +170,10 @@ function applyStyleState({id, enabled}) {
docRootObserver.start();
}
}
}
}
function removeStyle({id, retire = false}) {
function removeStyle({id, retire = false}) {
const el = document.getElementById(ID_PREFIX + id);
if (el) {
if (retire) {
@ -189,10 +191,10 @@ function removeStyle({id, retire = false}) {
styleElements.delete(ID_PREFIX + id);
disabledElements.delete(id);
retiredStyleTimers.delete(id);
}
}
function applyStyles(styles) {
function applyStyles(styles) {
if (!styles) {
// Chrome is starting up
requestStyles();
@ -253,14 +255,13 @@ function applyStyles(styles) {
}
});
}
}
function applySections(styleId, code) {
let el = document.getElementById(ID_PREFIX + styleId);
if (el) {
return;
}
function applySections(styleId, code) {
const id = ID_PREFIX + styleId;
let el = styleElements.get(id) || document.getElementById(id);
if (!el) {
if (document.documentElement instanceof SVGSVGElement) {
// SVG document style
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
@ -272,21 +273,21 @@ function applySections(styleId, code) {
el = document.createElement('style');
}
Object.assign(el, {
styleId,
id: ID_PREFIX + styleId,
id,
type: 'text/css',
textContent: code,
});
// SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus');
addStyleElement(el);
styleElements.set(el.id, el);
}
styleElements.set(id, el);
disabledElements.delete(Number(styleId));
return el;
}
}
function addStyleElement(newElement) {
function addStyleElement(newElement) {
if (!ROOT) {
return;
}
@ -307,10 +308,10 @@ function addStyleElement(newElement) {
newElement.disabled = true;
}
docRootObserver.start();
}
}
function replaceAll(newStyles) {
function replaceAll(newStyles) {
const oldStyles = Array.prototype.slice.call(
document.querySelectorAll(`style.stylus[id^="${ID_PREFIX}"]`));
oldStyles.forEach(el => (el.id += '-ghost'));
@ -320,10 +321,26 @@ function replaceAll(newStyles) {
retiredStyleTimers.clear();
applyStyles(newStyles);
oldStyles.forEach(el => el.remove());
}
}
function initDocRewriteObserver() {
function getStyleId(el) {
return parseInt(el.id.substr(ID_PREFIX.length));
}
function orphanCheck() {
if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true;
}
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.takeRecords() && ob.disconnect());
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
}
function initDocRewriteObserver() {
// detect documentElement being rewritten from inside the script
docRewriteObserver = new MutationObserver(mutations => {
for (let m = mutations.length; --m >= 0;) {
@ -346,9 +363,7 @@ function initDocRewriteObserver() {
// re-add styles if we detect documentElement being recreated
function reinjectStyles() {
if (!styleElements) {
if (orphanCheck) {
orphanCheck();
}
return;
}
ROOT = document.documentElement;
@ -363,33 +378,39 @@ function initDocRewriteObserver() {
docRootObserver.start();
styleElements = new Map(imported);
}
}
}
function initDocRootObserver() {
function initDocRootObserver() {
let lastRestorationTime = 0;
let restorationCounter = 0;
let observing = false;
let sorting = false;
// allow any types of elements between ours, except for the following:
const ORDERED_TAGS = ['head', 'body', 'style', 'link'];
docRootObserver = Object.assign(new MutationObserver(sortStyleElements), {
start({sort = false} = {}) {
init();
return;
function init() {
docRootObserver = new MutationObserver(sortStyleElements);
Object.assign(docRootObserver, {start, stop});
}
function start({sort = false} = {}) {
if (sort && sortStyleMap()) {
sortStyleElements();
}
if (!observing) {
this.observe(ROOT, {childList: true});
if (!observing && ROOT) {
docRootObserver.observe(ROOT, {childList: true});
observing = true;
}
},
stop() {
}
function stop() {
if (observing) {
this.disconnect();
docRootObserver.disconnect();
observing = false;
}
},
});
return;
}
function sortStyleMap() {
const list = [];
let prevStyleId = 0;
@ -407,40 +428,60 @@ function initDocRootObserver() {
return true;
}
}
function sortStyleElements() {
let prev = document.body || document.head;
if (!prev) {
let expected = document.body || document.head;
if (!expected || sorting) {
return;
}
let appliedChanges = false;
for (const [idStr, el] of styleElements.entries()) {
if (!el.parentNode && disabledElements.has(getStyleId(idStr))) {
for (const el of styleElements.values()) {
if (!isMovable(el)) {
continue;
}
if (el.previousElementSibling === prev) {
prev = el;
continue;
let prev = el.previousElementSibling;
while (prev !== expected) {
if (prev && isSkippable(prev)) {
expected = prev;
prev = prev.nextElementSibling;
} else if (!moveAfter(el, expected)) {
return;
} else {
break;
}
if (!appliedChanges) {
}
expected = el;
}
if (sorting) {
sorting = false;
docRootObserver.takeRecords();
setTimeout(start);
//docRootObserver.start();
}
}
function isMovable(el) {
return el.parentNode || !disabledElements.has(getStyleId(el));
}
function isSkippable(el) {
return !ORDERED_TAGS.includes(el.localName) ||
el.id.startsWith(ID_PREFIX) &&
el.id.endsWith('-ghost') &&
el.localName === 'style' &&
el.className === 'stylus';
}
function moveAfter(el, expected) {
if (!sorting) {
if (restorationLimitExceeded()) {
return;
return false;
}
appliedChanges = true;
sorting = true;
docRootObserver.stop();
}
prev.insertAdjacentElement('afterend', el);
expected.insertAdjacentElement('afterend', el);
if (el.disabled !== disableAll) {
// moving an element resets its 'disabled' state
el.disabled = disableAll;
}
prev = el;
return true;
}
if (appliedChanges) {
docRootObserver.start();
}
}
function restorationLimitExceeded() {
const t = performance.now();
if (t - lastRestorationTime > 1000) {
@ -453,47 +494,5 @@ function initDocRootObserver() {
return true;
}
}
}
function getStyleId(el) {
return parseInt((el.id || el).substr(ID_PREFIX.length));
}
function orphanCheck() {
const port = chrome.runtime.connect();
if (port) {
port.disconnect();
return;
}
// we're orphaned due to an extension update
// we can detach the mutation observer
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.disconnect());
// we can detach event listeners
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
// we can't detach chrome.runtime.onMessage because it's no longer connected internally
// we can destroy our globals in this context to free up memory
[ // functions
'addStyleElement',
'applyOnMessage',
'applySections',
'applyStyles',
'applyStyleState',
'doDisableAll',
'initDocRewriteObserver',
'initDocRootObserver',
'orphanCheck',
'removeStyle',
'replaceAll',
'requestStyles',
// variables
'ROOT',
'disabledElements',
'retiredStyleTimers',
'styleElements',
'docRewriteObserver',
'docRootObserver',
].forEach(fn => (window[fn] = null));
}
})();

View File

@ -1,28 +1,283 @@
'use strict';
const FIREFOX = !chrome.app;
const VIVALDI = chrome.app && /Vivaldi/.test(navigator.userAgent);
const OPERA = chrome.app && /OPR/.test(navigator.userAgent);
(() => {
const FIREFOX = !chrome.app;
const VIVALDI = chrome.app && /Vivaldi/.test(navigator.userAgent);
const OPERA = chrome.app && /OPR/.test(navigator.userAgent);
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
['Update', 'Install'].forEach(type =>
['Update', 'Install'].forEach(type =>
['', 'Chrome', 'Opera'].forEach(browser =>
document.addEventListener('stylish' + type + browser, onClick)));
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
// orphaned content script check
if (msg.method === 'ping') {
sendResponse(true);
}
});
});
new MutationObserver((mutations, observer) => {
if (document.body) {
observer.disconnect();
// TODO: remove the following statement when USO pagination title is fixed
document.title = document.title.replace(/^(\d+)&\w+=/, '#$1: ');
chrome.runtime.sendMessage({
method: 'getStyles',
url: getMeta('stylish-id-url') || location.href
}, checkUpdatability);
}
}).observe(document.documentElement, {childList: true});
/* since we are using "stylish-code-chrome" meta key on all browsers and
US.o does not provide "advanced settings" on this url if browser is not Chrome,
we need to fix this URL using "stylish-update-url" meta key
*/
function getStyleURL() {
const textUrl = getMeta('stylish-update-url') || '';
const jsonUrl = getMeta('stylish-code-chrome') ||
textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json');
const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?');
return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : '');
}
function checkUpdatability([installedStyle]) {
// TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', {
detail: installedStyle && installedStyle.updateUrl,
}));
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 {
getStyleJson().then(json => {
reportUpdatable(!json ||
!styleSectionsEqual(json, installedStyle));
});
}
function reportUpdatable(isUpdatable) {
sendEvent(
isUpdatable
? 'styleCanBeUpdatedChrome'
: 'styleAlreadyInstalledChrome',
{
updateUrl: installedStyle.updateUrl
}
);
}
}
function sendEvent(type, detail = null) {
if (FIREFOX) {
type = type.replace('Chrome', '');
} else if (OPERA || VIVALDI) {
type = type.replace('Chrome', 'Opera');
}
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
}
onDOMready().then(() => {
document.dispatchEvent(new CustomEvent(type, detail));
});
}
function onClick(event) {
if (onClick.processing || !orphanCheck()) {
return;
}
onClick.processing = true;
(event.type.includes('Update') ? onUpdate() : onInstall())
.then(done, done);
function done() {
setTimeout(() => {
onClick.processing = false;
});
}
}
function onInstall() {
return getResource(getMeta('stylish-description'))
.then(name => saveStyleCode('styleInstall', name))
.then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
}
function onUpdate() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
method: 'getStyles',
url: getMeta('stylish-id-url') || location.href,
}, ([style]) => {
saveStyleCode('styleUpdate', style.name, {id: style.id})
.then(resolve, reject);
});
});
}
function saveStyleCode(message, name, addProps) {
return new Promise((resolve, reject) => {
const needsConfirmation = message === 'styleInstall' || !saveStyleCode.confirmed;
if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) {
reject();
return;
}
saveStyleCode.confirmed = true;
enableUpdateButton(false);
getStyleJson().then(json => {
if (!json) {
prompt(chrome.i18n.getMessage('styleInstallFailed', ''),
'https://github.com/openstyles/stylus/issues/195');
return;
}
chrome.runtime.sendMessage(
Object.assign(json, addProps, {
method: 'saveStyle',
reason: 'update',
}),
style => {
if (message === 'styleUpdate' && style.updateUrl.includes('?')) {
enableUpdateButton(true);
} else {
sendEvent('styleInstalledChrome');
}
}
);
resolve();
});
});
function enableUpdateButton(state) {
const important = s => s.replace(/;/g, '!important;');
const button = document.getElementById('update_style_button');
if (button) {
button.style.cssText = state ? '' : important('pointer-events: none; opacity: .35;');
const icon = button.querySelector('img[src*=".svg"]');
if (icon) {
icon.style.cssText = state ? '' : important('transition: transform 5s; transform: rotate(0);');
if (state) {
setTimeout(() => (icon.style.cssText += important('transform: rotate(10turn);')));
}
}
}
}
}
function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null;
}
function getResource(url) {
return new Promise(resolve => {
if (url.startsWith('#')) {
resolve(document.getElementById(url.slice(1)).textContent);
} else {
chrome.runtime.sendMessage({method: 'download', url}, resolve);
}
});
}
function getStyleJson() {
const url = getStyleURL();
return getResource(url).then(code => {
try {
return JSON.parse(code);
} catch (e) {
return fetch(url).then(r => r.json()).catch(() => null);
}
});
}
function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) {
return undefined;
}
if (a.length !== b.length) {
return false;
}
// order of sections should be identical to account for the case of multiple
// sections matching the same URL because the order of rules is part of cascading
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
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) {
return (
array1.every(el => array2.includes(el)) &&
array2.every(el => array1.includes(el))
);
}
}
function onDOMready() {
if (document.readyState !== 'loading') {
return Promise.resolve();
}
return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _);
resolve();
});
});
}
function orphanCheck() {
if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true;
}
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
['Update', 'Install'].forEach(type =>
['', 'Chrome', 'Opera'].forEach(browser =>
document.addEventListener('stylish' + type + browser, onClick)));
}
})();
// TODO: remove the following statement when USO is fixed
document.documentElement.appendChild(document.createElement('script')).text = '(' +
function () {
let settings;
const originalResponseJson = Response.prototype.json;
document.currentScript.remove();
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
document.removeEventListener('stylusFixBuggyUSOsettings', _);
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search);
@ -110,257 +365,3 @@ if (location.search.includes('category=')) {
}).observe(document, {childList: true, subtree: true});
});
}
new MutationObserver((mutations, observer) => {
if (document.body) {
observer.disconnect();
// TODO: remove the following statement when USO pagination title is fixed
document.title = document.title.replace(/^(\d+)&\w+=/, '#$1: ');
chrome.runtime.sendMessage({
method: 'getStyles',
url: getMeta('stylish-id-url') || location.href
}, checkUpdatability);
}
}).observe(document.documentElement, {childList: true});
/* since we are using "stylish-code-chrome" meta key on all browsers and
US.o does not provide "advanced settings" on this url if browser is not Chrome,
we need to fix this URL using "stylish-update-url" meta key
*/
function getStyleURL() {
const textUrl = getMeta('stylish-update-url') || '';
const jsonUrl = getMeta('stylish-code-chrome') ||
textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json');
const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?');
return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : '');
}
function checkUpdatability([installedStyle]) {
// TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', {
detail: installedStyle && installedStyle.updateUrl,
}));
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 {
getStyleJson().then(json => {
reportUpdatable(!json ||
!styleSectionsEqual(json, installedStyle));
});
}
function reportUpdatable(isUpdatable) {
sendEvent(
isUpdatable
? 'styleCanBeUpdatedChrome'
: 'styleAlreadyInstalledChrome',
{
updateUrl: installedStyle.updateUrl
}
);
}
}
function sendEvent(type, detail = null) {
if (FIREFOX) {
type = type.replace('Chrome', '');
} else if (OPERA || VIVALDI) {
type = type.replace('Chrome', 'Opera');
}
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
}
onDOMready().then(() => {
document.dispatchEvent(new CustomEvent(type, detail));
});
}
function onClick(event) {
if (onClick.processing) {
return;
}
onClick.processing = true;
(event.type.includes('Update') ? onUpdate() : onInstall())
.then(done, done);
function done() {
setTimeout(() => {
onClick.processing = false;
});
}
}
function onInstall() {
return getResource(getMeta('stylish-description'))
.then(name => saveStyleCode('styleInstall', name))
.then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
}
function onUpdate() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
method: 'getStyles',
url: getMeta('stylish-id-url') || location.href,
}, ([style]) => {
saveStyleCode('styleUpdate', style.name, {id: style.id})
.then(resolve, reject);
});
});
}
function saveStyleCode(message, name, addProps) {
return new Promise((resolve, reject) => {
const needsConfirmation = message === 'styleInstall' || !saveStyleCode.confirmed;
if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) {
reject();
return;
}
saveStyleCode.confirmed = true;
enableUpdateButton(false);
getStyleJson().then(json => {
if (!json) {
prompt(chrome.i18n.getMessage('styleInstallFailed', ''),
'https://github.com/openstyles/stylus/issues/195');
return;
}
chrome.runtime.sendMessage(
Object.assign(json, addProps, {
method: 'saveStyle',
reason: 'update',
}),
style => {
if (message === 'styleUpdate' && style.updateUrl.includes('?')) {
enableUpdateButton(true);
} else {
sendEvent('styleInstalledChrome');
}
}
);
resolve();
});
});
function enableUpdateButton(state) {
const important = s => s.replace(/;/g, '!important;');
const button = document.getElementById('update_style_button');
if (button) {
button.style.cssText = state ? '' : important('pointer-events: none; opacity: .35;');
const icon = button.querySelector('img[src*=".svg"]');
if (icon) {
icon.style.cssText = state ? '' : important('transition: transform 5s; transform: rotate(0);');
if (state) {
setTimeout(() => (icon.style.cssText += important('transform: rotate(10turn);')));
}
}
}
}
}
function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null;
}
function getResource(url) {
return new Promise(resolve => {
if (url.startsWith('#')) {
resolve(document.getElementById(url.slice(1)).textContent);
} else {
chrome.runtime.sendMessage({method: 'download', url}, resolve);
}
});
}
function getStyleJson() {
const url = getStyleURL();
return getResource(url).then(code => {
try {
return JSON.parse(code);
} catch (e) {
return fetch(url).then(r => r.json()).catch(() => null);
}
});
}
function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) {
return undefined;
}
if (a.length !== b.length) {
return false;
}
// order of sections should be identical to account for the case of multiple
// sections matching the same URL because the order of rules is part of cascading
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
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) {
return (
array1.every(el => array2.includes(el)) &&
array2.every(el => array1.includes(el))
);
}
}
function onDOMready() {
if (document.readyState !== 'loading') {
return Promise.resolve();
}
return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _);
resolve();
});
});
}
function orphanCheck() {
const port = chrome.runtime.connect();
if (port) {
port.disconnect();
return true;
}
// we're orphaned due to an extension update
// we can detach event listeners
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
['Update', 'Install'].forEach(type =>
['', 'Chrome', 'Opera'].forEach(browser =>
document.addEventListener('stylish' + type + browser, onClick)));
}