From 5283bce4a598565bc18384848e21fb1bd34ff6c9 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Mon, 16 Nov 2020 09:19:32 -0600 Subject: [PATCH 01/17] Inject JS overrides --- Core/FingerprintUserScript.swift | 37 ++++++ Core/fingerprint.js | 187 +++++++++++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 16 +++ DuckDuckGo/TabViewController.swift | 2 + 4 files changed, 242 insertions(+) create mode 100644 Core/FingerprintUserScript.swift create mode 100644 Core/fingerprint.js diff --git a/Core/FingerprintUserScript.swift b/Core/FingerprintUserScript.swift new file mode 100644 index 0000000000..c00c5e6266 --- /dev/null +++ b/Core/FingerprintUserScript.swift @@ -0,0 +1,37 @@ +// +// FingerprintUserScript.swift +// DuckDuckGo +// +// Copyright © 2020 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import WebKit + +public class FingerprintUserScript: NSObject, UserScript { + public var source: String { + return loadJS("fingerprint") + } + + public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart + + public var forMainFrameOnly: Bool = false + + public var messageNames: [String] = [] + + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + + } +} diff --git a/Core/fingerprint.js b/Core/fingerprint.js new file mode 100644 index 0000000000..8fd6d54628 --- /dev/null +++ b/Core/fingerprint.js @@ -0,0 +1,187 @@ +// +// fingerprint.js +// DuckDuckGo +// +// Copyright © 2017 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +(function protect () { + // Property values to be set and their original values. + const fingerprintPropertyValues = { + 'screen': { + 'availTop': { + 'object': 'screen', + 'origValue': screen.availTop, + 'targetValue': 0 + }, + 'availLeft': { + 'object': 'screen', + 'origValue': screen.availLeft, + 'targetValue': 0 + }, + 'availWidth': { + 'object': 'screen', + 'origValue': screen.availWidth, + 'targetValue': screen.width + }, + 'availHeight': { + 'object': 'screen', + 'origValue': screen.availHeight, + 'targetValue': screen.height + }, + 'screenY': { + 'object': 'window', + 'origValue': window.screenY, + 'targetValue': 0 + }, + 'screenLeft': { + 'object': 'window', + 'origValue': window.screenLeft, + 'targetValue': 0 + }, + 'colorDepth': { + 'object': 'screen', + 'origValue': screen.colorDepth, + 'targetValue': 24 + }, + 'pixelDepth': { + 'object': 'screen', + 'origValue': screen.pixelDepth, + 'targetValue': 24 + } + }, + 'storage': { + 'webkitTemporaryStorage': { + 'object': 'navigator', + 'origValue': navigator.webkitTemporaryStorage, + 'targetValue': undefined + }, + 'webkitPersistentStorage': { + 'object': 'navigator', + 'origValue': navigator.webkitPersistentStorage, + 'targetValue': undefined + } + }, + 'options': { + 'doNotTrack': { + 'object': 'navigator', + 'origValue': navigator.doNotTrack, + 'targetValue': false + } + } + } + + /* + * Return device specific battery value that prevents fingerprinting. + * On Desktop/Laptop - fully charged and plugged in. + * On Mobile, should not plugged in with random battery values every load. + * Property event functions are also defined, for setting later. + */ + function getBattery () { + let battery = {} + battery.value = { + charging: true, + chargingTime: 0, + dischargingTime: Infinity, + level: 1 + } + battery.properties = ['onchargingchange', 'onchargingtimechange', 'ondischargingtimechange', 'onlevelchange'] + return battery + } + + /** + * For each property defined on the object, update it with the target value. + */ + function buildScriptProperties () { + let script = '' + for (const category in fingerprintPropertyValues) { + for (const [name, prop] of Object.entries(fingerprintPropertyValues[category])) { + // Don't update if existing value is undefined or null + if (!(prop.origValue === undefined)) { + script += `Object.defineProperty(${prop.object}, "${name}", { value: ${prop.targetValue} });\n` + } + } + } + return script + } + + /** + * Build a script that overwrites the Battery API if present in the browser. + * It will return the values defined in the getBattery function to the client, + * as well as prevent any script from listening to events. + */ + function buildBatteryScript () { + if (navigator.getBattery) { + const battery = getBattery() + let batteryScript = ` + navigator.getBattery = function getBattery () { + let battery = ${JSON.stringify(battery.value)} + ` + for (const prop of battery.properties) { + // Prevent setting events via event handlers + batteryScript += ` + Object.defineProperty(battery, '${prop}', { + enumerable: true, + configurable: false, + writable: false, + value: undefined + }) + ` + } + + // Wrap event listener functions so handlers aren't added + for (const handler of ['addEventListener']) { + batteryScript += ` + battery.${handler} = function ${handler} () { + return + } + ` + } + batteryScript += ` + return Promise.resolve(battery) + } + ` + return batteryScript + } else { + return '' + } + } + + /** + * All the steps for building the injection script. Should only be done at initial page load. + */ + function buildInjectionScript () { + let script = buildScriptProperties() + script += buildBatteryScript() + return script + } + + /** + * Inject all the overwrites into the page. + */ + function inject (scriptToInject, removeAfterExec) { + // Inject into main page + let e = document.createElement('script') + e.textContent = scriptToInject; + (document.head || document.documentElement).appendChild(e) + + if (removeAfterExec) { + e.remove() + } + } + + const injectionScript = buildInjectionScript() + inject(injectionScript) +})() diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1413a15f03..285abad57e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 02CA904924F6BFE700D41DDF /* navigatorsharepatch.js in Resources */ = {isa = PBXBuildFile; fileRef = 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */; }; 02CA904B24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */; }; 02CA904D24FD2DB000D41DDF /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; }; + 02DC6A52255D8C0A00B03BC2 /* fingerprint.js in Resources */ = {isa = PBXBuildFile; fileRef = 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */; }; + 02DC6A64255D8C5000B03BC2 /* FingerprintUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */; }; 0A6CC0EF23904D5400E4F627 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0A6CC0EE23904D5400E4F627 /* Settings.bundle */; }; 1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */; }; 1CB7B82323CEA28300AA24EA /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */; }; @@ -700,6 +702,8 @@ 02CA904824F6BFE700D41DDF /* navigatorsharepatch.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = navigatorsharepatch.js; sourceTree = ""; }; 02CA904A24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSharePatchUserScript.swift; sourceTree = ""; }; 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockingRulesTests.swift; sourceTree = ""; }; + 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fingerprint.js; sourceTree = ""; }; + 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintUserScript.swift; sourceTree = ""; }; 0A6CC0EE23904D5400E4F627 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; @@ -2093,6 +2097,14 @@ name = ContentBlockerRules; sourceTree = ""; }; + 02DC6A50255D8BE700B03BC2 /* fingerprinting */ = { + isa = PBXGroup; + children = ( + 02DC6A51255D8C0A00B03BC2 /* fingerprint.js */, + ); + name = fingerprinting; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -3280,6 +3292,7 @@ 836A941C247F23C600BF8EF5 /* UserAgentManager.swift */, 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */, 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */, + 02DC6A63255D8C5000B03BC2 /* FingerprintUserScript.swift */, ); name = Web; sourceTree = ""; @@ -3425,6 +3438,7 @@ F18608DE1E5E648100361C30 /* Javascript */ = { isa = PBXGroup; children = ( + 02DC6A50255D8BE700B03BC2 /* fingerprinting */, 835750931F8E9A610059E07B /* contentblocking */, F18608DF1E5E649400361C30 /* document.js */, ); @@ -4280,6 +4294,7 @@ buildActionMask = 2147483647; files = ( 83E2D2B4253CC16B005605F5 /* httpsMobileV2BloomSpec.json in Resources */, + 02DC6A52255D8C0A00B03BC2 /* fingerprint.js in Resources */, 987AFB6C22AE83C2001B84CF /* debug-messaging-enabled.js in Resources */, 98B001B0251EABB40090EC07 /* InfoPlist.strings in Resources */, F18608E01E5E649400361C30 /* document.js in Resources */, @@ -4805,6 +4820,7 @@ 85C271DD1FD04459007216B4 /* HTTPSUpgrade.swift in Sources */, 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */, 853A717620F62FE800FE60BC /* Pixel.swift in Sources */, + 02DC6A64255D8C5000B03BC2 /* FingerprintUserScript.swift in Sources */, 85BDC31224339E080053DB07 /* DocumentUserScript.swift in Sources */, 0201A45124ED821E00C6641C /* ContentBlockerRule.swift in Sources */, 9876B75E2232B36900D81D9F /* TabInstrumentation.swift in Sources */, diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index dff7fa6537..68a86973b6 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -158,6 +158,7 @@ class TabViewController: UIViewController { private var loginFormDetectionScript = LoginFormDetectionUserScript() private var contentBlockerScript = ContentBlockerUserScript() private var contentBlockerRulesScript = ContentBlockerRulesUserScript() + private var fingerprintScript = FingerprintUserScript() private var navigatorPatchScript = NavigatorSharePatchUserScript() private var doNotSellScript = DoNotSellUserScript() private var documentScript = DocumentUserScript() @@ -231,6 +232,7 @@ class TabViewController: UIViewController { navigatorPatchScript, contentBlockerScript, contentBlockerRulesScript, + fingerprintScript, faviconScript, fullScreenVideoScript ] From 7a73ee8abab229477037b66e5817277e928d795c Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Tue, 8 Dec 2020 07:39:04 -0600 Subject: [PATCH 02/17] Setup UI test --- DuckDuckGo.xcodeproj/project.pbxproj | 12 ++++++ UITests/FingerprintUITest.swift | 63 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 UITests/FingerprintUITest.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f6c940bddd..1e9d95f4cd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 0254AC2024ED8B82004855DF /* ContentBlockerRulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC1F24ED8B82004855DF /* ContentBlockerRulesManager.swift */; }; 0254AC2224EDAC41004855DF /* ContentBlockerRulesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */; }; 0254AC2424EDACDE004855DF /* contentblockerrules.js in Resources */ = {isa = PBXBuildFile; fileRef = 0254AC2324EDACDE004855DF /* contentblockerrules.js */; }; + 025CCF76257EAFAF001CD5BB /* FingerprintUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */; }; 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */; }; 02C57C5525153330009E5129 /* DoNotSellUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */; }; 02C57C5F251533CE009E5129 /* donotsell.js in Resources */ = {isa = PBXBuildFile; fileRef = 02C57C5E251533CE009E5129 /* donotsell.js */; }; @@ -699,6 +700,7 @@ 0254AC1F24ED8B82004855DF /* ContentBlockerRulesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesManager.swift; sourceTree = ""; }; 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesUserScript.swift; sourceTree = ""; }; 0254AC2324EDACDE004855DF /* contentblockerrules.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = contentblockerrules.js; sourceTree = ""; }; + 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintUITest.swift; sourceTree = ""; }; 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellSettingsViewController.swift; sourceTree = ""; }; 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellUserScript.swift; sourceTree = ""; }; 02C57C5E251533CE009E5129 /* donotsell.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = donotsell.js; sourceTree = ""; }; @@ -2103,6 +2105,14 @@ name = ContentBlockerRules; sourceTree = ""; }; + 025CCF74257EAF73001CD5BB /* UITests */ = { + isa = PBXGroup; + children = ( + 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */, + ); + path = UITests; + sourceTree = ""; + }; 02DC6A50255D8BE700B03BC2 /* fingerprinting */ = { isa = PBXGroup; children = ( @@ -2353,6 +2363,7 @@ 98A54A8222AFCB2C00E541F4 /* Instruments */, 84E341A91E2F7EFB00BDBA6F /* UnitTests */, 85F21DAE210F5E32002631A6 /* IntegrationTests */, + 025CCF74257EAF73001CD5BB /* UITests */, 85482D892462DCD100EDEDD1 /* OpenAction */, 8512EA5224ED30D20073EE19 /* Widgets */, F1AA545F1E48D90700223211 /* Frameworks */, @@ -4800,6 +4811,7 @@ files = ( 85519125247468580010FDD0 /* TrackerRadarIntegrationTests.swift in Sources */, 02CA904D24FD2DB000D41DDF /* ContentBlockingRulesTests.swift in Sources */, + 025CCF76257EAFAF001CD5BB /* FingerprintUITest.swift in Sources */, 85F21DBE21121147002631A6 /* AtbServerTests.swift in Sources */, 85F21DB0210F5E32002631A6 /* AtbIntegrationTests.swift in Sources */, 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */, diff --git a/UITests/FingerprintUITest.swift b/UITests/FingerprintUITest.swift new file mode 100644 index 0000000000..a4f1e1328c --- /dev/null +++ b/UITests/FingerprintUITest.swift @@ -0,0 +1,63 @@ +// +// FingerprintUITest.swift +// DuckDuckGo +// +// Copyright © 2020 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// swiftlint:disable line_length + +import XCTest + +class FingerprintUITest: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + let app = XCUIApplication() + + app.toolbars["Toolbar"]/*@START_MENU_TOKEN@*/.buttons["Fire"]/*[[".buttons[\"Close all tabs and clear data\"]",".buttons[\"Fire\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + app.sheets.scrollViews.otherElements.buttons["Close Tabs and Clear Data"].tap() + + app + /*@START_MENU_TOKEN@*/.searchFields["searchEntry"]/*[[".searchFields[\"Search or enter address\"]",".searchFields[\"searchEntry\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ + .tap() + app + .searchFields["searchEntry"] + .typeText("https://privacy-test-pages.glitch.me/privacy-protections/fingerprinting/\n") + let webview = app.webViews.firstMatch + XCTAssertTrue(webview.staticTexts["⚠️ Please note that:"].firstMatch.waitForExistence(timeout: 25), "Page not loaded") + + webview + /*@START_MENU_TOKEN@*/.buttons["Start the test"]/*[[".otherElements[\"Fingerprinting test page\"].buttons[\"Start the test\"]",".buttons[\"Start the test\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ + .tap() + XCTAssertTrue(webview.staticTexts["Click for details."].waitForExistence(timeout: 25), "Test not run") + } + +} From 1f82673a15bec4e2f1b20875a244853959d81b0f Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Wed, 9 Dec 2020 07:40:36 -0600 Subject: [PATCH 03/17] Finish off UI test --- UITests/FingerprintUITest.swift | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/UITests/FingerprintUITest.swift b/UITests/FingerprintUITest.swift index a4f1e1328c..c8a4473284 100644 --- a/UITests/FingerprintUITest.swift +++ b/UITests/FingerprintUITest.swift @@ -36,15 +36,45 @@ class FingerprintUITest: XCTestCase { } override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + // Remove the bookmark we added + let app = XCUIApplication() + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + let tablesQuery = app.tables + tablesQuery/*@START_MENU_TOKEN@*/.staticTexts["DuckDuckGo — Privacy, simplified."]/*[[".cells.staticTexts[\"DuckDuckGo — Privacy, simplified.\"]",".staticTexts[\"DuckDuckGo — Privacy, simplified.\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.swipeLeft() + tablesQuery/*@START_MENU_TOKEN@*/.buttons["Delete"]/*[[".cells.buttons[\"Delete\"]",".buttons[\"Delete\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + app.navigationBars["Bookmarks"].buttons["Done"].tap() } - func testExample() throws { + func test() throws { let app = XCUIApplication() + // Add a bookmark to edit to a bookmarklet later + app/*@START_MENU_TOKEN@*/.searchFields["searchEntry"]/*[[".searchFields[\"Search or enter address\"]",".searchFields[\"searchEntry\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + app + .searchFields["searchEntry"] + .typeText("https://duckduckgo.com\n") + app.buttons["Browsing Menu"].tap() + app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + + // Edit bookmark into bookmarklet to verify fingerprinting test + let bookmarksNavigationBar = app.navigationBars["Bookmarks"] + bookmarksNavigationBar.buttons["Edit"].tap() + app.tables/*@START_MENU_TOKEN@*/.staticTexts["DuckDuckGo — Privacy, simplified."]/*[[".cells.staticTexts[\"DuckDuckGo — Privacy, simplified.\"]",".staticTexts[\"DuckDuckGo — Privacy, simplified.\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews/*@START_MENU_TOKEN@*/.textFields["www.example.com"]/*[[".cells.textFields[\"www.example.com\"]",".textFields[\"www.example.com\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap(withNumberOfTaps: 3, numberOfTouches: 1) + app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews/*@START_MENU_TOKEN@*/.textFields["www.example.com"]/*[[".cells.textFields[\"www.example.com\"]",".textFields[\"www.example.com\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ + .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.webkitTemporaryStorage': undefined,'navigator.webkitPersistentStorage': undefined,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") + app.alerts["Edit Bookmark"].scrollViews.otherElements.buttons["Save"].tap() + bookmarksNavigationBar.buttons["Done"].tap() + bookmarksNavigationBar.buttons["Done"].tap() + + // Clear all tabs and data app.toolbars["Toolbar"]/*@START_MENU_TOKEN@*/.buttons["Fire"]/*[[".buttons[\"Close all tabs and clear data\"]",".buttons[\"Fire\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() app.sheets.scrollViews.otherElements.buttons["Close Tabs and Clear Data"].tap() + sleep(2) + + // Go to fingerprinting test page app /*@START_MENU_TOKEN@*/.searchFields["searchEntry"]/*[[".searchFields[\"Search or enter address\"]",".searchFields[\"searchEntry\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ .tap() @@ -54,10 +84,17 @@ class FingerprintUITest: XCTestCase { let webview = app.webViews.firstMatch XCTAssertTrue(webview.staticTexts["⚠️ Please note that:"].firstMatch.waitForExistence(timeout: 25), "Page not loaded") + // Run webpage test webview /*@START_MENU_TOKEN@*/.buttons["Start the test"]/*[[".otherElements[\"Fingerprinting test page\"].buttons[\"Start the test\"]",".buttons[\"Start the test\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ .tap() - XCTAssertTrue(webview.staticTexts["Click for details."].waitForExistence(timeout: 25), "Test not run") + + // Run the new bookmarklet + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + app.tables/*@START_MENU_TOKEN@*/.staticTexts["DuckDuckGo — Privacy, simplified."]/*[[".cells.staticTexts[\"DuckDuckGo — Privacy, simplified.\"]",".staticTexts[\"DuckDuckGo — Privacy, simplified.\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + + // Verify the test passed + XCTAssertTrue(webview.staticTexts["TEST PASSED"].waitForExistence(timeout: 25), "Test not run") } } From 518a17c73b138eb20b9e8c3f87eb0f1c02ee83b1 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Wed, 9 Dec 2020 08:11:51 -0600 Subject: [PATCH 04/17] Clean up test --- UITests/FingerprintUITest.swift | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/UITests/FingerprintUITest.swift b/UITests/FingerprintUITest.swift index c8a4473284..854b75b05e 100644 --- a/UITests/FingerprintUITest.swift +++ b/UITests/FingerprintUITest.swift @@ -24,15 +24,20 @@ import XCTest class FingerprintUITest: XCTestCase { override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + let app = XCUIApplication() + + // Add a bookmark to edit to a bookmarklet later + app/*@START_MENU_TOKEN@*/.searchFields["searchEntry"]/*[[".searchFields[\"Search or enter address\"]",".searchFields[\"searchEntry\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + app + .searchFields["searchEntry"] + .typeText("https://duckduckgo.com\n") + app.buttons["Browsing Menu"].tap() + app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() } override func tearDownWithError() throws { @@ -47,15 +52,6 @@ class FingerprintUITest: XCTestCase { func test() throws { let app = XCUIApplication() - - // Add a bookmark to edit to a bookmarklet later - app/*@START_MENU_TOKEN@*/.searchFields["searchEntry"]/*[[".searchFields[\"Search or enter address\"]",".searchFields[\"searchEntry\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() - app - .searchFields["searchEntry"] - .typeText("https://duckduckgo.com\n") - app.buttons["Browsing Menu"].tap() - app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() - app.toolbars["Toolbar"].buttons["Bookmarks"].tap() // Edit bookmark into bookmarklet to verify fingerprinting test let bookmarksNavigationBar = app.navigationBars["Bookmarks"] From fc11fa69f764b66905695bf29ac3c4934e39ddf8 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Wed, 9 Dec 2020 13:54:45 -0600 Subject: [PATCH 05/17] Test optimization --- UITests/FingerprintUITest.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/UITests/FingerprintUITest.swift b/UITests/FingerprintUITest.swift index 854b75b05e..23f026ada4 100644 --- a/UITests/FingerprintUITest.swift +++ b/UITests/FingerprintUITest.swift @@ -76,15 +76,10 @@ class FingerprintUITest: XCTestCase { .tap() app .searchFields["searchEntry"] - .typeText("https://privacy-test-pages.glitch.me/privacy-protections/fingerprinting/\n") + .typeText("https://privacy-test-pages.glitch.me/privacy-protections/fingerprinting/?run\n") let webview = app.webViews.firstMatch XCTAssertTrue(webview.staticTexts["⚠️ Please note that:"].firstMatch.waitForExistence(timeout: 25), "Page not loaded") - // Run webpage test - webview - /*@START_MENU_TOKEN@*/.buttons["Start the test"]/*[[".otherElements[\"Fingerprinting test page\"].buttons[\"Start the test\"]",".buttons[\"Start the test\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/ - .tap() - // Run the new bookmarklet app.toolbars["Toolbar"].buttons["Bookmarks"].tap() app.tables/*@START_MENU_TOKEN@*/.staticTexts["DuckDuckGo — Privacy, simplified."]/*[[".cells.staticTexts[\"DuckDuckGo — Privacy, simplified.\"]",".staticTexts[\"DuckDuckGo — Privacy, simplified.\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() From ac0c9dcef78c335338886668a602fb866a304868 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 10 Dec 2020 07:56:49 -0600 Subject: [PATCH 06/17] UI test target --- DuckDuckGo.xcodeproj/project.pbxproj | 136 +++++++++++++++++- .../xcschemes/AdhocDebug.xcscheme | 10 ++ .../BookmarksTodayExtension.xcscheme | 19 ++- .../xcschemes/DuckDuckGo.xcscheme | 10 ++ .../xcschemes/FingerprintingUITests.xcscheme | 52 +++++++ .../xcschemes/IntegrationTests.xcscheme | 28 ++-- .../xcschemes/OpenAction.xcscheme | 10 ++ .../QuickActionsTodayExtension.xcscheme | 10 ++ .../xcschemes/ShareExtension.xcscheme | 10 ++ .../xcshareddata/xcschemes/UnitTests.xcscheme | 10 ++ .../FingerprintUITest.swift | 18 +-- FingerprintingUITests/Info.plist | 22 +++ 12 files changed, 296 insertions(+), 39 deletions(-) create mode 100644 DuckDuckGo.xcodeproj/xcshareddata/xcschemes/FingerprintingUITests.xcscheme rename {UITests => FingerprintingUITests}/FingerprintUITest.swift (66%) create mode 100644 FingerprintingUITests/Info.plist diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1e9d95f4cd..000f4c1798 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 0254AC2024ED8B82004855DF /* ContentBlockerRulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC1F24ED8B82004855DF /* ContentBlockerRulesManager.swift */; }; 0254AC2224EDAC41004855DF /* ContentBlockerRulesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */; }; 0254AC2424EDACDE004855DF /* contentblockerrules.js in Resources */ = {isa = PBXBuildFile; fileRef = 0254AC2324EDACDE004855DF /* contentblockerrules.js */; }; - 025CCF76257EAFAF001CD5BB /* FingerprintUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */; }; + 025CD01025826035001CD5BB /* FingerprintUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */; }; 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */; }; 02C57C5525153330009E5129 /* DoNotSellUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */; }; 02C57C5F251533CE009E5129 /* donotsell.js in Resources */ = {isa = PBXBuildFile; fileRef = 02C57C5E251533CE009E5129 /* donotsell.js */; }; @@ -580,6 +580,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 025CCFE72582601C001CD5BB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84E341911E2F7EFB00BDBA6F; + remoteInfo = DuckDuckGo; + }; 83491190217F491200610F35 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; @@ -701,6 +708,8 @@ 0254AC2124EDAC41004855DF /* ContentBlockerRulesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesUserScript.swift; sourceTree = ""; }; 0254AC2324EDACDE004855DF /* contentblockerrules.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = contentblockerrules.js; sourceTree = ""; }; 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintUITest.swift; sourceTree = ""; }; + 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FingerprintingUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 025CCFE62582601C001CD5BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 02C57C4A2514FEFB009E5129 /* DoNotSellSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellSettingsViewController.swift; sourceTree = ""; }; 02C57C5425153330009E5129 /* DoNotSellUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoNotSellUserScript.swift; sourceTree = ""; }; 02C57C5E251533CE009E5129 /* donotsell.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = donotsell.js; sourceTree = ""; }; @@ -2008,6 +2017,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 025CCFDF2582601C001CD5BB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491184217F491200610F35 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2105,12 +2121,13 @@ name = ContentBlockerRules; sourceTree = ""; }; - 025CCF74257EAF73001CD5BB /* UITests */ = { + 025CCFE32582601C001CD5BB /* FingerprintingUITests */ = { isa = PBXGroup; children = ( 025CCF75257EAFAF001CD5BB /* FingerprintUITest.swift */, + 025CCFE62582601C001CD5BB /* Info.plist */, ); - path = UITests; + path = FingerprintingUITests; sourceTree = ""; }; 02DC6A50255D8BE700B03BC2 /* fingerprinting */ = { @@ -2363,9 +2380,9 @@ 98A54A8222AFCB2C00E541F4 /* Instruments */, 84E341A91E2F7EFB00BDBA6F /* UnitTests */, 85F21DAE210F5E32002631A6 /* IntegrationTests */, - 025CCF74257EAF73001CD5BB /* UITests */, 85482D892462DCD100EDEDD1 /* OpenAction */, 8512EA5224ED30D20073EE19 /* Widgets */, + 025CCFE32582601C001CD5BB /* FingerprintingUITests */, F1AA545F1E48D90700223211 /* Frameworks */, 84E341931E2F7EFB00BDBA6F /* Products */, 83ED3B8D1FA8E63700B47556 /* README.md */, @@ -2387,6 +2404,7 @@ 98A54A8122AFCB2C00E541F4 /* Instruments.instrdst */, 85482D882462DCD100EDEDD1 /* OpenAction.appex */, 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */, + 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */, ); name = Products; sourceTree = ""; @@ -3768,6 +3786,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 025CCFE12582601C001CD5BB /* FingerprintingUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 025CCFEB2582601C001CD5BB /* Build configuration list for PBXNativeTarget "FingerprintingUITests" */; + buildPhases = ( + 025CCFDE2582601C001CD5BB /* Sources */, + 025CCFDF2582601C001CD5BB /* Frameworks */, + 025CCFE02582601C001CD5BB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 025CCFE82582601C001CD5BB /* PBXTargetDependency */, + ); + name = FingerprintingUITests; + productName = FingerprintingUITests; + productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 83491186217F491200610F35 /* BookmarksTodayExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 83491193217F491300610F35 /* Build configuration list for PBXNativeTarget "BookmarksTodayExtension" */; @@ -3980,10 +4016,14 @@ 84E3418A1E2F7EFB00BDBA6F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1220; LastUpgradeCheck = 1020; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { + 025CCFE12582601C001CD5BB = { + CreatedOnToolsVersion = 12.2; + TestTargetID = 84E341911E2F7EFB00BDBA6F; + }; 83491186217F491200610F35 = { CreatedOnToolsVersion = 10.0; DevelopmentTeam = HKE973VLUW; @@ -4123,11 +4163,19 @@ 98A54A8022AFCB2C00E541F4 /* Instruments */, 85F21DAC210F5E32002631A6 /* IntegrationTests */, 84E341A51E2F7EFB00BDBA6F /* UnitTests */, + 025CCFE12582601C001CD5BB /* FingerprintingUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 025CCFE02582601C001CD5BB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491185217F491200610F35 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4459,6 +4507,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 025CCFDE2582601C001CD5BB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 025CD01025826035001CD5BB /* FingerprintUITest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 83491183217F491200610F35 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4811,7 +4867,6 @@ files = ( 85519125247468580010FDD0 /* TrackerRadarIntegrationTests.swift in Sources */, 02CA904D24FD2DB000D41DDF /* ContentBlockingRulesTests.swift in Sources */, - 025CCF76257EAFAF001CD5BB /* FingerprintUITest.swift in Sources */, 85F21DBE21121147002631A6 /* AtbServerTests.swift in Sources */, 85F21DB0210F5E32002631A6 /* AtbIntegrationTests.swift in Sources */, 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */, @@ -4931,6 +4986,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 025CCFE82582601C001CD5BB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84E341911E2F7EFB00BDBA6F /* DuckDuckGo */; + targetProxy = 025CCFE72582601C001CD5BB /* PBXContainerItemProxy */; + }; 83491191217F491200610F35 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 83491186217F491200610F35 /* BookmarksTodayExtension */; @@ -5974,6 +6034,61 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 025CCFE92582601C001CD5BB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = Debug; + }; + 025CCFEA2582601C001CD5BB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HKE973VLUW; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = FingerprintingUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.FingerprintingUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DuckDuckGo; + }; + name = Release; + }; 83491194217F491300610F35 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6613,6 +6728,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 025CCFEB2582601C001CD5BB /* Build configuration list for PBXNativeTarget "FingerprintingUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 025CCFE92582601C001CD5BB /* Debug */, + 025CCFEA2582601C001CD5BB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 83491193217F491300610F35 /* Build configuration list for PBXNativeTarget "BookmarksTodayExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme index ffb26fd890..9b178ff66a 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AdhocDebug.xcscheme @@ -54,6 +54,16 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + - - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme index cb7d427c02..4f662a0cc7 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme @@ -11,15 +11,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - @@ -31,6 +22,16 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> + + + + - - - - diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme index f1e63c8353..a0cd760595 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/OpenAction.xcscheme @@ -43,6 +43,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + + + + + + + + + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + From 75ab24db57ece82cedf7b898e852859bbd821d27 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 10 Dec 2020 08:31:15 -0600 Subject: [PATCH 07/17] renable line_length --- FingerprintingUITests/FingerprintUITest.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index 6a630bfb4e..1ebadaad62 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -89,3 +89,5 @@ class FingerprintUITest: XCTestCase { } } + +// swiftlint:enable line_length From 55171663dae04717b78ec9d83d1ad9d34156e04c Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 10 Dec 2020 08:37:04 -0600 Subject: [PATCH 08/17] suppress onboarding --- FingerprintingUITests/FingerprintUITest.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index 1ebadaad62..38ca2399fb 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -25,10 +25,16 @@ class FingerprintUITest: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false - - XCUIApplication().launch() - + let app = XCUIApplication() + + app.launchEnvironment = [ + "DAXDIALOGS": "false", + "ONBOARDING": "false", + "VARIANT": "sc" + ] + + app.launch() // Add a bookmark to edit to a bookmarklet later app.searchFields["searchEntry"].tap() From 71012ee8a2ca9a73b5c17d1980bfe8cc59e67887 Mon Sep 17 00:00:00 2001 From: Chris Brind Date: Thu, 10 Dec 2020 14:52:44 +0000 Subject: [PATCH 09/17] specify duckduckgo as the debug executable --- .../xcschemes/FingerprintingUITests.xcscheme | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/FingerprintingUITests.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/FingerprintingUITests.xcscheme index f5e642e63c..70141ee903 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/FingerprintingUITests.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/FingerprintingUITests.xcscheme @@ -34,6 +34,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + + + + + + + From 7d99526381d0fcda89259d3fee9d378a3a38ceec Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 10 Dec 2020 09:36:21 -0600 Subject: [PATCH 10/17] Updates to fp script --- Core/fingerprint.js | 38 +++++++++++++------ FingerprintingUITests/FingerprintUITest.swift | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Core/fingerprint.js b/Core/fingerprint.js index 8fd6d54628..27a6263cdb 100644 --- a/Core/fingerprint.js +++ b/Core/fingerprint.js @@ -62,18 +62,6 @@ 'targetValue': 24 } }, - 'storage': { - 'webkitTemporaryStorage': { - 'object': 'navigator', - 'origValue': navigator.webkitTemporaryStorage, - 'targetValue': undefined - }, - 'webkitPersistentStorage': { - 'object': 'navigator', - 'origValue': navigator.webkitPersistentStorage, - 'targetValue': undefined - } - }, 'options': { 'doNotTrack': { 'object': 'navigator', @@ -158,12 +146,38 @@ return '' } } + + /** + * Temporary storage can be used to determine hard disk usage and size. + * This will limit the max storage to 4GB without completely disabling the + * feature. + */ + function modifyTemporaryStorage () { + const script = ` + if (navigator.webkitTemporaryStorage) { + try { + const org = navigator.webkitTemporaryStorage.queryUsageAndQuota + navigator.webkitTemporaryStorage.queryUsageAndQuota = function queryUsageAndQuota (callback, err) { + const modifiedCallback = function (usedBytes, grantedBytes) { + const maxBytesGranted = 4 * 1024 * 1024 * 1024 + const spoofedGrantedBytes = Math.min(grantedBytes, maxBytesGranted) + callback(usedBytes, spoofedGrantedBytes) + } + org.call(navigator.webkitTemporaryStorage, modifiedCallback, err) + } + } + catch(e) {} + } + ` + return script + } /** * All the steps for building the injection script. Should only be done at initial page load. */ function buildInjectionScript () { let script = buildScriptProperties() + script += modifyTemporaryStorage() script += buildBatteryScript() return script } diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index 38ca2399fb..fb94f613aa 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -65,7 +65,7 @@ class FingerprintUITest: XCTestCase { app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].tap() app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"].tap(withNumberOfTaps: 3, numberOfTouches: 1) app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"] - .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.webkitTemporaryStorage': undefined,'navigator.webkitPersistentStorage': undefined,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") + .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") app.alerts["Edit Bookmark"].scrollViews.otherElements.buttons["Save"].tap() bookmarksNavigationBar.buttons["Done"].tap() bookmarksNavigationBar.buttons["Done"].tap() From 843a6618b7c38b44f5a08273a17fa0d826218edf Mon Sep 17 00:00:00 2001 From: Chris Brind Date: Thu, 10 Dec 2020 16:24:13 +0000 Subject: [PATCH 11/17] set the ios version for compatibility with CI --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 000f4c1798..241537bcf4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -6046,7 +6046,7 @@ DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6074,7 +6074,7 @@ DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = FingerprintingUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From 5d91cb725564f201f392e27c7030d90a32fac2df Mon Sep 17 00:00:00 2001 From: Chris Brind Date: Sun, 13 Dec 2020 19:35:26 +0000 Subject: [PATCH 12/17] remove fingerprinting from standard tests --- .../xcshareddata/xcschemes/DuckDuckGo.xcscheme | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme index 2281b6a579..bc8d3da5b5 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme @@ -54,16 +54,6 @@ ReferencedContainer = "container:DuckDuckGo.xcodeproj"> - - - - Date: Mon, 14 Dec 2020 09:16:54 -0600 Subject: [PATCH 13/17] Add hardware keyboard filtering for UI tests --- DuckDuckGo/AppDelegate.swift | 11 +++++++++++ FingerprintingUITests/FingerprintUITest.swift | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index fa6aaa441d..da72eb682f 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -48,6 +48,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + #if targetEnvironment(simulator) + if ProcessInfo.processInfo.environment["UITESTING"] == "true" { + // Disable hardware keyboards. + let setHardwareLayout = NSSelectorFromString("setHardwareLayout:") + UITextInputMode.activeInputModes + // Filter `UIKeyboardInputMode`s. + .filter({ $0.responds(to: setHardwareLayout) }) + .forEach { $0.perform(setHardwareLayout, with: nil) } + } + #endif + _ = UserAgentManager.shared testing = ProcessInfo().arguments.contains("testing") if testing { diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index fb94f613aa..09d39fec27 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -31,7 +31,8 @@ class FingerprintUITest: XCTestCase { app.launchEnvironment = [ "DAXDIALOGS": "false", "ONBOARDING": "false", - "VARIANT": "sc" + "VARIANT": "sc", + "UITESTING": "true" ] app.launch() From 8d31ab4797fc4ae6aef7f46ecc99306d3c786583 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Mon, 14 Dec 2020 11:27:05 -0600 Subject: [PATCH 14/17] Delays --- FingerprintingUITests/FingerprintUITest.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index 09d39fec27..ee3fc04a5b 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -62,8 +62,10 @@ class FingerprintUITest: XCTestCase { // Edit bookmark into bookmarklet to verify fingerprinting test let bookmarksNavigationBar = app.navigationBars["Bookmarks"] + sleep(1) bookmarksNavigationBar.buttons["Edit"].tap() - app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + sleep(1) + app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"].tap(withNumberOfTaps: 3, numberOfTouches: 1) app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"] .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") From 360b246f2e8652b7603b00c3a208d3330e6c4cde Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Tue, 15 Dec 2020 10:59:46 -0600 Subject: [PATCH 15/17] Try waitForExistence --- FingerprintingUITests/FingerprintUITest.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index ee3fc04a5b..f9095d71b5 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -62,10 +62,13 @@ class FingerprintUITest: XCTestCase { // Edit bookmark into bookmarklet to verify fingerprinting test let bookmarksNavigationBar = app.navigationBars["Bookmarks"] - sleep(1) + _ = bookmarksNavigationBar.buttons["Edit"].waitForExistence(timeout: 25) bookmarksNavigationBar.buttons["Edit"].tap() - sleep(1) - app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + if app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: 25) { + app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + } else { + XCTFail("Could not find bookmark") + } app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"].tap(withNumberOfTaps: 3, numberOfTouches: 1) app.alerts["Edit Bookmark"].scrollViews.otherElements.collectionViews.textFields["www.example.com"] .typeText("javascript:(function(){const values = {'screen.availTop': 0,'screen.availLeft': 0,'screen.availWidth': screen.width,'screen.availHeight': screen.height,'screen.colorDepth': 24,'screen.pixelDepth': 24,'window.screenY': 0,'window.screenLeft': 0,'navigator.doNotTrack': undefined};var passed = true;var reason = null;for (const test of results.results) {if (values[test.id] !== undefined) {if (values[test.id] !== test.value) {console.log(test.id, values[test.id]);reason = test.id;passed = false;break;}}}var elem = document.createElement('p');elem.innerHTML = (passed) ? 'TEST PASSED' : 'TEST FAILED: ' + reason;document.body.insertBefore(elem, document.body.childNodes[0]);}());") From 2ebce6068c200e8fdeb52af7841bdbec6b911c10 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 17 Dec 2020 08:11:51 -0600 Subject: [PATCH 16/17] Did I fix it? --- FingerprintingUITests/FingerprintUITest.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index f9095d71b5..ff8c6c7a77 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -43,8 +43,11 @@ class FingerprintUITest: XCTestCase { .searchFields["searchEntry"] .typeText("https://duckduckgo.com\n") app.buttons["Browsing Menu"].tap() - app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() - app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + if app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].waitForExistence(timeout: 2) { + app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap() + } else { + app.sheets.scrollViews.otherElements.buttons["Cancel"].tap() + } } override func tearDownWithError() throws { @@ -60,6 +63,12 @@ class FingerprintUITest: XCTestCase { func test() throws { let app = XCUIApplication() + if app.toolbars["Toolbar"].buttons["Bookmarks"].waitForExistence(timeout: 2) { + app.toolbars["Toolbar"].buttons["Bookmarks"].tap() + } else { + XCTFail("Bookmarks button missing") + } + // Edit bookmark into bookmarklet to verify fingerprinting test let bookmarksNavigationBar = app.navigationBars["Bookmarks"] _ = bookmarksNavigationBar.buttons["Edit"].waitForExistence(timeout: 25) From bd716cbbffc53e2ba7a51bbfb3523f712cfdf74a Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 17 Dec 2020 08:49:02 -0600 Subject: [PATCH 17/17] Add delay for site load --- FingerprintingUITests/FingerprintUITest.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index ff8c6c7a77..7da0a0cc3a 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -42,6 +42,9 @@ class FingerprintUITest: XCTestCase { app .searchFields["searchEntry"] .typeText("https://duckduckgo.com\n") + + sleep(5) // let site load + app.buttons["Browsing Menu"].tap() if app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].waitForExistence(timeout: 2) { app.sheets.scrollViews.otherElements.buttons["Add to Bookmarks"].tap()