diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 5f618a3..d1a9079 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -22,7 +22,13 @@ "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], - "js": ["/js/vapi-client.js", "/js/contentscript.js"], + "js": ["/js/vapi-client.js", "/js/contentscript-start.js"], + "run_at": "document_start", + "all_frames": true + }, + { + "matches": ["http://*/*", "https://*/*"], + "js": ["/js/contentscript.js"], "run_at": "document_end", "all_frames": true } diff --git a/platform/webext/manifest.json b/platform/webext/manifest.json index 798859f..09f4da3 100644 --- a/platform/webext/manifest.json +++ b/platform/webext/manifest.json @@ -18,11 +18,17 @@ "default_popup": "popup.html" }, "content_scripts": [ - { - "matches": ["http://*/*", "https://*/*"], - "js": ["/js/vapi-client.js", "/js/contentscript.js"], - "run_at": "document_end", - "all_frames": true + { + "matches": ["http://*/*", "https://*/*"], + "js": ["/js/vapi-client.js", "/js/contentscript-start.js"], + "run_at": "document_start", + "all_frames": true + }, + { + "matches": ["http://*/*", "https://*/*"], + "js": ["/js/contentscript.js"], + "run_at": "document_end", + "all_frames": true } ], "default_locale": "en", diff --git a/src/css/popup.css b/src/css/popup.css index cd33a61..bcd7542 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -128,6 +128,12 @@ body .toolbar button.fa { opacity: 1; stroke: none; } +#mtxSwitches > li.relevant > svg .dot { + fill: #888; + } +#mtxSwitches > li.switchTrue.relevant > svg .dot { + fill: #eee; + } #mtxSwitches > li > svg .off, #mtxSwitches > li.switchTrue > svg .on, #mtxSwitches > li.relevant > svg .dot { diff --git a/src/js/background.js b/src/js/background.js index 1a81b3b..774f772 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -110,6 +110,7 @@ return { }, clearBrowserCacheCycle: 0, + cspNoWorkerSrc: undefined, updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond, firstUpdateAfter: 11 * oneMinute, nextUpdateAfter: 11 * oneHour, diff --git a/src/js/messaging.js b/src/js/messaging.js index 5f30192..c77e129 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -167,6 +167,7 @@ var matrixSnapshot = function(pageStore, details) { has3pReferrer: pageStore.has3pReferrer, hasMixedContent: pageStore.hasMixedContent, hasNoscriptTags: pageStore.hasNoscriptTags, + hasWebWorkers: pageStore.hasWebWorkers, headerIndices: Array.from(headerIndices), hostname: pageStore.pageHostname, mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime, @@ -541,6 +542,24 @@ var onMessage = function(request, sender, callback) { } break; + case 'securityPolicyViolation': + if ( request.policy !== µm.cspNoWorkerSrc ) { break; } + if ( pageStore !== null ) { + pageStore.hasWebWorkers = true; + pageStore.recordRequest('script', request.url, true); + } + if ( tabContext !== null ) { + µm.logger.writeOne( + tabId, + 'net', + tabContext.rootHostname, + request.url, + 'worker', + true + ); + } + break; + case 'shutdown?': if ( tabContext !== null ) { response = µm.tMatrix.evaluateSwitchZ('matrix-off', tabContext.rootHostname); diff --git a/src/js/pagestats.js b/src/js/pagestats.js index 65aaac8..9f0baa5 100644 --- a/src/js/pagestats.js +++ b/src/js/pagestats.js @@ -126,6 +126,7 @@ PageStore.prototype = { this.has3pReferrer = false; this.hasMixedContent = false; this.hasNoscriptTags = false; + this.hasWebWorkers = false; this.incinerationTimer = null; this.mtxContentModifiedTime = 0; this.mtxCountModifiedTime = 0; diff --git a/src/js/popup.js b/src/js/popup.js index aac39a6..c9a8e27 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1196,6 +1196,10 @@ function updateMatrixSwitches() { 'relevant', matrixSnapshot.hasMixedContent ); + uDom.nodeFromId('mtxSwitch_no-workers').classList.toggle( + 'relevant', + matrixSnapshot.hasWebWorkers + ); uDom.nodeFromId('mtxSwitch_referrer-spoof').classList.toggle( 'relevant', matrixSnapshot.has3pReferrer diff --git a/src/js/traffic.js b/src/js/traffic.js index d77e10e..79be08e 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -299,24 +299,44 @@ var onHeadersReceived = function(details) { var tabContext = µm.tabContextManager.lookup(tabId); if ( tabContext === null ) { return; } - if ( µm.mustAllow(tabContext.rootHostname, µm.URI.hostnameFromURI(requestURL), 'script') ) { - return; + var csp = []; + + if ( + µm.mustAllow( + tabContext.rootHostname, + µm.URI.hostnameFromURI(requestURL), + 'script' + ) !== true + ) { + csp.push("script-src 'unsafe-eval' blob: *"); } + if ( µm.cspNoWorkerSrc === undefined ) { + µm.cspNoWorkerSrc = vAPI.webextFlavor.startsWith('Mozilla-') ? + "child-src 'none'; frame-src data: blob: *" : + "worker-src 'none'" ; + } + + if ( µm.tMatrix.evaluateSwitchZ('no-workers', tabContext.rootHostname) ) { + csp.push(µm.cspNoWorkerSrc); + } + + if ( csp.length === 0 ) { return; } + // If javascript is not allowed, say so through a `Content-Security-Policy` // directive. // We block only inline-script tags, all the external javascript will be // blocked by our request handler. - var csp = "script-src 'unsafe-eval' blob: *", + var cspDirectives = csp.join(','), headers = details.responseHeaders, i = headerIndexFromName('content-security-policy', headers); // A CSP header is already present: just add our own directive as a // separate disposition (i.e. use comma). if ( i !== -1 ) { - headers[i].value += ', ' + csp; + headers[i].value += ', ' + cspDirectives; } else { - headers.push({ name: 'Content-Security-Policy', value: csp }); + headers.push({ name: 'Content-Security-Policy', value: cspDirectives }); } if ( requestType === 'doc' ) { diff --git a/src/popup.html b/src/popup.html index 1d1fd65..0a0810a 100644 --- a/src/popup.html +++ b/src/popup.html @@ -87,6 +87,7 @@