| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- 'use strict';
- (() => {
- 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);
- ['Update', 'Install'].forEach(type =>
- ['', 'Chrome', 'Opera'].forEach(browser =>
- document.addEventListener('stylish' + type + browser, onClick)));
- 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',
- md5Url: getMeta('stylish-md5-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',
- md5Url: getMeta('stylish-md5-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 isNew = message === 'styleInstall';
- const needsConfirmation = isNew || !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: isNew ? 'install' : 'update',
- }),
- style => {
- if (!isNew && 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', _);
- // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
- settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search.replace(/^\?/, ''));
- if (!settings) {
- Response.prototype.json = originalResponseJson;
- }
- });
- Response.prototype.json = function (...args) {
- return originalResponseJson.call(this, ...args).then(json => {
- if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') {
- return json;
- }
- Response.prototype.json = originalResponseJson;
- const images = new Map();
- for (const jsonSetting of json.style_settings) {
- let value = settings.get('ik-' + jsonSetting.install_key);
- if (!value
- || !jsonSetting.style_setting_options
- || !jsonSetting.style_setting_options[0]) {
- continue;
- }
- if (value.startsWith('ik-')) {
- value = value.replace(/^ik-/, '');
- const defaultItem = jsonSetting.style_setting_options.find(item => item.default);
- if (!defaultItem || defaultItem.install_key !== value) {
- if (defaultItem) {
- defaultItem.default = false;
- }
- jsonSetting.style_setting_options.some(item => {
- if (item.install_key === value) {
- item.default = true;
- return true;
- }
- });
- }
- } else if (jsonSetting.setting_type === 'image') {
- jsonSetting.style_setting_options.some(item => {
- if (item.default) {
- item.default = false;
- return true;
- }
- });
- images.set(jsonSetting.install_key, value);
- } else {
- const item = jsonSetting.style_setting_options[0];
- if (item.value !== value && item.install_key === 'placeholder') {
- item.value = value;
- }
- }
- }
- if (images.size) {
- new MutationObserver((_, observer) => {
- if (!document.getElementById('style-settings')) {
- return;
- }
- observer.disconnect();
- for (const [name, url] of images.entries()) {
- const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
- const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
- if (elUrl) {
- elUrl.value = url;
- }
- }
- }).observe(document, {childList: true, subtree: true});
- }
- return json;
- });
- };
- } + ')()';
- // TODO: remove the following statement when USO pagination is fixed
- if (location.search.includes('category=')) {
- document.addEventListener('DOMContentLoaded', function _() {
- document.removeEventListener('DOMContentLoaded', _);
- new MutationObserver((_, observer) => {
- if (!document.getElementById('pagination')) {
- return;
- }
- observer.disconnect();
- const category = '&' + location.search.match(/category=[^&]+/)[0];
- const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])');
- for (let i = 0; i < links.length; i++) {
- links[i].href += category;
- }
- }).observe(document, {childList: true, subtree: true});
- });
- }
|