/* global RX_META URLS download openURL */// toolbox.js /* global addAPI bgReady */// common.js /* global tabMan */// msg.js 'use strict'; bgReady.all.then(() => { const installCodeCache = {}; addAPI(/** @namespace API */ { usercss: { getInstallCode(url) { // when the installer tab is reloaded after the cache is expired, this will throw intentionally const {code, timer} = installCodeCache[url]; clearInstallCode(url); clearTimeout(timer); return code; }, }, }); // `glob`: pathname match pattern for webRequest // `rx`: pathname regex to verify the URL really looks like a raw usercss const maybeDistro = { // https://github.com/StylishThemes/GitHub-Dark/raw/master/github-dark.user.css 'github.com': { glob: '/*/raw/*', rx: /^\/[^/]+\/[^/]+\/raw\/[^/]+\/[^/]+?\.user\.(css|styl)$/, }, // https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.user.css 'raw.githubusercontent.com': { glob: '/*', rx: /^(\/[^/]+?){4}\.user\.(css|styl)$/, }, }; chrome.webRequest.onBeforeSendHeaders.addListener(maybeInstallFromDistro, { urls: [ URLS.usoArchiveRaw + 'usercss/*.user.css', '*://greasyfork.org/scripts/*/code/*.user.css', '*://sleazyfork.org/scripts/*/code/*.user.css', ...[].concat( ...Object.entries(maybeDistro) .map(([host, {glob}]) => makeUsercssGlobs(host, glob))), ], types: ['main_frame'], }, ['blocking']); chrome.webRequest.onHeadersReceived.addListener(rememberContentType, { urls: makeUsercssGlobs('*', '/*'), types: ['main_frame'], }, ['responseHeaders']); tabMan.onUpdate(maybeInstall); function clearInstallCode(url) { return delete installCodeCache[url]; } /** Sites may be using custom types like text/stylus so this coarse filter only excludes html */ function isContentTypeText(type) { return /^text\/(?!html)/i.test(type); } // in Firefox we have to use a content script to read file:// async function loadFromFile(tabId) { return (await browser.tabs.executeScript(tabId, {file: '/content/install-hook-usercss.js'}))[0]; } async function loadFromUrl(tabId, url) { return ( url.startsWith('file:') || tabMan.get(tabId, isContentTypeText.name) || isContentTypeText((await fetch(url, {method: 'HEAD'})).headers.get('content-type')) ) && download(url); } function makeUsercssGlobs(host, path) { return '%css,%css?*,%styl,%styl?*'.replace(/%/g, `*://${host}${path}.user.`).split(','); } async function maybeInstall({tabId, url, oldUrl = ''}) { if (url.includes('.user.') && /^(https?|file|ftps?):/.test(url) && /\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]) && !oldUrl.startsWith(URLS.installUsercss)) { const inTab = url.startsWith('file:') && !chrome.app; const code = await (inTab ? loadFromFile : loadFromUrl)(tabId, url); if (!/^\s* openURL({ url: `${newUrl}&tabId=${tabId}`, active: tab.active, index: tab.index + 1, openerTabId: tabId, currentWindow: null, })); } else { const timer = setTimeout(clearInstallCode, 10e3, url); installCodeCache[url] = {code, timer}; chrome.tabs.update(tabId, {url: newUrl}); } } /** Remember Content-Type to avoid wasting time to re-fetch in loadFromUrl **/ function rememberContentType({tabId, responseHeaders}) { const h = responseHeaders.find(h => h.name.toLowerCase() === 'content-type'); tabMan.set(tabId, isContentTypeText.name, h && isContentTypeText(h.value) || undefined); } });