123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019 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://libregit.org/heckyel/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- /* global HTMLDocument, XMLDocument */
- '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() {
- var 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 {
- var hasLocalStorage =
- window.localStorage && window.localStorage.length !== 0;
- var 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
- var collapser = (function() {
- var resquestIdGenerator = 1,
- processTimer,
- toProcess = [],
- toFilter = [],
- toCollapse = new Map(),
- cachedBlockedMap,
- cachedBlockedMapHash,
- cachedBlockedMapTimer,
- reURLPlaceholder = /\{\{url\}\}/g;
- var src1stProps = {
- 'embed': 'src',
- 'iframe': 'src',
- 'img': 'src',
- 'object': 'data'
- };
- var src2ndProps = {
- 'img': 'srcset'
- };
- var tagToTypeMap = {
- embed: 'media',
- iframe: 'frame',
- img: 'image',
- object: 'media'
- };
- var cachedBlockedSetClear = function() {
- cachedBlockedMap =
- cachedBlockedMapHash =
- cachedBlockedMapTimer = undefined;
- };
- // https://github.com/chrisaljoudi/uBlock/issues/174
- // Do not remove fragment from src URL
- var onProcessed = function(response) {
- if ( !response ) { // This happens if uBO is disabled or restarted.
- toCollapse.clear();
- return;
- }
- var 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;
- }
- var placeholders = response.placeholders,
- tag, prop, src, collapsed, docurl, replaced;
- for ( var target of targets ) {
- tag = target.localName;
- prop = src1stProps[tag];
- if ( prop === undefined ) { continue; }
- 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; }
- }
- 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; }
- docurl =
- 'data:text/html,' +
- encodeURIComponent(
- placeholders.frameDocument.replace(
- reURLPlaceholder,
- src
- )
- );
- 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;
- }
- }
- };
- var send = function() {
- processTimer = undefined;
- toCollapse.set(resquestIdGenerator, toProcess);
- var msg = {
- what: 'lookupBlockedCollapsibles',
- id: resquestIdGenerator,
- toFilter: toFilter,
- hash: cachedBlockedMapHash
- };
- vAPI.messaging.send('contentscript.js', msg, onProcessed);
- toProcess = [];
- toFilter = [];
- resquestIdGenerator += 1;
- };
- var 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);
- }
- };
- var add = function(target) {
- toProcess.push(target);
- };
- var addMany = function(targets) {
- var i = targets.length;
- while ( i-- ) {
- toProcess.push(targets[i]);
- }
- };
- var iframeSourceModified = function(mutations) {
- var i = mutations.length;
- while ( i-- ) {
- addIFrame(mutations[i].target, true);
- }
- process();
- };
- var iframeSourceObserver;
- var iframeSourceObserverOptions = {
- attributes: true,
- attributeFilter: [ 'src' ]
- };
- var 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);
- }
- var src = iframe.src;
- if ( src === '' || typeof src !== 'string' ) { return; }
- if ( src.startsWith('http') === false ) { return; }
- toFilter.push({ type: 'frame', url: iframe.src });
- add(iframe);
- };
- var addIFrames = function(iframes) {
- var i = iframes.length;
- while ( i-- ) {
- addIFrame(iframes[i]);
- }
- };
- var addNodeList = function(nodeList) {
- var node,
- i = nodeList.length;
- while ( i-- ) {
- node = nodeList[i];
- if ( node.nodeType !== 1 ) { continue; }
- if ( node.localName === 'iframe' ) {
- addIFrame(node);
- }
- if ( node.childElementCount !== 0 ) {
- addIFrames(node.querySelectorAll('iframe'));
- }
- }
- };
- var 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; }
- var addedNodeLists = [];
- var addedNodeListsTimer;
- var treeMutationObservedHandler = function() {
- addedNodeListsTimer = undefined;
- var i = addedNodeLists.length;
- while ( 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.
- var treeMutationObservedHandlerAsync = function(mutations) {
- var iMutation = mutations.length,
- nodeList;
- while ( iMutation-- ) {
- nodeList = mutations[iMutation].addedNodes;
- if ( nodeList.length !== 0 ) {
- addedNodeLists.push(nodeList);
- }
- }
- if ( addedNodeListsTimer === undefined ) {
- addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 47);
- }
- };
- // https://github.com/gorhill/httpswitchboard/issues/176
- var 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() {
- var noscripts = document.querySelectorAll('noscript');
- if ( noscripts.length === 0 ) { return; }
- var redirectTimer,
- reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i,
- reSafeURL = /^https?:\/\//;
- var autoRefresh = function(root) {
- var meta = root.querySelector('meta[http-equiv="refresh"][content]');
- if ( meta === null ) { return; }
- var match = reMetaContent.exec(meta.getAttribute('content'));
- if ( match === null || match[3].trim() === '' ) { return; }
- var 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);
- };
- var morphNoscript = function(from) {
- if ( /^application\/(?:xhtml\+)?xml/.test(document.contentType) ) {
- var to = document.createElement('span');
- while ( from.firstChild !== null ) {
- to.appendChild(from.firstChild);
- }
- return to;
- }
- var parser = new DOMParser();
- var doc = parser.parseFromString(
- '<span>' + from.textContent + '</span>',
- 'text/html'
- );
- return document.adoptNode(doc.querySelector('span'));
- };
- var renderNoscriptTags = function(response) {
- if ( response !== true ) { return; }
- var parent, span;
- for ( var noscript of noscripts ) {
- parent = noscript.parentNode;
- if ( parent === null ) { continue; }
- 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();
- }
- }
- );
- /******************************************************************************/
- /******************************************************************************/
- })();
|