From 57e93e5429ca07fea63a80da8588b07e9077904d Mon Sep 17 00:00:00 2001 From: Brandon T Date: Mon, 15 Aug 2022 03:34:56 -0400 Subject: [PATCH] Refactor scripts for security reasons --- .../Handlers/BraveSearchScriptHandler.swift | 40 +- .../Handlers/BraveTalkScriptHandler.swift | 2 +- .../Browser/User Scripts/ScriptFactory.swift | 6 +- .../Frontend/Browser/UserScriptManager.swift | 14 +- .../PrintHandler.js | 6 +- .../UserScripts/BraveSearchHelper.js | 49 +- .../UserScripts/BraveTalkHelper.js | 43 +- .../Frontend/UserContent/UserScripts/DeAMP.js | 129 ++--- .../UserScripts/FarblingProtection.js | 456 +++++++++--------- .../UserScripts/RequestBlocking.js | 7 +- .../UserScripts/ResourceDownloader.js | 72 +-- .../UserScripts/WindowRenderHelper.js | 66 ++- 12 files changed, 411 insertions(+), 479 deletions(-) rename Client/Frontend/UserContent/UserScripts/AllFrames/{AtDocumentEnd => AtDocumentStart}/PrintHandler.js (91%) diff --git a/Client/Frontend/Browser/Handlers/BraveSearchScriptHandler.swift b/Client/Frontend/Browser/Handlers/BraveSearchScriptHandler.swift index 4b65e935391..c6d7c7f1014 100644 --- a/Client/Frontend/Browser/Handlers/BraveSearchScriptHandler.swift +++ b/Client/Frontend/Browser/Handlers/BraveSearchScriptHandler.swift @@ -32,7 +32,9 @@ class BraveSearchScriptHandler: TabContentScript { static func name() -> String { "BraveSearchHelper" } - func scriptMessageHandlerName() -> String? { BraveSearchScriptHandler.name() } + func scriptMessageHandlerName() -> String? { + "BraveSearchHelper_\(UserScriptManager.messageHandlerTokenString)" + } private enum Method: Int { case canSetBraveSearchAsDefault = 1 @@ -52,7 +54,6 @@ class BraveSearchScriptHandler: TabContentScript { didReceiveScriptMessage message: WKScriptMessage, replyHandler: (Any?, String?) -> Void ) { - defer { replyHandler(nil, nil) } let allowedHosts = DomainUserScript.braveSearchHelper.associatedDomains guard let requestHost = message.frameInfo.request.url?.host, @@ -60,6 +61,7 @@ class BraveSearchScriptHandler: TabContentScript { message.frameInfo.isMainFrame else { log.error("Backup search request called from disallowed host") + replyHandler(nil, nil) return } @@ -67,31 +69,31 @@ class BraveSearchScriptHandler: TabContentScript { let method = try? JSONDecoder().decode(MethodModel.self, from: data).methodId else { log.error("Failed to retrieve method id") + replyHandler(nil, nil) return } switch method { case Method.canSetBraveSearchAsDefault.rawValue: - handleCanSetBraveSearchAsDefault(methodId: method) + handleCanSetBraveSearchAsDefault(replyHandler: replyHandler) case Method.setBraveSearchDefault.rawValue: - handleSetBraveSearchDefault(methodId: method) + handleSetBraveSearchDefault(replyHandler: replyHandler) default: break } } - private func handleCanSetBraveSearchAsDefault(methodId: Int) { - + private func handleCanSetBraveSearchAsDefault(replyHandler: (Any?, String?) -> Void) { if PrivateBrowsingManager.shared.isPrivateBrowsing { log.debug("Private mode detected, skipping setting Brave Search as a default") - callback(methodId: methodId, result: false) + replyHandler(false, nil) return } let maximumPromptCount = Preferences.Search.braveSearchDefaultBrowserPromptCount if Self.canSetAsDefaultCounter >= maxCountOfDefaultBrowserPromptsPerSession || maximumPromptCount.value >= maxCountOfDefaultBrowserPromptsTotal { log.debug("Maximum number of tries of Brave Search website prompts reached") - callback(methodId: methodId, result: false) + replyHandler(false, nil) return } @@ -100,27 +102,11 @@ class BraveSearchScriptHandler: TabContentScript { let defaultEngine = profile.searchEngines.defaultEngine(forType: .standard).shortName let canSetAsDefault = defaultEngine != OpenSearchEngine.EngineNames.brave - - callback(methodId: methodId, result: canSetAsDefault) + replyHandler(canSetAsDefault, nil) } - private func handleSetBraveSearchDefault(methodId: Int) { + private func handleSetBraveSearchDefault(replyHandler: (Any?, String?) -> Void) { profile.searchEngines.updateDefaultEngine(OpenSearchEngine.EngineNames.brave, forType: .standard) - callback(methodId: methodId, result: nil) - } - - private func callback(methodId: Int, result: Bool?) { - let functionName = - "window.__firefox__.BSH\(UserScriptManager.messageHandlerTokenString).resolve" - - var args: [Any] = [methodId] - if let result = result { - args.append(result) - } - - self.tab?.webView?.evaluateSafeJavaScript( - functionName: functionName, - args: args, - contentWorld: .page) + replyHandler(nil, nil) } } diff --git a/Client/Frontend/Browser/Handlers/BraveTalkScriptHandler.swift b/Client/Frontend/Browser/Handlers/BraveTalkScriptHandler.swift index f868fb301f2..aeb89be0450 100644 --- a/Client/Frontend/Browser/Handlers/BraveTalkScriptHandler.swift +++ b/Client/Frontend/Browser/Handlers/BraveTalkScriptHandler.swift @@ -26,7 +26,7 @@ class BraveTalkScriptHandler: TabContentScript { static func name() -> String { "BraveTalkHelper" } - func scriptMessageHandlerName() -> String? { BraveTalkScriptHandler.name() } + func scriptMessageHandlerName() -> String? { "BraveTalkHelper_\(UserScriptManager.messageHandlerTokenString)" } func userContentController( _ userContentController: WKUserContentController, diff --git a/Client/Frontend/Browser/User Scripts/ScriptFactory.swift b/Client/Frontend/Browser/User Scripts/ScriptFactory.swift index a814356cce7..1c7ca589dcb 100644 --- a/Client/Frontend/Browser/User Scripts/ScriptFactory.swift +++ b/Client/Frontend/Browser/User Scripts/ScriptFactory.swift @@ -70,7 +70,7 @@ class ScriptFactory { case .farblingProtection(let etld): let randomConfiguration = RandomConfiguration(etld: etld) let fakeParams = try FarblingProtectionHelper.makeFarblingParams(from: randomConfiguration) - source = "\(source)\nwindow.braveFarble(\(fakeParams))\ndelete window.braveFarble" + source = source.replacingOccurrences(of: "$", with: fakeParams) case .nacl: // No modifications needed @@ -95,7 +95,7 @@ class ScriptFactory { case .braveSearchHelper: let securityToken = UserScriptManager.securityTokenString - let messageToken = "BSH\(UserScriptManager.messageHandlerTokenString)" + let messageToken = "BraveSearchHelper_\(UserScriptManager.messageHandlerTokenString)" source = source .replacingOccurrences(of: "$", with: messageToken, options: .literal) @@ -103,7 +103,7 @@ class ScriptFactory { case .braveTalkHelper: let securityToken = UserScriptManager.securityTokenString - let messageToken = "BT\(UserScriptManager.messageHandlerTokenString)" + let messageToken = "BraveTalkHelper_\(UserScriptManager.messageHandlerTokenString)" source = source .replacingOccurrences(of: "$", with: messageToken, options: .literal) diff --git a/Client/Frontend/Browser/UserScriptManager.swift b/Client/Frontend/Browser/UserScriptManager.swift index 293f81cbc37..7b090cfe35c 100644 --- a/Client/Frontend/Browser/UserScriptManager.swift +++ b/Client/Frontend/Browser/UserScriptManager.swift @@ -190,11 +190,7 @@ class UserScriptManager { return nil } - source = [ - source, - "window.braveBlockRequests(\(fakeParams))", - "delete window.braveBlockRequests" - ].joined(separator: "\n") + source = source.replacingOccurrences(of: "$", with: fakeParams) return WKUserScript.create( source: source, @@ -225,12 +221,8 @@ class UserScriptManager { assertionFailure("A nil here is impossible") return nil } - - source = [ - source, - "window.braveDeAmp(\(arguments))", - "delete window.braveDeAmp" - ].joined(separator: "\n") + + source = source.replacingOccurrences(of: "$", with: arguments, options: .literal) return WKUserScript.create( source: source, diff --git a/Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentEnd/PrintHandler.js b/Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentStart/PrintHandler.js similarity index 91% rename from Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentEnd/PrintHandler.js rename to Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentStart/PrintHandler.js index 192c9402096..f0c3e0d9e94 100644 --- a/Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentEnd/PrintHandler.js +++ b/Client/Frontend/UserContent/UserScripts/AllFrames/AtDocumentStart/PrintHandler.js @@ -8,7 +8,11 @@ // Ensure this module only gets included once. This is // required for user scripts injected into all frames. window.__firefox__.includeOnce("PrintHandler", function() { - window.print = function() { + function postMessage() { webkit.messageHandlers.printHandler.postMessage({"securitytoken": SECURITY_TOKEN}); + } + + window.print = function() { + postMessage(); }; }); diff --git a/Client/Frontend/UserContent/UserScripts/BraveSearchHelper.js b/Client/Frontend/UserContent/UserScripts/BraveSearchHelper.js index c7a8028f9c0..ebf05c34e69 100644 --- a/Client/Frontend/UserContent/UserScripts/BraveSearchHelper.js +++ b/Client/Frontend/UserContent/UserScripts/BraveSearchHelper.js @@ -5,45 +5,22 @@ 'use strict'; -Object.defineProperty(window.__firefox__, '$', { +window.__firefox__.includeOnce("BraveSearchHelper", function() { + function sendMessage(method_id) { + return webkit.messageHandlers.$.postMessage({ 'securitytoken': '$' ,'method_id': method_id}); + } + + Object.defineProperty(window, 'brave', { enumerable: false, configurable: true, writable: false, value: { - id: 1, - resolution_handlers: {}, - resolve(id, data, error) { - if (error && window.__firefox__.$.resolution_handlers[id].reject) { - window.__firefox__.$.resolution_handlers[id].reject(error); - } else if (window.__firefox__.$.resolution_handlers[id].resolve) { - window.__firefox__.$.resolution_handlers[id].resolve(data); - } else if (window.__firefox__.$.resolution_handlers[id].reject) { - window.__firefox__.$.resolution_handlers[id].reject(new Error("Invalid Data!")); - } else { - console.log("Invalid Promise ID: ", id); - } - - delete window.__firefox__.$.resolution_handlers[id]; - }, - sendMessage(method_id) { - return new Promise((resolve, reject) => { - window.__firefox__.$.resolution_handlers[method_id] = { resolve, reject }; - webkit.messageHandlers.BraveSearchHelper.postMessage({ 'securitytoken': '$' ,'method_id': method_id}); - }); - } - } -}); - -Object.defineProperty(window, 'brave', { - enumerable: false, - configurable: true, - writable: false, - value: { - getCanSetDefaultSearchProvider() { - return window.__firefox__.$.sendMessage(1); - }, - setIsDefaultSearchProvider() { - return window.__firefox__.$.sendMessage(2); - } + getCanSetDefaultSearchProvider() { + return sendMessage(1); + }, + setIsDefaultSearchProvider() { + return sendMessage(2); + } } + }); }); diff --git a/Client/Frontend/UserContent/UserScripts/BraveTalkHelper.js b/Client/Frontend/UserContent/UserScripts/BraveTalkHelper.js index a0a0799172b..fd6a948dcee 100644 --- a/Client/Frontend/UserContent/UserScripts/BraveTalkHelper.js +++ b/Client/Frontend/UserContent/UserScripts/BraveTalkHelper.js @@ -5,42 +5,19 @@ 'use strict'; -Object.defineProperty(window.__firefox__, '$', { +window.__firefox__.includeOnce("BraveTalkHelper", function() { + function sendMessage() { + return webkit.messageHandlers.$.postMessage({ 'securitytoken': '$' }); + } + + Object.defineProperty(window, 'chrome', { enumerable: false, configurable: true, writable: false, value: { - id: 1, - resolution_handlers: {}, - resolve(id, data, error) { - if (error && window.__firefox__.$.resolution_handlers[id].reject) { - window.__firefox__.$.resolution_handlers[id].reject(error); - } else if (window.__firefox__.$.resolution_handlers[id].resolve) { - window.__firefox__.$.resolution_handlers[id].resolve(data); - } else if (window.__firefox__.$.resolution_handlers[id].reject) { - window.__firefox__.$.resolution_handlers[id].reject(new Error("Invalid Data!")); - } else { - console.log("Invalid Promise ID: ", id); - } - - delete window.__firefox__.$.resolution_handlers[id]; - }, - sendMessage() { - return new Promise((resolve, reject) => { - window.__firefox__.$.resolution_handlers[1] = { resolve, reject }; - webkit.messageHandlers.BraveTalkHelper.postMessage({ 'securitytoken': '$' }); - }); - } + braveRequestAdsEnabled() { + return sendMessage(); + } } -}); - -Object.defineProperty(window, 'chrome', { -enumerable: false, -configurable: true, -writable: false, - value: { - braveRequestAdsEnabled() { - return window.__firefox__.$.sendMessage(); - } -} + }); }); diff --git a/Client/Frontend/UserContent/UserScripts/DeAMP.js b/Client/Frontend/UserContent/UserScripts/DeAMP.js index b35d4733f8d..6da7be5b5da 100644 --- a/Client/Frontend/UserContent/UserScripts/DeAMP.js +++ b/Client/Frontend/UserContent/UserScripts/DeAMP.js @@ -5,77 +5,78 @@ "use strict"; -window.braveDeAmp = (args) => { - const W = window - const D = W.document - const securityToken = args.securityToken - const messageHandler = webkit.messageHandlers[args.handlerName] - - let timesToCheck = 20 - let intervalId - - const sendMessage = (destURL) => { - return messageHandler.postMessage({ - securityToken: securityToken, - destURL: destURL.href - }) - } - - const checkIfShouldStopChecking = _ => { - timesToCheck -= 1 - if (timesToCheck === 0) { - W.clearInterval(intervalId) +window.__firefox__.includeOnce("DeAMP", function() { + (function(){ + const args = $; + const W = window; + const D = W.document; + const securityToken = args.securityToken; + const messageHandler = webkit.messageHandlers[args.handlerName]; + + let timesToCheck = 20; + let intervalId = 0; + + const sendMessage = (destURL) => { + return messageHandler.postMessage({ + securityToken: securityToken, + destURL: destURL.href + }); } - } - - const checkForAmp = _ => { - const htmlElm = document.documentElement - const headElm = document.head - if (!headElm && !htmlElm) { - // checked too early and page structure not available. - checkIfShouldStopChecking() - return + const checkIfShouldStopChecking = _ => { + timesToCheck -= 1; + if (timesToCheck === 0) { + W.clearInterval(intervalId); + } } - if (!htmlElm.hasAttribute('amp') && !htmlElm.hasAttribute('⚡')) { - // We know this isn't an amp document, and no point of checking further - W.clearInterval(intervalId) - return - } + const checkForAmp = _ => { + const htmlElm = document.documentElement; + const headElm = document.head; - const canonicalLinkElm = D.querySelector('head > link[rel="canonical"][href^="http"]') - if (canonicalLinkElm === null || canonicalLinkElm === undefined) { - // didn't find a link elm. - checkIfShouldStopChecking() - return - } + if (!headElm && !htmlElm) { + // checked too early and page structure not available. + checkIfShouldStopChecking(); + return; + } - const targetHref = canonicalLinkElm.getAttribute('href') - try { - const destUrl = new URL(targetHref) - W.clearInterval(intervalId) - - if (W.location.href == destUrl.href || !(destUrl.protocol === 'http:' || destUrl.protocol === 'https:')) { - // Only handle http/https and only if the canoncial url is different than the current url - // Also add a check the referrer to prevent an infinite load loop in some cases - return + if (!htmlElm.hasAttribute('amp') && !htmlElm.hasAttribute('⚡')) { + // We know this isn't an amp document, and no point of checking further + W.clearInterval(intervalId); + return; } - - sendMessage(destUrl).then(deAmp => { - if (deAmp) { - W.location.replace(destUrl.href) + + const canonicalLinkElm = D.querySelector('head > link[rel="canonical"][href^="http"]') + if (canonicalLinkElm === null || canonicalLinkElm === undefined) { + // didn't find a link elm. + checkIfShouldStopChecking(); + return; + } + + const targetHref = canonicalLinkElm.getAttribute('href'); + try { + const destUrl = new URL(targetHref); + W.clearInterval(intervalId); + + if (W.location.href == destUrl.href || !(destUrl.protocol === 'http:' || destUrl.protocol === 'https:')) { + // Only handle http/https and only if the canoncial url is different than the current url + // Also add a check the referrer to prevent an infinite load loop in some cases + return; } - }) - } catch (_) { - // Invalid canonical URL detected - W.clearInterval(intervalId) - return + + sendMessage(destUrl).then(deAmp => { + if (deAmp) { + W.location.replace(destUrl.href); + } + }) + } catch (_) { + // Invalid canonical URL detected + W.clearInterval(intervalId); + return; + } } - } - - intervalId = W.setInterval(checkForAmp, 250) - checkForAmp() -} -// Invoke the method and delete the function + intervalId = W.setInterval(checkForAmp, 250); + checkForAmp(); + })(); +}); diff --git a/Client/Frontend/UserContent/UserScripts/FarblingProtection.js b/Client/Frontend/UserContent/UserScripts/FarblingProtection.js index 82cf6786260..54234fb17ec 100644 --- a/Client/Frontend/UserContent/UserScripts/FarblingProtection.js +++ b/Client/Frontend/UserContent/UserScripts/FarblingProtection.js @@ -5,265 +5,267 @@ "use strict"; -window.braveFarble = (args) => { - // 1. Farble audio - // Adds slight randization when reading data for audio files - // Randomization is determined by the fudge factor - const farbleAudio = (fudgeFactor) => { +window.__firefox__.includeOnce("FarblingProtection", function() { + (function() { + const args = $; const braveNacl = window.nacl - delete window.nacl + + // 1. Farble audio + // Adds slight randization when reading data for audio files + // Randomization is determined by the fudge factor + const farbleAudio = (fudgeFactor) => { + delete window.nacl + + const farbleArrayData = (destination) => { + // Let's fudge the data by our fudge factor. + for (const index in destination) { + destination[index] = destination[index] * fudgeFactor + } + } - const farbleArrayData = (destination) => { - // Let's fudge the data by our fudge factor. - for (const index in destination) { - destination[index] = destination[index] * fudgeFactor + // Convert an unsinged byte (Uint8) to a hex character + // Unsigned bytes must be between 0 and 255 + const byteToHex = (unsignedByte) => { + // convert the possibly signed byte (-128 to 127) to an unsigned byte (0 to 255). + // if you know, that you only deal with unsigned bytes (Uint8Array), you can omit this line + // const unsignedByte = byte & 0xff + + // If the number can be represented with only 4 bits (0-15), + // the hexadecimal representation of this number is only one char (0-9, a-f). + if (unsignedByte < 16) { + return '0' + unsignedByte.toString(16) + } else { + return unsignedByte.toString(16) + } } - } - // Convert an unsinged byte (Uint8) to a hex character - // Unsigned bytes must be between 0 and 255 - const byteToHex = (unsignedByte) => { - // convert the possibly signed byte (-128 to 127) to an unsigned byte (0 to 255). - // if you know, that you only deal with unsigned bytes (Uint8Array), you can omit this line - // const unsignedByte = byte & 0xff - - // If the number can be represented with only 4 bits (0-15), - // the hexadecimal representation of this number is only one char (0-9, a-f). - if (unsignedByte < 16) { - return '0' + unsignedByte.toString(16) - } else { - return unsignedByte.toString(16) + // Convert an array of unsigned bytes (Uint8Array) to a hex string. + // Each value in the array must be between 0 and 255, + // resulting in hex values between 0 to f (i.e. 0 to 15) + const toHexString = (unsignedBytes) => { + return Array.from(unsignedBytes) + .map(byte => byteToHex(byte)) + .join('') } - } - // Convert an array of unsigned bytes (Uint8Array) to a hex string. - // Each value in the array must be between 0 and 255, - // resulting in hex values between 0 to f (i.e. 0 to 15) - const toHexString = (unsignedBytes) => { - return Array.from(unsignedBytes) - .map(byte => byteToHex(byte)) - .join('') - } + // Hash an array + const hashArray = (a) => { + const byteArray = new Uint8Array(a.buffer) + const hexArray = braveNacl.hash(byteArray) + return toHexString(hexArray) + } - // Hash an array - const hashArray = (a) => { - const byteArray = new Uint8Array(a.buffer) - const hexArray = braveNacl.hash(byteArray) - return toHexString(hexArray) - } + // 1. Farble `getChannelData` + // This will also result in a farbled `copyFromChannel` + const getChannelData = window.AudioBuffer.prototype.getChannelData + window.AudioBuffer.prototype.getChannelData = function () { + const channelData = Reflect.apply(getChannelData, this, arguments) + // TODO: @JS Add more optimized audio farbling. + // Will be done as a future PR of #5482 here: + // https://github.com/brave/brave-ios/pull/5485 + return channelData + } - // 1. Farble `getChannelData` - // This will also result in a farbled `copyFromChannel` - const getChannelData = window.AudioBuffer.prototype.getChannelData - window.AudioBuffer.prototype.getChannelData = function () { - const channelData = Reflect.apply(getChannelData, this, arguments) - // TODO: @JS Add more optimized audio farbling. - // Will be done as a future PR of #5482 here: - // https://github.com/brave/brave-ios/pull/5485 - return channelData + // 2. Farble "destination" methods + const structuresToFarble = [ + [window.AnalyserNode, 'getFloatFrequencyData'], + [window.AnalyserNode, 'getByteFrequencyData'], + [window.AnalyserNode, 'getByteTimeDomainData'], + [window.AnalyserNode, 'getFloatTimeDomainData'] + ] + + for (const [structure, methodName] of structuresToFarble) { + const origImplementation = structure.prototype[methodName] + structure.prototype[methodName] = function () { + Reflect.apply(origImplementation, this, arguments) + farbleArrayData(arguments[0]) + } + } } - // 2. Farble "destination" methods - const structuresToFarble = [ - [window.AnalyserNode, 'getFloatFrequencyData'], - [window.AnalyserNode, 'getByteFrequencyData'], - [window.AnalyserNode, 'getByteTimeDomainData'], - [window.AnalyserNode, 'getFloatTimeDomainData'] - ] - - for (const [structure, methodName] of structuresToFarble) { - const origImplementation = structure.prototype[methodName] - structure.prototype[methodName] = function () { - Reflect.apply(origImplementation, this, arguments) - farbleArrayData(arguments[0]) + // 2. Farble plugin data + // Injects fake plugins with fake mime-types + // Random plugins are determined by the plugin data + const farblePlugins = (pluginData) => { + // Function that create a fake mime-type based on the given fake data + const makeFakeMimeType = (fakeData) => { + return Object.create(window.MimeType.prototype, { + suffixes: { value: fakeData.suffixes }, + type: { value: fakeData.type }, + description: { value: fakeData.description } + }) } - } - } - - // 2. Farble plugin data - // Injects fake plugins with fake mime-types - // Random plugins are determined by the plugin data - const farblePlugins = (pluginData) => { - // Function that create a fake mime-type based on the given fake data - const makeFakeMimeType = (fakeData) => { - return Object.create(window.MimeType.prototype, { - suffixes: { value: fakeData.suffixes }, - type: { value: fakeData.type }, - description: { value: fakeData.description } - }) - } - // Create a fake plugin given the plugin data - const makeFakePlugin = (pluginData) => { - const newPlugin = Object.create(window.Plugin.prototype, { - description: { value: pluginData.description }, - name: { value: pluginData.name }, - filename: { value: pluginData.filename }, - length: { value: pluginData.mimeTypes.length } - }) + // Create a fake plugin given the plugin data + const makeFakePlugin = (pluginData) => { + const newPlugin = Object.create(window.Plugin.prototype, { + description: { value: pluginData.description }, + name: { value: pluginData.name }, + filename: { value: pluginData.filename }, + length: { value: pluginData.mimeTypes.length } + }) - // Create mime-types and link them to the new plugin - for (const [index, mimeType] of pluginData.mimeTypes.entries()) { - const newMimeType = makeFakeMimeType(mimeType) + // Create mime-types and link them to the new plugin + for (const [index, mimeType] of pluginData.mimeTypes.entries()) { + const newMimeType = makeFakeMimeType(mimeType) - newPlugin[index] = newMimeType - newPlugin[newMimeType.type] = newMimeType + newPlugin[index] = newMimeType + newPlugin[newMimeType.type] = newMimeType - Reflect.defineProperty(newMimeType, 'enabledPlugin', { - value: newPlugin - }) - } + Reflect.defineProperty(newMimeType, 'enabledPlugin', { + value: newPlugin + }) + } - // Patch `Plugin.item(index)` function to return the correct item otherwise it - // throws a `TypeError: Can only call Plugin.item on instances of Plugin` - newPlugin.item = function (index) { - return newPlugin[index] + // Patch `Plugin.item(index)` function to return the correct item otherwise it + // throws a `TypeError: Can only call Plugin.item on instances of Plugin` + newPlugin.item = function (index) { + return newPlugin[index] + } + + return newPlugin } - - return newPlugin - } - if (window.navigator.plugins !== undefined) { - // We need the original length so we can reference it (as we will change it) - const plugins = window.navigator.plugins - const originalPluginsLength = plugins.length + if (window.navigator.plugins !== undefined) { + // We need the original length so we can reference it (as we will change it) + const plugins = window.navigator.plugins + const originalPluginsLength = plugins.length - // Adds a fake plugin for the given index on fakePluginData - const addPluginAtIndex = (newPlugin, index) => { - const pluginPosition = originalPluginsLength + index - window.navigator.plugins[pluginPosition] = newPlugin - window.navigator.plugins[newPlugin.name] = newPlugin - } + // Adds a fake plugin for the given index on fakePluginData + const addPluginAtIndex = (newPlugin, index) => { + const pluginPosition = originalPluginsLength + index + window.navigator.plugins[pluginPosition] = newPlugin + window.navigator.plugins[newPlugin.name] = newPlugin + } - for (const [index, pluginData] of fakePluginData.entries()) { - const newPlugin = makeFakePlugin(pluginData) - addPluginAtIndex(newPlugin, index) - } + for (const [index, pluginData] of fakePluginData.entries()) { + const newPlugin = makeFakePlugin(pluginData) + addPluginAtIndex(newPlugin, index) + } - // Adjust the length of the original plugin array - Reflect.defineProperty(window.navigator.plugins, 'length', { - value: originalPluginsLength + fakePluginData.length - }) + // Adjust the length of the original plugin array + Reflect.defineProperty(window.navigator.plugins, 'length', { + value: originalPluginsLength + fakePluginData.length + }) - // Patch `PluginArray.item(index)` function to return the correct item - // otherwise it returns `undefined` - const originalItemFunction = plugins.item - window.PluginArray.prototype.item = function (index) { - if (index < originalPluginsLength) { - return Reflect.apply(originalItemFunction, plugins, arguments) - } else { - return plugins[index] + // Patch `PluginArray.item(index)` function to return the correct item + // otherwise it returns `undefined` + const originalItemFunction = plugins.item + window.PluginArray.prototype.item = function (index) { + if (index < originalPluginsLength) { + return Reflect.apply(originalItemFunction, plugins, arguments) + } else { + return plugins[index] + } } } } - } - - // 3. Farble speech synthesizer - // Adds a vake voice determined by the fakeVoiceName and randomVoiceIndexScale. - const farbleVoices = (fakeVoiceName, randomVoiceIndexScale) => { - const makeFakeVoiceFromVoice = (voice) => { - const newVoice = Object.create(Object.getPrototypeOf(voice), { - name: { value: fakeVoiceName }, - voiceURI: { value: voice.voiceURI }, - lang: { value: voice.lang }, - localService: { value: voice.localService }, - default: { value: false } - }) - return newVoice - } + // 3. Farble speech synthesizer + // Adds a vake voice determined by the fakeVoiceName and randomVoiceIndexScale. + const farbleVoices = (fakeVoiceName, randomVoiceIndexScale) => { + const makeFakeVoiceFromVoice = (voice) => { + const newVoice = Object.create(Object.getPrototypeOf(voice), { + name: { value: fakeVoiceName }, + voiceURI: { value: voice.voiceURI }, + lang: { value: voice.lang }, + localService: { value: voice.localService }, + default: { value: false } + }) - let originalVoice - let fakeVoice - let passedFakeVoice - - // We need to override the voice property to allow our fake voice to work - const descriptor = Reflect.getOwnPropertyDescriptor(SpeechSynthesisUtterance.prototype, 'voice') - Reflect.defineProperty(SpeechSynthesisUtterance.prototype, 'voice', { - get () { - if (!passedFakeVoice) { - // We didn't set a fake voice - return Reflect.apply(descriptor.get, this, arguments) - } else { - // We set a fake voice, return that instead - return passedFakeVoice - } - }, - set (passedVoice) { - if (passedVoice === fakeVoice && originalVoice !== undefined) { - // If we passed a fake voice, ignore it. We need to use the real voice - // The fake voice will not work. - passedFakeVoice = passedVoice - Reflect.apply(descriptor.set, this, [originalVoice]) - } else { - // Otherwise, if we set a real voice, use a real voice instead. - passedFakeVoice = undefined - Reflect.apply(descriptor.set, this, arguments) - } + return newVoice } - }) - // Patch get voices to return an extra fake voice - const getVoices = window.speechSynthesis.getVoices - const getVoicesPrototype = Object.getPrototypeOf(window.speechSynthesis) - getVoicesPrototype.getVoices = function () { - const voices = Reflect.apply(getVoices, this, arguments) + let originalVoice + let fakeVoice + let passedFakeVoice + + // We need to override the voice property to allow our fake voice to work + const descriptor = Reflect.getOwnPropertyDescriptor(SpeechSynthesisUtterance.prototype, 'voice') + Reflect.defineProperty(SpeechSynthesisUtterance.prototype, 'voice', { + get () { + if (!passedFakeVoice) { + // We didn't set a fake voice + return Reflect.apply(descriptor.get, this, arguments) + } else { + // We set a fake voice, return that instead + return passedFakeVoice + } + }, + set (passedVoice) { + if (passedVoice === fakeVoice && originalVoice !== undefined) { + // If we passed a fake voice, ignore it. We need to use the real voice + // The fake voice will not work. + passedFakeVoice = passedVoice + Reflect.apply(descriptor.set, this, [originalVoice]) + } else { + // Otherwise, if we set a real voice, use a real voice instead. + passedFakeVoice = undefined + Reflect.apply(descriptor.set, this, arguments) + } + } + }) + + // Patch get voices to return an extra fake voice + const getVoices = window.speechSynthesis.getVoices + const getVoicesPrototype = Object.getPrototypeOf(window.speechSynthesis) + getVoicesPrototype.getVoices = function () { + const voices = Reflect.apply(getVoices, this, arguments) - if (fakeVoice === undefined) { - const randomVoiceIndex = Math.round(randomVoiceIndexScale * voices.length) - originalVoice = voices[randomVoiceIndex] - fakeVoice = makeFakeVoiceFromVoice(originalVoice) + if (fakeVoice === undefined) { + const randomVoiceIndex = Math.round(randomVoiceIndexScale * voices.length) + originalVoice = voices[randomVoiceIndex] + fakeVoice = makeFakeVoiceFromVoice(originalVoice) - if (fakeVoice !== undefined) { + if (fakeVoice !== undefined) { + voices.push(fakeVoice) + } + } else { voices.push(fakeVoice) } - } else { - voices.push(fakeVoice) + return voices } - return voices } - } - - // 4. Farble hardwareConcurrency - // Adds a random value between 2 and the original hardware concurrency - // using the provided `randomHardwareIndexScale` which must be a random value between 0 and 1. - const farbleHardwareConcurrency = (randomHardwareIndexScale) => { - const hardwareConcurrency = window.navigator.hardwareConcurrency - // We only farble amounts greater than 2 - if (hardwareConcurrency <= 2) { return } - const remaining = hardwareConcurrency - 2 - const newRemaining = Math.round(remaining * randomHardwareIndexScale) - - Reflect.defineProperty(window.navigator, 'hardwareConcurrency', { - value: newRemaining + 2 - }) - } - - // A value between 0.99 and 1 to fudge the audio data - // A value between 0.99 to 1 means the values in the destination will - // always be within the expected range of -1 and 1. - // This small decrease should not affect affect legitimite users of this api. - // But will affect fingerprinters by introducing a small random change. - const fudgeFactor = args['fudgeFactor'] - farbleAudio(fudgeFactor) - - // Fake data that is to be used to construct fake plugins - const fakePluginData = args['fakePluginData'] - farblePlugins(fakePluginData) - - // A value representing a fake voice name that will be used to add a fake voice - const fakeVoiceName = args['fakeVoiceName'] - // This value is used to get a random index between 0 and voices.length - // It's important to have a value between 0 - 1 in order to be within the - // array bounds - const randomVoiceIndexScale = args['randomVoiceIndexScale'] - farbleVoices(fakeVoiceName, randomVoiceIndexScale) - - // This value lets us pick a value between 2 and window.navigator.hardwareConcurrency - // It is a value between 0 and 1. For example 0.5 will give us 3 and - // thus return 2 + 3 = 5 for hardware concurrency - const randomHardwareIndexScale = args['randomHardwareIndexScale'] - farbleHardwareConcurrency(randomHardwareIndexScale) -} - -// Invoke window.braveFarble then delete the function + + // 4. Farble hardwareConcurrency + // Adds a random value between 2 and the original hardware concurrency + // using the provided `randomHardwareIndexScale` which must be a random value between 0 and 1. + const farbleHardwareConcurrency = (randomHardwareIndexScale) => { + const hardwareConcurrency = window.navigator.hardwareConcurrency + // We only farble amounts greater than 2 + if (hardwareConcurrency <= 2) { return } + const remaining = hardwareConcurrency - 2 + const newRemaining = Math.round(remaining * randomHardwareIndexScale) + + Reflect.defineProperty(window.navigator, 'hardwareConcurrency', { + value: newRemaining + 2 + }) + } + + // A value between 0.99 and 1 to fudge the audio data + // A value between 0.99 to 1 means the values in the destination will + // always be within the expected range of -1 and 1. + // This small decrease should not affect affect legitimite users of this api. + // But will affect fingerprinters by introducing a small random change. + const fudgeFactor = args['fudgeFactor'] + farbleAudio(fudgeFactor) + + // Fake data that is to be used to construct fake plugins + const fakePluginData = args['fakePluginData'] + farblePlugins(fakePluginData) + + // A value representing a fake voice name that will be used to add a fake voice + const fakeVoiceName = args['fakeVoiceName'] + // This value is used to get a random index between 0 and voices.length + // It's important to have a value between 0 - 1 in order to be within the + // array bounds + const randomVoiceIndexScale = args['randomVoiceIndexScale'] + farbleVoices(fakeVoiceName, randomVoiceIndexScale) + + // This value lets us pick a value between 2 and window.navigator.hardwareConcurrency + // It is a value between 0 and 1. For example 0.5 will give us 3 and + // thus return 2 + 3 = 5 for hardware concurrency + const randomHardwareIndexScale = args['randomHardwareIndexScale'] + farbleHardwareConcurrency(randomHardwareIndexScale) + })(); +}); diff --git a/Client/Frontend/UserContent/UserScripts/RequestBlocking.js b/Client/Frontend/UserContent/UserScripts/RequestBlocking.js index ba22a5e6964..552c0eb113a 100644 --- a/Client/Frontend/UserContent/UserScripts/RequestBlocking.js +++ b/Client/Frontend/UserContent/UserScripts/RequestBlocking.js @@ -3,8 +3,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -window.braveBlockRequests = (args) => { +(function() { 'use strict' + const args = $; const messageHandler = webkit.messageHandlers[args.handlerName] const securityToken = args.securityToken const sendMessage = (resourceURL) => { @@ -79,6 +80,4 @@ window.braveBlockRequests = (args) => { } }) } -} - -// Invoke window.braveBlockRequests then delete the function +})(); diff --git a/Client/Frontend/UserContent/UserScripts/ResourceDownloader.js b/Client/Frontend/UserContent/UserScripts/ResourceDownloader.js index bf981f7357e..9f60c8323df 100644 --- a/Client/Frontend/UserContent/UserScripts/ResourceDownloader.js +++ b/Client/Frontend/UserContent/UserScripts/ResourceDownloader.js @@ -5,43 +5,45 @@ "use strict"; -Object.defineProperty(window, "$", { - value: {} -}) +window.__firefox__.includeOnce("DownloadManager", function() { + Object.defineProperty(window, "$", { + value: {} + }) -Object.defineProperty($, "postMessage", { - value: function (msg) { - if (msg) { - webkit.messageHandlers.$.postMessage(msg); - } - } -}) + Object.defineProperty($, "postMessage", { + value: function (msg) { + if (msg) { + webkit.messageHandlers.$.postMessage(msg); + } + } + }) -Object.defineProperty($, "download", { - value: function (link) { - var xhr = new XMLHttpRequest(); - xhr.responseType = "arraybuffer"; - xhr.onreadystatechange = function() { - if (this.readyState == XMLHttpRequest.DONE) { - if (this.status == 200) { - var byteArray = new Uint8Array(this.response); - var binaryString = new Array(byteArray.length); + Object.defineProperty($, "download", { + value: function (link) { + var xhr = new XMLHttpRequest(); + xhr.responseType = "arraybuffer"; + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (this.status == 200) { + var byteArray = new Uint8Array(this.response); + var binaryString = new Array(byteArray.length); - for (var i = 0; i < byteArray.length; ++i) { - binaryString[i] = String.fromCharCode(byteArray[i]); - } + for (var i = 0; i < byteArray.length; ++i) { + binaryString[i] = String.fromCharCode(byteArray[i]); + } - var data = binaryString.join(''); - var base64 = window.btoa(data); + var data = binaryString.join(''); + var base64 = window.btoa(data); - $.postMessage({ "statusCode": this.status, "base64Data": base64 }); - } - else { - $.postMessage({ "statusCode": this.status, "base64Data": "" }); - } - } - }; - xhr.open("GET", link, true); - xhr.send(null); - } -}) + $.postMessage({ "statusCode": this.status, "base64Data": base64 }); + } + else { + $.postMessage({ "statusCode": this.status, "base64Data": "" }); + } + } + }; + xhr.open("GET", link, true); + xhr.send(null); + } + }) +}); diff --git a/Client/Frontend/UserContent/UserScripts/WindowRenderHelper.js b/Client/Frontend/UserContent/UserScripts/WindowRenderHelper.js index 2cf4c9ae3dd..a2852ad0a33 100644 --- a/Client/Frontend/UserContent/UserScripts/WindowRenderHelper.js +++ b/Client/Frontend/UserContent/UserScripts/WindowRenderHelper.js @@ -3,44 +3,36 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -Object.defineProperty(window, "$", { - value: {} -}) +window.__firefox__.includeOnce("WindowRenderHelper", function() { + Object.defineProperty(window, "$", { + value: {} + }) -Object.defineProperty($, "postMessage", { - value: function (msg) { - if (msg) { - webkit.messageHandlers.$.postMessage(msg); - } - } -}) + Object.defineProperty($, "resizeWindow", { + value: function () { + var evt = document.createEvent('UIEvents'); + evt.initUIEvent('resize', true, false, window, 0); + window.dispatchEvent(evt); + } + }) -Object.defineProperty($, "resizeWindow", { - value: function () { - var evt = document.createEvent('UIEvents'); - evt.initUIEvent('resize', true, false, window, 0); - window.dispatchEvent(evt); - } -}) + Object.defineProperty($, "addDocumentStateListener", { + value: function () { + document.addEventListener('readystatechange', event => { + if (event.target.readyState === "interactive") { + //Used for debugging in Safari development tools to know what the state of the page is. + //Not needed while in use because we only care about JSON messages and not state. + $.resizeWindow(); + } -Object.defineProperty($, "addDocumentStateListener", { - value: function () { - document.addEventListener('readystatechange', event => { - if (event.target.readyState === "interactive") { - //Used for debugging in Safari development tools to know what the state of the page is. - //Not needed while in use because we only care about JSON messages and not state. - //postMessage(event.target.readyState); - $.resizeWindow(); - } + if (event.target.readyState === "complete") { + //Used for debugging in Safari development tools to know what the state of the page is. + //Not needed while in use because we only care about JSON messages and not state. + $.resizeWindow(); + } + }); + } + }) - if (event.target.readyState === "complete") { - //Used for debugging in Safari development tools to know what the state of the page is. - //Not needed while in use because we only care about JSON messages and not state. - //postMessage(event.target.readyState); - $.resizeWindow(); - } - }); - } -}) - -$.addDocumentStateListener() + $.addDocumentStateListener() +});