123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019-2022 Alessio Vanni
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- 'use strict';
- // Injected into content pages
- (function () {
- // https://github.com/chrisaljoudi/uBlock/issues/464
- // https://github.com/gorhill/uMatrix/issues/621
- if (document instanceof HTMLDocument === false
- && document instanceof XMLDocument === false) {
- return;
- }
- // This can also happen (for example if script injected into a
- // `data:` URI doc)
- if (!window.location) {
- return;
- }
- // This can happen
- if (typeof vAPI !== 'object') {
- //console.debug('contentscript.js > vAPI not found');
- return;
- }
- // https://github.com/chrisaljoudi/uBlock/issues/456
- // Already injected?
- if (vAPI.contentscriptEndInjected) {
- //console.debug('contentscript.js > content script already injected');
- return;
- }
- vAPI.contentscriptEndInjected = true;
- // Executed only once.
- (function () {
- let localStorageHandler = function (mustRemove) {
- if (mustRemove) {
- window.localStorage.clear();
- window.sessionStorage.clear();
- }
- };
- // Check with extension whether local storage must be emptied
- // rhill 2014-03-28: we need an exception handler in case
- // 3rd-party access to site data is disabled.
- // https://github.com/gorhill/httpswitchboard/issues/215
- try {
- let hasLocalStorage =
- window.localStorage && window.localStorage.length !== 0;
- let hasSessionStorage =
- window.sessionStorage && window.sessionStorage.length !== 0;
- if (hasLocalStorage || hasSessionStorage) {
- vAPI.messaging.send('contentscript.js', {
- what: 'contentScriptHasLocalStorage',
- originURL: window.location.origin
- }, localStorageHandler);
- }
- // TODO: indexedDB
- //if ( window.indexedDB && !!window.indexedDB.webkitGetDatabaseNames ) {
- // var db = window.indexedDB.webkitGetDatabaseNames().onsuccess = function(sender) {
- // console.debug('webkitGetDatabaseNames(): result=%o', sender.target.result);
- // };
- //}
- // TODO: Web SQL
- // if ( window.openDatabase ) {
- // Sad:
- // "There is no way to enumerate or delete the databases available for an origin from this API."
- // Ref.: http://www.w3.org/TR/webdatabase/#databases
- // }
- } catch (e) {
- }
- })();
- // https://github.com/gorhill/uMatrix/issues/45
- let collapser = (function () {
- let resquestIdGenerator = 1;
- let processTimer;
- let toProcess = [];
- let toFilter = [];
- let toCollapse = new Map();
- let cachedBlockedMap;
- let cachedBlockedMapHash;
- let cachedBlockedMapTimer;
- let reURLPlaceholder = /\{\{url\}\}/g;
- let src1stProps = {
- embed: 'src',
- iframe: 'src',
- img: 'src',
- object: 'data',
- };
- let src2ndProps = {
- img: 'srcset',
- };
- let tagToTypeMap = {
- embed: 'media',
- iframe: 'frame',
- img: 'image',
- object: 'media',
- };
- let cachedBlockedSetClear = function () {
- cachedBlockedMap =
- cachedBlockedMapHash =
- cachedBlockedMapTimer = undefined;
- };
- // https://github.com/chrisaljoudi/uBlock/issues/174
- // Do not remove fragment from src URL
- let onProcessed = function (response) {
- if (!response) { // This happens if uBO is disabled or restarted.
- toCollapse.clear();
- return;
- }
- let targets = toCollapse.get(response.id);
- if (targets === undefined) {
- return;
- }
- toCollapse.delete(response.id);
- if (cachedBlockedMapHash !== response.hash) {
- cachedBlockedMap = new Map(response.blockedResources);
- cachedBlockedMapHash = response.hash;
- if (cachedBlockedMapTimer !== undefined) {
- clearTimeout(cachedBlockedMapTimer);
- }
- cachedBlockedMapTimer =
- vAPI.setTimeout(cachedBlockedSetClear, 30000);
- }
- if (cachedBlockedMap === undefined || cachedBlockedMap.size === 0) {
- return;
- }
- let placeholders = response.placeholders;
- for (let target of targets) {
- let tag = target.localName;
- let prop = src1stProps[tag];
- if (prop === undefined) {
- continue;
- }
- let src = target[prop];
- if (typeof src !== 'string' || src.length === 0) {
- prop = src2ndProps[tag];
- if (prop === undefined) {
- continue;
- }
- src = target[prop];
- if (typeof src !== 'string' || src.length === 0) {
- continue;
- }
- }
- let collapsed = cachedBlockedMap.get(tagToTypeMap[tag]
- + ' '
- + src);
- if (collapsed === undefined) {
- continue;
- }
- if (collapsed) {
- target.style.setProperty('display', 'none', 'important');
- target.hidden = true;
- continue;
- }
- switch (tag) {
- case 'iframe':
- if (placeholders.frame !== true) {
- break;
- }
- let docurl = 'data:text/html,'
- + encodeURIComponent(placeholders
- .frameDocument
- .replace(reURLPlaceholder, src));
- let replaced = false;
- // Using contentWindow.location prevent tainting browser
- // history -- i.e. breaking back button (seen on Chromium).
- if (target.contentWindow) {
- try {
- target.contentWindow.location.replace(docurl);
- replaced = true;
- } catch(ex) {
- }
- }
- if (!replaced) {
- target.setAttribute('src', docurl);
- }
- break;
- case 'img':
- if (placeholders.image !== true) {
- break;
- }
- target.style.setProperty('display', 'inline-block');
- target.style.setProperty('min-width', '20px', 'important');
- target.style.setProperty('min-height', '20px', 'important');
- target.style.setProperty('border', placeholders.imageBorder,
- 'important');
- target.style.setProperty('background',
- placeholders.imageBackground,
- 'important');
- break;
- }
- }
- };
- let send = function () {
- processTimer = undefined;
- toCollapse.set(resquestIdGenerator, toProcess);
- let msg = {
- what: 'lookupBlockedCollapsibles',
- id: resquestIdGenerator,
- toFilter: toFilter,
- hash: cachedBlockedMapHash
- };
- vAPI.messaging.send('contentscript.js', msg, onProcessed);
- toProcess = [];
- toFilter = [];
- resquestIdGenerator += 1;
- };
- let process = function (delay) {
- if (toProcess.length === 0) {
- return;
- }
- if (delay === 0) {
- if (processTimer !== undefined) {
- clearTimeout(processTimer);
- }
- send();
- } else if (processTimer === undefined) {
- processTimer = vAPI.setTimeout(send, delay || 47);
- }
- };
- let add = function (target) {
- toProcess.push(target);
- };
- let addMany = function (targets) {
- for (let i=targets.length-1; i>=0; --i) {
- toProcess.push(targets[i]);
- }
- };
- let iframeSourceModified = function (mutations) {
- for (let i=mutations.length-1; i>=0; --i) {
- addIFrame(mutations[i].target, true);
- }
- process();
- };
- let iframeSourceObserver;
- let iframeSourceObserverOptions = {
- attributes: true,
- attributeFilter: [ 'src' ]
- };
- let addIFrame = function (iframe, dontObserve) {
- // https://github.com/gorhill/uBlock/issues/162
- // Be prepared to deal with possible change of src attribute.
- if (dontObserve !== true) {
- if (iframeSourceObserver === undefined) {
- iframeSourceObserver =
- new MutationObserver(iframeSourceModified);
- }
- iframeSourceObserver.observe(iframe,
- iframeSourceObserverOptions);
- }
- let src = iframe.src;
- if (src === '' || typeof src !== 'string') {
- return;
- }
- if (src.startsWith('http') === false) {
- return;
- }
- toFilter.push({
- type: 'frame',
- url: iframe.src,
- });
- add(iframe);
- };
- let addIFrames = function (iframes) {
- for (let i=iframes.length-1; i>=0; --i) {
- addIFrame(iframes[i]);
- }
- };
- let addNodeList = function (nodeList) {
- for (let i=nodeList.length-1; i>=0; --i) {
- let node = nodeList[i];
- if (node.nodeType !== 1) {
- continue;
- }
- if (node.localName === 'iframe') {
- addIFrame(node);
- }
- if (node.childElementCount !== 0) {
- addIFrames(node.querySelectorAll('iframe'));
- }
- }
- };
- let onResourceFailed = function (ev) {
- if (tagToTypeMap[ev.target.localName] !== undefined) {
- add(ev.target);
- process();
- }
- };
- document.addEventListener('error', onResourceFailed, true);
- vAPI.shutdown.add(function () {
- document.removeEventListener('error', onResourceFailed, true);
- if (iframeSourceObserver !== undefined) {
- iframeSourceObserver.disconnect();
- iframeSourceObserver = undefined;
- }
- if (processTimer !== undefined) {
- clearTimeout(processTimer);
- processTimer = undefined;
- }
- });
- return {
- addMany: addMany,
- addIFrames: addIFrames,
- addNodeList: addNodeList,
- process: process
- };
- })();
- // Observe changes in the DOM
- // Added node lists will be cumulated here before being processed
- (function () {
- // This fixes http://acid3.acidtests.org/
- if (!document.body) {
- return;
- }
- let addedNodeLists = [];
- let addedNodeListsTimer;
- let treeMutationObservedHandler = function () {
- addedNodeListsTimer = undefined;
- for (let i=addedNodeLists.length-1; i>=0; --i) {
- collapser.addNodeList(addedNodeLists[i]);
- }
- collapser.process();
- addedNodeLists = [];
- };
- // https://github.com/gorhill/uBlock/issues/205
- // Do not handle added node directly from within mutation observer.
- let treeMutationObservedHandlerAsync = function (mutations) {
- for (let i=mutations.length-1; i>=0; --i) {
- let nodeList = mutations[i].addedNodes;
- if (nodeList.length !== 0) {
- addedNodeLists.push(nodeList);
- }
- }
- if (addedNodeListsTimer === undefined) {
- addedNodeListsTimer =
- vAPI.setTimeout(treeMutationObservedHandler, 47);
- }
- };
- // https://github.com/gorhill/httpswitchboard/issues/176
- let treeObserver =
- new MutationObserver(treeMutationObservedHandlerAsync);
- treeObserver.observe(document.body, {
- childList: true,
- subtree: true
- });
- vAPI.shutdown.add(function () {
- if (addedNodeListsTimer !== undefined) {
- clearTimeout(addedNodeListsTimer);
- addedNodeListsTimer = undefined;
- }
- if (treeObserver !== null) {
- treeObserver.disconnect();
- treeObserver = undefined;
- }
- addedNodeLists = [];
- });
- })();
- // Executed only once.
- //
- // https://github.com/gorhill/httpswitchboard/issues/25
- //
- // https://github.com/gorhill/httpswitchboard/issues/131
- // Looks for inline javascript also in at least one a[href] element.
- //
- // https://github.com/gorhill/uMatrix/issues/485
- // Mind "on..." attributes.
- //
- // https://github.com/gorhill/uMatrix/issues/924
- // Report inline styles.
- (function () {
- if (document.querySelector('script:not([src])') !== null
- || document.querySelector('a[href^="javascript:"]') !== null
- || document.querySelector('[onabort],[onblur],[oncancel],'
- + '[oncanplay],[oncanplaythrough],'
- + '[onchange],[onclick],[onclose],'
- + '[oncontextmenu],[oncuechange],'
- + '[ondblclick],[ondrag],[ondragend],'
- + '[ondragenter],[ondragexit],'
- + '[ondragleave],[ondragover],'
- + '[ondragstart],[ondrop],'
- + '[ondurationchange],[onemptied],'
- + '[onended],[onerror],[onfocus],'
- + '[oninput],[oninvalid],[onkeydown],'
- + '[onkeypress],[onkeyup],[onload],'
- + '[onloadeddata],[onloadedmetadata],'
- + '[onloadstart],[onmousedown],'
- + '[onmouseenter],[onmouseleave],'
- + '[onmousemove],[onmouseout],'
- + '[onmouseover],[onmouseup],[onwheel],'
- + '[onpause],[onplay],[onplaying],'
- + '[onprogress],[onratechange],[onreset],'
- + '[onresize],[onscroll],[onseeked],'
- + '[onseeking],[onselect],[onshow],'
- + '[onstalled],[onsubmit],[onsuspend],'
- + '[ontimeupdate],[ontoggle],'
- + '[onvolumechange],[onwaiting],'
- + '[onafterprint],[onbeforeprint],'
- + '[onbeforeunload],[onhashchange],'
- + '[onlanguagechange],[onmessage],'
- + '[onoffline],[ononline],[onpagehide],'
- + '[onpageshow],[onrejectionhandled],'
- + '[onpopstate],[onstorage],'
- + '[onunhandledrejection],[onunload],'
- + '[oncopy],[oncut],[onpaste]') !== null) {
- vAPI.messaging.send('contentscript.js', {
- what: 'securityPolicyViolation',
- directive: 'script-src',
- documentURI: window.location.href
- });
- }
- if (document.querySelector('style,[style]') !== null) {
- vAPI.messaging.send('contentscript.js', {
- what: 'securityPolicyViolation',
- directive: 'style-src',
- documentURI: window.location.href
- });
- }
- collapser.addMany(document.querySelectorAll('img'));
- collapser.addIFrames(document.querySelectorAll('iframe'));
- collapser.process();
- })();
- // Executed only once.
- // https://github.com/gorhill/uMatrix/issues/232
- // Force `display` property, Firefox is still affected by the issue.
- (function () {
- let noscripts = document.querySelectorAll('noscript');
- if (noscripts.length === 0) {
- return;
- }
- let redirectTimer;
- let reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i;
- let reSafeURL = /^https?:\/\//;
- let autoRefresh = function (root) {
- let meta =
- root.querySelector('meta[http-equiv="refresh"][content]');
- if (meta === null) {
- return;
- }
- let match = reMetaContent.exec(meta.getAttribute('content'));
- if (match === null || match[3].trim() === '') {
- return;
- }
- let url = new URL(match[3], document.baseURI);
- if (reSafeURL.test(url.href) === false) {
- return;
- }
- redirectTimer = setTimeout(function () {
- location.assign(url.href);
- }, parseInt(match[1], 10) * 1000 + 1);
- meta.parentNode.removeChild(meta);
- };
- let morphNoscript = function (from) {
- if (/^application\/(?:xhtml\+)?xml/.test(document.contentType)) {
- let to = document.createElement('span');
- while (from.firstChild !== null) {
- to.appendChild(from.firstChild);
- }
- return to;
- }
- let parser = new DOMParser();
- let doc =
- parser.parseFromString('<span>' + from.textContent + '</span>',
- 'text/html');
- return document.adoptNode(doc.querySelector('span'));
- };
- let renderNoscriptTags = function (response) {
- if (response !== true) {
- return;
- }
- for (let noscript of noscripts) {
- let parent = noscript.parentNode;
- if (parent === null) {
- continue;
- }
- let span = morphNoscript(noscript);
- span.style.setProperty('display', 'inline', 'important');
- if (redirectTimer === undefined) {
- autoRefresh(span);
- }
- parent.replaceChild(span, noscript);
- }
- };
- vAPI.messaging.send('contentscript.js', {
- what: 'mustRenderNoscriptTags?'
- }, renderNoscriptTags);
- })();
- vAPI.messaging.send('contentscript.js', {
- what: 'shutdown?'
- }, function (response) {
- if (response === true) {
- vAPI.shutdown.exec();
- }
- });
- })();
|