From a936aea3a10875761cc0209e4cc8854f10c570b4 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Tue, 6 Feb 2024 15:18:34 -0500 Subject: [PATCH 01/10] Add find in page in engine session --- .../Scripts/FindInPageContentScript.swift | 51 +++++++++++++++++++ .../Scripts/WKUserScriptManager.swift | 6 +-- .../WebEngine/WKWebview/WKEngineSession.swift | 8 +++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift new file mode 100644 index 000000000000..ca7c4a7abc50 --- /dev/null +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Common +import Foundation +import WebKit + +protocol FindInPageHelperDelegate: AnyObject { + func findInPageHelper(_ findInPageContentScript: FindInPageContentScript, didUpdateCurrentResult currentResult: Int) + func findInPageHelper(_ findInPageContentScript: FindInPageContentScript, didUpdateTotalResults totalResults: Int) +} + +class FindInPageContentScript: WKContentScript { + weak var delegate: FindInPageHelperDelegate? + private var logger: Logger + + init(logger: Logger = DefaultLogger.shared) { + self.logger = logger + } + + class func name() -> String { + return "FindInPage" + } + + func scriptMessageHandlerNames() -> [String] { + return ["findInPageHandler"] + } + + func userContentController( + _ userContentController: WKUserContentController, + didReceiveScriptMessage message: WKScriptMessage + ) { + guard let parameters = message.body as? [String: String], + let token = parameters["appIdToken"], + token == DefaultUserScriptManager.appIdToken else { + logger.log("FindInPage.js sent wrong type of message", + level: .warning, + category: .webview) + return + } + + if let currentResult = parameters["currentResult"], let result = Int(currentResult) { + delegate?.findInPageHelper(self, didUpdateCurrentResult: result) + } + + if let totalResults = parameters["totalResults"], let result = Int(totalResults) { + delegate?.findInPageHelper(self, didUpdateTotalResults: result) + } + } +} diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift index afd1af0ded54..7745e6370f1c 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift @@ -12,7 +12,7 @@ protocol WKUserScriptManager { class DefaultUserScriptManager: WKUserScriptManager { // Scripts can use this to verify the application (not JS on the web) is calling into them - private let appIdToken = UUID().uuidString + static let appIdToken = UUID().uuidString private let scriptProvider: UserScriptProvider var compiledUserScripts = [String: WKUserScript]() @@ -68,7 +68,7 @@ class DefaultUserScriptManager: WKUserScriptManager { private func injectFrameScript(name: String, userScriptInfo: UserScriptInfo) { guard let source = scriptProvider.getScript(for: name) else { return } - let wrappedSource = "(function() { const APP_ID_TOKEN = '\(appIdToken)'; \(source) })()" + let wrappedSource = "(function() { const APP_ID_TOKEN = '\(DefaultUserScriptManager.appIdToken)'; \(source) })()" // Create in default content world let userScript = WKUserScript(source: wrappedSource, injectionTime: userScriptInfo.injectionTime, @@ -81,7 +81,7 @@ class DefaultUserScriptManager: WKUserScriptManager { let webcompatName = "Webcompat\(name)" guard let source = scriptProvider.getScript(for: webcompatName) else { return } - let wrappedSource = "(function() { const APP_ID_TOKEN = '\(appIdToken)'; \(source) })()" + let wrappedSource = "(function() { const APP_ID_TOKEN = '\(DefaultUserScriptManager.appIdToken)'; \(source) })()" // Create in page content world let userScript = WKUserScript(source: wrappedSource, injectionTime: userScriptInfo.injectionTime, diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index ddce0e223236..5d875ca8312c 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -45,6 +45,7 @@ class WKEngineSession: NSObject, webView.navigationDelegate = self webView.delegate = self userScriptManager.injectUserScriptsIntoWebView(webView) + addContentScripts() } // TODO: FXIOS-7903 #17648 no return from this load(url:), we need a way to recordNavigationInTab @@ -221,6 +222,13 @@ class WKEngineSession: NSObject, } } + // MARK: - Content scripts + + private func addContentScripts() { + let findInPage = FindInPageContentScript() + contentScriptManager.addContentScript(findInPage, name: FindInPageContentScript.name(), forSession: self) + } + // MARK: - WKUIDelegate func webView(_ webView: WKWebView, From fc006b94c2a9dc85a0fdf5ae9f23b6f212288be1 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 10:15:49 -0500 Subject: [PATCH 02/10] Add tests --- .../Sources/WebEngine/WKWebview/WKEngineSession.swift | 5 +++-- .../WebEngineTests/Mock/MockWKContentScriptManager.swift | 5 +++++ .../Tests/WebEngineTests/WKEngineSessionTests.swift | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index 5d875ca8312c..ca48ca664815 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -225,8 +225,9 @@ class WKEngineSession: NSObject, // MARK: - Content scripts private func addContentScripts() { - let findInPage = FindInPageContentScript() - contentScriptManager.addContentScript(findInPage, name: FindInPageContentScript.name(), forSession: self) + contentScriptManager.addContentScript(FindInPageContentScript(), + name: FindInPageContentScript.name(), + forSession: self) } // MARK: - WKUIDelegate diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift index 161ce93fa6ba..7cb71681d480 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift @@ -12,15 +12,20 @@ class MockWKContentScriptManager: NSObject, WKContentScriptManager { var uninstallCalled = 0 var userContentControllerCalled = 0 + var savedContentScriptNames = [String]() + var savedContentScriptPageNames = [String]() + func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + savedContentScriptNames.append(name) addContentScriptCalled += 1 } func addContentScriptToPage(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + savedContentScriptPageNames.append(name) addContentScriptToPageCalled += 1 } diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift index 9998ba6ab2a9..74247b68ad9b 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift @@ -407,6 +407,15 @@ final class WKEngineSessionTests: XCTestCase { // MARK: Content script manager + func testContentScriptGivenInitContentScriptsThenAreAddedAtInit() { + let subject = createSubject() + + XCTAssertEqual(contentScriptManager.addContentScriptCalled, 1) + XCTAssertEqual(contentScriptManager.savedContentScriptNames.count, 1) + XCTAssertEqual(contentScriptManager.savedContentScriptNames[0], FindInPageContentScript.name()) + prepareForTearDown(subject!) + } + func testContentScriptWhenCloseCalledThenUninstallIsCalled() { let subject = createSubject() From c9eab0455962e011b4df3a1e69debc32c0eeaa86 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 11:44:18 -0500 Subject: [PATCH 03/10] Integrate find in page, add missing functionalities --- .../Sources/WebEngine/EngineSession.swift | 12 +- .../Scripts/FindInPageContentScript.swift | 30 ++- .../Scripts/WKContentScriptManager.swift | 6 +- .../WebEngine/WKWebview/WKEngineSession.swift | 16 ++ .../SampleBrowser.xcodeproj/project.pbxproj | 32 +++ .../chevronDownLarge.imageset/Contents.json | 12 ++ .../chevronDownLarge.pdf | Bin 0 -> 1022 bytes .../chevronUpLarge.imageset/Contents.json | 12 ++ .../chevronUpLarge.pdf | Bin 0 -> 965 bytes .../crossMedium.imageset/Contents.json | 12 ++ .../crossMedium.imageset/crossMedium.pdf | Bin 0 -> 1034 bytes .../Engine/Scripts/AllFramesAtDocumentEnd.js | 1 + .../Scripts/AllFramesAtDocumentStart.js | 1 + .../Engine/Scripts/MainFrameAtDocumentEnd.js | 1 + .../Scripts/MainFrameAtDocumentStart.js | 2 + .../WebcompatAllFramesAtDocumentStart.js | 1 + .../UI/Browser/BrowserViewController.swift | 30 ++- .../UI/Components/FindInPage.swift | 191 ++++++++++++++++++ .../SampleBrowser/UI/RootViewController.swift | 54 ++++- .../UI/Settings/SettingsDelegate.swift | 1 + .../UI/Settings/SettingsViewController.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 27 --- 22 files changed, 399 insertions(+), 44 deletions(-) create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/Contents.json create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/chevronUpLarge.imageset/chevronUpLarge.pdf create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/Contents.json create mode 100644 SampleBrowser/SampleBrowser/Assets.xcassets/crossMedium.imageset/crossMedium.pdf create mode 100644 SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentEnd.js create mode 100644 SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js create mode 100644 SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentEnd.js create mode 100644 SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js create mode 100644 SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js create mode 100644 SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift diff --git a/BrowserKit/Sources/WebEngine/EngineSession.swift b/BrowserKit/Sources/WebEngine/EngineSession.swift index f25eba4fe589..f5be2b45bbc0 100644 --- a/BrowserKit/Sources/WebEngine/EngineSession.swift +++ b/BrowserKit/Sources/WebEngine/EngineSession.swift @@ -7,6 +7,7 @@ import Foundation /// Protocol representing a single engine session. In browsers usually a session corresponds to a tab. public protocol EngineSession { var delegate: EngineSessionDelegate? { get set } + var findInPageDelegate: FindInPageHelperDelegate? { get set } /// The engine received a request to load a URL. /// - Parameter url: The URL string that is requested @@ -24,9 +25,18 @@ public protocol EngineSession { /// Navigates forward in the history of this session. func goForward() - /// Scroll the session to the top + /// Scroll the session to the top. func scrollToTop() + /// Find a text selection in this session. + /// - Parameters: + /// - text: The text to search + /// - function: The functionality the find in page should search with + func findInPage(text: String, function: FindInPageFunction) + + /// Called whenever the find in page session should be ended. + func findInPageDone() + /// Navigates to the specified index in the history of this session. The current index of /// this session's history will be updated but the items within it will be unchanged. /// Invalid index values are ignored. diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift index ca7c4a7abc50..a15bddf9f1f1 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift @@ -6,9 +6,20 @@ import Common import Foundation import WebKit -protocol FindInPageHelperDelegate: AnyObject { - func findInPageHelper(_ findInPageContentScript: FindInPageContentScript, didUpdateCurrentResult currentResult: Int) - func findInPageHelper(_ findInPageContentScript: FindInPageContentScript, didUpdateTotalResults totalResults: Int) +public enum FindInPageFunction: String { + /// Find all the occurences of this text in the page + case find + + /// Find the next occurence of this text in the page + case findNext + + /// Find the previous occurence of this text in the page + case findPrevious +} + +public protocol FindInPageHelperDelegate: AnyObject { + func findInPageHelper(didUpdateCurrentResult currentResult: Int) + func findInPageHelper(didUpdateTotalResults totalResults: Int) } class FindInPageContentScript: WKContentScript { @@ -31,21 +42,20 @@ class FindInPageContentScript: WKContentScript { _ userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage ) { - guard let parameters = message.body as? [String: String], - let token = parameters["appIdToken"], - token == DefaultUserScriptManager.appIdToken else { + guard let parameters = message.body as? [String: Int] else { + // TODO: FXIOS-6463 - Integrate message handler check logger.log("FindInPage.js sent wrong type of message", level: .warning, category: .webview) return } - if let currentResult = parameters["currentResult"], let result = Int(currentResult) { - delegate?.findInPageHelper(self, didUpdateCurrentResult: result) + if let currentResult = parameters["currentResult"] { + delegate?.findInPageHelper(didUpdateCurrentResult: currentResult) } - if let totalResults = parameters["totalResults"], let result = Int(totalResults) { - delegate?.findInPageHelper(self, didUpdateTotalResults: result) + if let totalResults = parameters["totalResults"] { + delegate?.findInPageHelper(didUpdateTotalResults: totalResults) } } } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift index be2dcf3199b2..a3b02734758f 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift @@ -7,6 +7,8 @@ import WebKit /// Manager used to add and remove scripts inside a WKEngineSession protocol WKContentScriptManager: WKScriptMessageHandler { + var scripts: [String: WKContentScript] { get } + func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) @@ -19,12 +21,12 @@ protocol WKContentScriptManager: WKScriptMessageHandler { } class DefaultContentScriptManager: NSObject, WKContentScriptManager { - var scripts = [String: WKContentScript]() + private(set) var scripts = [String: WKContentScript]() func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { - // If a script already exists on a tab, skip adding this duplicate. + // If a script already exists on a session, skip adding this duplicate. guard scripts[name] == nil else { return } scripts[name] = script diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index ca48ca664815..49b7a4cfd212 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -12,6 +12,13 @@ class WKEngineSession: NSObject, WKNavigationDelegate, WKEngineWebViewDelegate { weak var delegate: EngineSessionDelegate? + weak var findInPageDelegate: FindInPageHelperDelegate? { + didSet { + guard let findInPage = contentScriptManager.scripts[FindInPageContentScript.name()] as? FindInPageContentScript else { return } + findInPage.delegate = findInPageDelegate + } + } + private(set) var webView: WKEngineWebView private var logger: Logger var sessionData: WKEngineSessionData @@ -117,6 +124,15 @@ class WKEngineSession: NSObject, webView.engineScrollView.setContentOffset(CGPoint.zero, animated: true) } + func findInPage(text: String, function: FindInPageFunction) { + let escaped = text.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + webView.evaluateJavascriptInDefaultContentWorld("__firefox__.\(function.rawValue)(\"\(escaped)\")") + } + + func findInPageDone() { + webView.evaluateJavascriptInDefaultContentWorld("__firefox__.findDone()") + } + func goToHistory(index: Int) { // TODO: FXIOS-7907 #17651 Handle goToHistoryIndex in WKEngineSession (equivalent to goToBackForwardListItem) } diff --git a/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj b/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj index 6ed157d23f0c..de37815dbf61 100644 --- a/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj +++ b/SampleBrowser/SampleBrowser.xcodeproj/project.pbxproj @@ -37,6 +37,12 @@ 8A46021E2B0FE50D00FFD17F /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A46021D2B0FE50D00FFD17F /* UIView+Extension.swift */; }; 8A4602202B0FE52F00FFD17F /* UISearchbar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A46021F2B0FE52F00FFD17F /* UISearchbar+Extension.swift */; }; 8A4602222B0FE55300FFD17F /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4602212B0FE55300FFD17F /* UIViewController+Extension.swift */; }; + 8ADF72C62B73DB9700530E7A /* MainFrameAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */; }; + 8ADF72C72B73DB9700530E7A /* AllFramesAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */; }; + 8ADF72C82B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */; }; + 8ADF72C92B73DB9700530E7A /* AllFramesAtDocumentEnd.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */; }; + 8ADF72CA2B73DB9700530E7A /* MainFrameAtDocumentEnd.js in Resources */ = {isa = PBXBuildFile; fileRef = 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */; }; + 8ADF72CD2B73DE6A00530E7A /* FindInPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */; }; 8AEBDD712B69A8C300FE9192 /* AppLaunchUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEBDD702B69A8C300FE9192 /* AppLaunchUtil.swift */; }; /* End PBXBuildFile section */ @@ -73,6 +79,12 @@ 8A46021D2B0FE50D00FFD17F /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 8A46021F2B0FE52F00FFD17F /* UISearchbar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchbar+Extension.swift"; sourceTree = ""; }; 8A4602212B0FE55300FFD17F /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; + 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MainFrameAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = AllFramesAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = WebcompatAllFramesAtDocumentStart.js; sourceTree = ""; }; + 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = AllFramesAtDocumentEnd.js; sourceTree = ""; }; + 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MainFrameAtDocumentEnd.js; sourceTree = ""; }; + 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInPage.swift; sourceTree = ""; }; 8AEBDD702B69A8C300FE9192 /* AppLaunchUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchUtil.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -91,6 +103,7 @@ 8A0F44692B56EDC000438589 /* Engine */ = { isa = PBXGroup; children = ( + 8ADF72C02B73D6F600530E7A /* Scripts */, 8A0F446A2B56EDC900438589 /* EngineProvider.swift */, ); path = Engine; @@ -206,6 +219,7 @@ 8A4602152B0FE42300FFD17F /* Components */ = { isa = PBXGroup; children = ( + 8ADF72CC2B73DE6A00530E7A /* FindInPage.swift */, 8A4602162B0FE43A00FFD17F /* BrowserSearchBar.swift */, 8A4602182B0FE45200FFD17F /* BrowserToolbar.swift */, ); @@ -231,6 +245,18 @@ path = Browser; sourceTree = ""; }; + 8ADF72C02B73D6F600530E7A /* Scripts */ = { + isa = PBXGroup; + children = ( + 8ADF72C42B73DB9700530E7A /* AllFramesAtDocumentEnd.js */, + 8ADF72C22B73DB9700530E7A /* AllFramesAtDocumentStart.js */, + 8ADF72C52B73DB9700530E7A /* MainFrameAtDocumentEnd.js */, + 8ADF72C12B73DB9700530E7A /* MainFrameAtDocumentStart.js */, + 8ADF72C32B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js */, + ); + path = Scripts; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -292,8 +318,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8ADF72C92B73DB9700530E7A /* AllFramesAtDocumentEnd.js in Resources */, + 8ADF72C82B73DB9700530E7A /* WebcompatAllFramesAtDocumentStart.js in Resources */, 8A4601F12B0FE20700FFD17F /* LaunchScreen.storyboard in Resources */, 8A4601EE2B0FE20700FFD17F /* Assets.xcassets in Resources */, + 8ADF72C62B73DB9700530E7A /* MainFrameAtDocumentStart.js in Resources */, + 8ADF72C72B73DB9700530E7A /* AllFramesAtDocumentStart.js in Resources */, + 8ADF72CA2B73DB9700530E7A /* MainFrameAtDocumentEnd.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -321,6 +352,7 @@ 8A0F44852B56FFFC00438589 /* SearchTerm.swift in Sources */, 8A3DB32F2B5AD3D400F89705 /* SettingsCell.swift in Sources */, 8A0F44792B56FD6E00438589 /* SuggestionCellViewModel.swift in Sources */, + 8ADF72CD2B73DE6A00530E7A /* FindInPage.swift in Sources */, 8A3DB32D2B5AD3C700F89705 /* SettingsViewController.swift in Sources */, 8A4602192B0FE45200FFD17F /* BrowserToolbar.swift in Sources */, 8AEBDD712B69A8C300FE9192 /* AppLaunchUtil.swift in Sources */, diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json new file mode 100644 index 000000000000..4e1aac97fc56 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chevronDownLarge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf b/SampleBrowser/SampleBrowser/Assets.xcassets/chevronDownLarge.imageset/chevronDownLarge.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ad97547171e0ea914807ef7f550e5f0e24ce54f9 GIT binary patch literal 1022 zcmZXT%Wm5+5JmU%6|*sr6bQvPDIf^Y*iBKiMO~%4APYri92K_IN=lLT>pRpdcFYxZ z%$d2DmtL$_Hy46A0D&Cqho69Wd5Kq75UNf51bN7lFQM9YPpAlH@l@M2A>Tu?3V+HX zWFN0_d3*R(wD21QrKoN;)(eY_)G|rRr5hYr z&xxH{-N6$)zJm(BLkw$7Vu~#$G{mXAn#0sHrI3^NRaJK_-rZB^w2~ixe+5j|`8JH; zYuJ?eb^U^OlhxkM5T?{qnyfgc5QgvbE9MfZMe2IL*OsbEG@%FqBDmZt4#wH0Mu`o!iweJUmVk% zky5Xcw{_k09X@=g-qA|F{r%-JSr=PBfzN(Z6}Qa`J{&p_#YsIkdh(5QgvbE9PP;IRtir-DRaHQe!t&)mC*$Zcz>tZyYKcS|C;0U*DMp)^@!I zG(60{^T}epy1SB*0gx!L{r&?Gudngu23)h@S4fU|{Mj|z;RREI1$*^F=gK|AEBCvu zT=C%+m-okSRS&;FM#3KyvvC!y@GXt3wc0Ao*hyzOR(l9;=SE*OU?QPKmP=H8Ikf`! zAf*?|6-p5?%M|Xw+qpFwr|-%T_1BC^&Fj8Y!BIx#lcLm=(5RAWVm@-NEmdpNTvG`lM{DOkagZrp%EBv->73WSgBN&)2EG9g zZkozGLY~$*+SIBr*ep;Qc>Jzu+M&nCCt3hH@xz~=4&!yXbrbmFHg$R1zTxAXo;Vmu zXwbo$l?vCluU+MOxnzid;c~bDU-S zN_XX1{ymW+C4HjA1@ z%6J)0krL*Ar1LW^qYhd_SJpe%0Z;GairR6}w|jSq7xm_HY)V|T?SNdcZ*Y8H|8{"use strict";window.__firefox__||Object.defineProperty(window,"__firefox__",{enumerable:!1,configurable:!1,writable:!1,value:{userScripts:{},includeOnce:function(e,t){return!!__firefox__.userScripts[e]||(__firefox__.userScripts[e]=!0,"function"==typeof t&&t(),!1)}}})})(),(()=>{function e(){let e={url:document.location.href,urls:t(),cookies:n()};webkit.messageHandlers.adsMessageHandler.postMessage(e)}function t(){let e=[],t=document.getElementsByTagName("a");for(let n of t)n.href&&e.push(n.href);return e}function n(){let e=document.cookie.split("; "),t=[];return e.forEach((e=>{var[n,...o]=e.split("=");o=o.join("="),t.push({name:n,value:o})})),t}const o=t=>{switch(t.type){case"load":e();break;case"pageshow":t.persisted&&e();break;default:console.log("Event:",t.type)}};["pageshow","load"].forEach((e=>window.addEventListener(e,o)))})(),(()=>{"use strict";window.__firefox__.includeOnce("ContextMenu",(function(){window.addEventListener("touchstart",(function(e){var t=e.target,n=t.closest("a"),o=t.closest("img");if(n||o){var s={};s.touchX=e.changedTouches[0].pageX-window.scrollX,s.touchY=e.changedTouches[0].pageY-window.scrollY,n&&(s.link=n.href,s.title=n.textContent),o&&(s.image=o.src,s.title=s.title||o.title,s.alt=o.alt),(s.link||s.image)&&webkit.messageHandlers.contextMenuMessageHandler.postMessage(s)}}),!0)}))})(),(()=>{"use strict";window.__firefox__.includeOnce("FocusHelper",(function(){const e=e=>{const t=e.type,n=e.target.nodeName;("INPUT"===n||"TEXTAREA"===n||e.target.isContentEditable)&&((e=>{if("INPUT"!==e.nodeName)return!1;const t=e.type.toUpperCase();return"BUTTON"==t||"SUBMIT"==t||"FILE"==t})(e.target)||webkit.messageHandlers.focusHelper.postMessage({eventType:t,elementType:n}))},t={capture:!0,passive:!0},n=window.document.body;["focus","blur"].forEach((o=>{n.addEventListener(o,e,t)}))}))})(),(()=>{"use strict";window.__firefox__.includeOnce("PrintHandler",(function(){window.print=function(){webkit.messageHandlers.printHandler.postMessage({})}}))})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js new file mode 100644 index 000000000000..69d3f26801c5 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/AllFramesAtDocumentStart.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={919:()=>{webkit.messageHandlers.trackingProtectionStats&&function(){let e=!0;Object.defineProperty(window.__firefox__,"TrackingProtectionStats",{enumerable:!1,configurable:!1,writable:!1,value:{}}),Object.defineProperty(window.__firefox__.TrackingProtectionStats,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(t,n){n===APP_ID_TOKEN&&t!==e&&(e=t,c(t))}});let t=new Array,n=null;function r(r){if(e){try{if(document.location.host===new URL(r).host)return}catch(e){}r&&t.push(r),n||(n=setTimeout((()=>{n=null,t.length<1||(webkit.messageHandlers.trackingProtectionStats.postMessage({urls:t}),t=new Array)}),200))}}function i(){[].slice.apply(document.scripts).forEach((function(e){r(e.src)})),[].slice.apply(document.images).forEach((function(e){r(e.src)})),[].slice.apply(document.getElementsByTagName("iframe")).forEach((function(e){r(e.src)}))}let o=null,s=null,a=null,l=null,u=null;function c(e){if(!e)return window.removeEventListener("load",i,!1),void(o&&(XMLHttpRequest.prototype.open=o,XMLHttpRequest.prototype.send=s,window.fetch=a,u.disconnect(),o=s=l=u=null));if(!o){window.addEventListener("load",i,!1),o||(o=XMLHttpRequest.prototype.open,s=XMLHttpRequest.prototype.send);var t=new WeakMap;new WeakMap,XMLHttpRequest.prototype.open=function(e,n){return t.set(this,n),o.apply(this,arguments)},XMLHttpRequest.prototype.send=function(e){return r(t.get(this)),s.apply(this,arguments)},a||(a=window.fetch),window.fetch=function(e,t){return"string"==typeof e?r(e):e instanceof Request&&r(e.url),a.apply(window,arguments)},l||(l=Object.getOwnPropertyDescriptor(Image.prototype,"src")),delete Image.prototype.src,Object.defineProperty(Image.prototype,"src",{get:function(){return l.get.call(this)},set:function(e){r(this.src),l.set.call(this,e)}}),u=new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){if("SCRIPT"===e.tagName&&e.src)r(e.src);else if("IMG"===e.tagName&&e.src)r(e.src);else if("IFRAME"===e.tagName&&e.src){if("about:blank"===e.src)return;r(e.src)}else"LINK"===e.tagName&&e.href&&r(e.href)}))}))})),u.observe(document.documentElement,{childList:!0,subtree:!0})}}c(!0)}()}},t={};window.__firefox__||Object.defineProperty(window,"__firefox__",{enumerable:!1,configurable:!1,writable:!1,value:{userScripts:{},includeOnce:function(e,t){return!!__firefox__.userScripts[e]||(__firefox__.userScripts[e]=!0,"function"==typeof t&&t(),!1)}}}),void 0===window.__firefox__.download&&Object.defineProperty(window.__firefox__,"download",{enumerable:!1,configurable:!1,writable:!1,value:function(e,t){if(t===APP_ID_TOKEN){if(e.startsWith("blob:")){var n=new XMLHttpRequest;return n.open("GET",e,!0),n.responseType="blob",n.onload=function(){if(200===this.status){var t=this.response;!function(e,t){var n=new FileReader;n.onloadend=function(){t(this.result.split(",")[1])},n.readAsDataURL(e)}(t,(function(n){webkit.messageHandlers.downloadManager.postMessage({url:e,mimeType:t.type,size:t.size,base64String:n})}))}},void n.send()}var r=document.createElement("a");r.href=e,r.dispatchEvent(new MouseEvent("click"))}}}),window.__firefox__.includeOnce("LoginsHelper",(function(){function e(e){}var t={_getRandomId:function(){return Math.round(Math.random()*(Number.MAX_VALUE-Number.MIN_VALUE)+Number.MIN_VALUE).toString()},_messages:["RemoteLogins:loginsFound"],_requests:{},_takeRequest:function(e){var t=e,n=this._requests[t.requestId];return this._requests[t.requestId]=void 0,n},_sendRequest:function(e,t){var n=this._getRandomId();t.requestId=n,webkit.messageHandlers.loginsManagerMessageHandler.postMessage(t);var r=this;return new Promise((function(t,i){e.promise={resolve:t,reject:i},r._requests[n]=e}))},receiveMessage:function(e){var t=this._takeRequest(e);switch(e.name){case"RemoteLogins:loginsFound":t.promise.resolve({form:t.form,loginsFound:e.logins});break;case"RemoteLogins:loginsAutoCompleted":t.promise.resolve(e.logins)}},_asyncFindLogins:function(e,t){var i=this._getFormFields(e,!1);if(!i[0]||!i[1])return Promise.reject("No logins found");i[0].addEventListener("blur",r);var o=n._getPasswordOrigin(e.ownerDocument.documentURI),s=n._getActionOrigin(e);if(null==s)return Promise.reject("Action origin is null");var a={form:e},l={type:"request",formOrigin:o,actionOrigin:s};return this._sendRequest(a,l)},loginsFound:function(e,t){this._fillForm(e,!0,!1,!1,!1,t)},onUsernameInput:function(t){var n=t.target;if(n.ownerDocument instanceof HTMLDocument&&this._isUsernameFieldType(n)){var r=n.form;if(r&&n.value){t.type;var[i,o,s]=this._getFormFields(r,!1);if(i==n&&o){var a=this;this._asyncFindLogins(r,{showMasterPassword:!1}).then((function(e){a._fillForm(e.form,!0,!0,!0,!0,e.loginsFound)})).then(null,e)}}}},_getPasswordFields:function(e,t){for(var n=[],r=0;r3?(n.length,null):n},_isUsernameFieldType:function(e){return e instanceof HTMLInputElement&&!!class{static inputTypeIsCompatibleWithUsername(e){const t=e.getAttribute("type")?.toLowerCase()||e.type;return["text","email","url","tel","number","search"].includes(t)||t?.includes("user")}static elementAttrsMatchRegex(e,t){if(t.test(e.id)||t.test(e.name)||t.test(e.className))return!0;const n=e.getAttribute("placeholder");return n&&t.test(n)}static hasLabelMatchingRegex(e,t){return t.test(e.labels?.[0]?.textContent)}}.inputTypeIsCompatibleWithUsername(e)},_getFormFields:function(e,t){var n,r,i=null,o=this._getPasswordFields(e,t);if(!o)return[null,null,null];for(var s=o[0].index-1;s>=0;s--){var a=e.elements[s];if(this._isUsernameFieldType(a)){i=a;break}}if(!t||1==o.length)return[i,o[0].element,null];var l=o[0].element.value,u=o[1].element.value,c=o[2]?o[2].element.value:null;if(3==o.length)if(l==u&&u==c)r=o[0].element,n=null;else if(l==u)r=o[0].element,n=o[2].element;else if(u==c)n=o[0].element,r=o[2].element;else{if(l!=c)return[null,null,null];r=o[0].element,n=o[1].element}else l==u?(r=o[0].element,n=null):(n=o[0].element,r=o[1].element);return[i,r,n]},_isAutocompleteDisabled:function(e){return!(!e||!e.hasAttribute("autocomplete")||"off"!=e.getAttribute("autocomplete").toLowerCase())},_onFormSubmit:function(e){var t=e.ownerDocument,r=t.defaultView,i=n._getPasswordOrigin(t.documentURI);if(i){var o=n._getActionOrigin(e),s=this._getFormFields(e,!0),a=s[0],l=s[1],u=s[2];if(null!=l){this._isAutocompleteDisabled(e)||this._isAutocompleteDisabled(a)||this._isAutocompleteDisabled(l)||this._isAutocompleteDisabled(u);var c=a?{name:a.name,value:a.value}:null,d={name:l.name,value:l.value};u&&(u.name,u.value),r.opener&&r.opener.top,webkit.messageHandlers.loginsManagerMessageHandler.postMessage({type:"submit",hostname:i,username:c.value,usernameField:c.name,password:d.value,passwordField:d.name,formSubmitUrl:o})}}},_fillForm:function(e,t,n,r,i,o){var s=this._getFormFields(e,!1),l=s[0],u=s[1];if(null==u)return[!1,o];if(u.disabled||u.readOnly)return[!1,o];var c=Number.MAX_VALUE,d=Number.MAX_VALUE;l&&l.maxLength>=0&&(c=l.maxLength),u.maxLength>=0&&(d=u.maxLength),o=function(e,t){var n,r,i;if(null==e)throw new TypeError("Array is null or not defined");var o=Object(e),s=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(arguments.length>1&&(n=e),r=new Array(s),i=0;i{var e={327:(e,t,n)=>{const{makeUrlAbsolute:r,parseUrl:o}=n(898);function i(e){return e.replace(/www[a-zA-Z0-9]*\./,"").replace(".co.",".").split(".").slice(0,-1).join(" ")}function l(e){return(t,n)=>{let r,o=0;for(let n=0;no&&(o=i,r=l(t))}}if(!r&&e.defaultValue&&(r=e.defaultValue(n)),r){if(e.processors)for(const t of e.processors)r=t(r,n);return r.trim&&(r=r.trim()),r}}}const a={description:{rules:[['meta[property="og:description"]',e=>e.getAttribute("content")],['meta[name="description" i]',e=>e.getAttribute("content")]]},icon:{rules:[['link[rel="apple-touch-icon"]',e=>e.getAttribute("href")],['link[rel="apple-touch-icon-precomposed"]',e=>e.getAttribute("href")],['link[rel="icon" i]',e=>e.getAttribute("href")],['link[rel="fluid-icon"]',e=>e.getAttribute("href")],['link[rel="shortcut icon"]',e=>e.getAttribute("href")],['link[rel="Shortcut Icon"]',e=>e.getAttribute("href")],['link[rel="mask-icon"]',e=>e.getAttribute("href")]],scorers:[(e,t)=>{const n=e.getAttribute("sizes");if(n){const e=n.match(/\d+/g);if(e)return e[0]}}],defaultValue:e=>"favicon.ico",processors:[(e,t)=>r(t.url,e)]},image:{rules:[['meta[property="og:image:secure_url"]',e=>e.getAttribute("content")],['meta[property="og:image:url"]',e=>e.getAttribute("content")],['meta[property="og:image"]',e=>e.getAttribute("content")],['meta[name="twitter:image"]',e=>e.getAttribute("content")],['meta[property="twitter:image"]',e=>e.getAttribute("content")],['meta[name="thumbnail"]',e=>e.getAttribute("content")]],processors:[(e,t)=>r(t.url,e)]},keywords:{rules:[['meta[name="keywords" i]',e=>e.getAttribute("content")]],processors:[(e,t)=>e.split(",").map((e=>e.trim()))]},title:{rules:[['meta[property="og:title"]',e=>e.getAttribute("content")],['meta[name="twitter:title"]',e=>e.getAttribute("content")],['meta[property="twitter:title"]',e=>e.getAttribute("content")],['meta[name="hdl"]',e=>e.getAttribute("content")],["title",e=>e.text]]},language:{rules:[["html[lang]",e=>e.getAttribute("lang")],['meta[name="language" i]',e=>e.getAttribute("content")]],processors:[(e,t)=>e.split("-")[0]]},type:{rules:[['meta[property="og:type"]',e=>e.getAttribute("content")]]},url:{rules:[["a.amp-canurl",e=>e.getAttribute("href")],['link[rel="canonical"]',e=>e.getAttribute("href")],['meta[property="og:url"]',e=>e.getAttribute("content")]],defaultValue:e=>e.url,processors:[(e,t)=>r(t.url,e)]},provider:{rules:[['meta[property="og:site_name"]',e=>e.getAttribute("content")]],defaultValue:e=>i(o(e.url))}};e.exports={buildRuleSet:l,getMetadata:function(e,t,n){const r={},o={url:t},i=n||a;return Object.keys(i).map((t=>{const n=l(i[t]);r[t]=n(e,o)})),r},getProvider:i,metadataRuleSets:a}},898:(e,t,n)=>{if(void 0!==n.g.URL)e.exports={makeUrlAbsolute:(e,t)=>new URL(t,e).href,parseUrl:e=>new URL(e).host};else{const t=n(327);e.exports={makeUrlAbsolute:(e,n)=>null===t.parse(n).host?t.resolve(e,n):n,parseUrl:e=>t.parse(e).hostname}}}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),(()=>{"use strict";Object.defineProperty(window.__firefox__,"searchQueryForField",{enumerable:!1,configurable:!1,writable:!1,value:function(){var e=document.activeElement;if("input"!==e.tagName.toLowerCase())return null;var t=e.form;if(!t||"get"!=t.method.toLowerCase())return null;var n=t.getElementsByTagName("input"),r=(n=Array.prototype.slice.call(n,0)).map((function(t){return t.name==e.name?[t.name,"{searchTerms}"].join("="):[t.name,t.value].map(encodeURIComponent).join("=")})),o=t.getElementsByTagName("select"),i=(o=Array.prototype.slice.call(o,0)).map((function(e){return[e.name,e.options[e.selectedIndex].value].map(encodeURIComponent).join("=")}));return r=r.concat(i),t.action?[t.action,r.join("&")].join("?"):null}})})(),(()=>{"use strict";const e=document.getElementById(APP_ID_TOKEN+"__firefox__visitOnce");null!=e&&e.addEventListener("click",(function(e){e.preventDefault(),webkit.messageHandlers.errorPageHelperMessageManager.postMessage({type:"certVisitOnce"})}))})(),(()=>{"use strict";const e="__firefox__find-highlight",t="__firefox__find-highlight-active",n=`.${e} {\n color: #000;\n background-color: #ffde49;\n border-radius: 1px;\n box-shadow: 0 0 0 2px #ffde49;\n transition: all 100ms ease 100ms;\n}\n.${e}.${t} {\n background-color: #f19750;\n box-shadow: 0 0 0 4px #f19750,0 1px 3px 3px rgba(0,0,0,.75);\n}`;var r="",o=null,i=null,l=-1,a=document.createElement("span");a.className=e;var c=document.createElement("style");function u(){i&&(l=(l+i.length+1)%i.length,f())}function s(){if(!i)return;let e,t=i;for(let n=0,r=t.length;n0){let e=u.substring(s,c.index);f.appendChild(document.createTextNode(e))}let n=a.cloneNode(!1);if(n.textContent=t,f.appendChild(n),r.push(n),s=e.lastIndex,d=!0,r.length>500){o=!0;break}}if(d){if(s{"use strict";const e=n(327);Object.defineProperty(window.__firefox__,"metadata",{enumerable:!1,configurable:!1,writable:!1,value:Object.freeze(new function(){this.getMetadata=function(){let t=e.getMetadata(document,document.URL);if(t.url=document.URL,!location.pathname.startsWith("/amp/"))return t;const n=document.querySelector("link[rel='canonical']");if(!n?.href)return t;try{new URL(n.href,location).protocol.match(/^https?:$/)&&(t.url=n.href)}catch(e){}return t}})})})()})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js new file mode 100644 index 000000000000..19f3822ab334 --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/MainFrameAtDocumentStart.js @@ -0,0 +1,2 @@ +/*! For license information please see MainFrameAtDocumentStart.js.LICENSE.txt */ +(()=>{var e={893:e=>{var t={unlikelyCandidates:/-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,okMaybeItsACandidate:/and|article|body|column|content|main|shadow/i};function i(e){return(!e.style||"none"!=e.style.display)&&!e.hasAttribute("hidden")&&(!e.hasAttribute("aria-hidden")||"true"!=e.getAttribute("aria-hidden")||e.className&&e.className.indexOf&&-1!==e.className.indexOf("fallback-image"))}e.exports=function(e,n={}){"function"==typeof n&&(n={visibilityChecker:n});var a={minScore:20,minContentLength:140,visibilityChecker:i};n=Object.assign(a,n);var r=e.querySelectorAll("p, pre, article"),o=e.querySelectorAll("div > br");if(o.length){var s=new Set(r);[].forEach.call(o,(function(e){s.add(e.parentNode)})),r=Array.from(s)}var l=0;return[].some.call(r,(function(e){if(!n.visibilityChecker(e))return!1;var i=e.className+" "+e.id;if(t.unlikelyCandidates.test(i)&&!t.okMaybeItsACandidate.test(i))return!1;if(e.matches("li p"))return!1;var a=e.textContent.trim().length;return!(an.minScore}))}},174:e=>{function t(e,t){if(t&&t.documentElement)e=t,t=arguments[2];else if(!e||!e.documentElement)throw new Error("First argument to Readability constructor should be a document object.");if(t=t||{},this._doc=e,this._docJSDOMParser=this._doc.firstChild.__JSDOMParser__,this._articleTitle=null,this._articleByline=null,this._articleDir=null,this._articleSiteName=null,this._attempts=[],this._debug=!!t.debug,this._maxElemsToParse=t.maxElemsToParse||this.DEFAULT_MAX_ELEMS_TO_PARSE,this._nbTopCandidates=t.nbTopCandidates||this.DEFAULT_N_TOP_CANDIDATES,this._charThreshold=t.charThreshold||this.DEFAULT_CHAR_THRESHOLD,this._classesToPreserve=this.CLASSES_TO_PRESERVE.concat(t.classesToPreserve||[]),this._keepClasses=!!t.keepClasses,this._serializer=t.serializer||function(e){return e.innerHTML},this._disableJSONLD=!!t.disableJSONLD,this._allowedVideoRegex=t.allowedVideoRegex||this.REGEXPS.videos,this._flags=this.FLAG_STRIP_UNLIKELYS|this.FLAG_WEIGHT_CLASSES|this.FLAG_CLEAN_CONDITIONALLY,this._debug){let e=function(e){if(e.nodeType==e.TEXT_NODE)return`${e.nodeName} ("${e.textContent}")`;let t=Array.from(e.attributes||[],(function(e){return`${e.name}="${e.value}"`})).join(" ");return`<${e.localName} ${t}>`};this.log=function(){if("undefined"!=typeof console){let t=Array.from(arguments,(t=>t&&t.nodeType==this.ELEMENT_NODE?e(t):t));t.unshift("Reader: (Readability)"),console.log.apply(console,t)}else if("undefined"!=typeof dump){var t=Array.prototype.map.call(arguments,(function(t){return t&&t.nodeName?e(t):t})).join(" ");dump("Reader: (Readability) "+t+"\n")}}}else this.log=function(){}}t.prototype={FLAG_STRIP_UNLIKELYS:1,FLAG_WEIGHT_CLASSES:2,FLAG_CLEAN_CONDITIONALLY:4,ELEMENT_NODE:1,TEXT_NODE:3,DEFAULT_MAX_ELEMS_TO_PARSE:0,DEFAULT_N_TOP_CANDIDATES:5,DEFAULT_TAGS_TO_SCORE:"section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),DEFAULT_CHAR_THRESHOLD:500,REGEXPS:{unlikelyCandidates:/-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,okMaybeItsACandidate:/and|article|body|column|content|main|shadow/i,positive:/article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,negative:/-ad-|hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,extraneous:/print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,byline:/byline|author|dateline|writtenby|p-author/i,replaceFonts:/<(\/?)font[^>]*>/gi,normalize:/\s{2,}/g,videos:/\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,shareElements:/(\b|_)(share|sharedaddy)(\b|_)/i,nextLink:/(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,prevLink:/(prev|earl|old|new|<|«)/i,tokenize:/\W+/g,whitespace:/^\s*$/,hasContent:/\S$/,hashUrl:/^#.+/,srcsetUrl:/(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g,b64DataUrl:/^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i,jsonLdArticleTypes:/^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/},UNLIKELY_ROLES:["menu","menubar","complementary","navigation","alert","alertdialog","dialog"],DIV_TO_P_ELEMS:new Set(["BLOCKQUOTE","DL","DIV","IMG","OL","P","PRE","TABLE","UL"]),ALTER_TO_DIV_EXCEPTIONS:["DIV","ARTICLE","SECTION","P"],PRESENTATIONAL_ATTRIBUTES:["align","background","bgcolor","border","cellpadding","cellspacing","frame","hspace","rules","style","valign","vspace"],DEPRECATED_SIZE_ATTRIBUTE_ELEMS:["TABLE","TH","TD","HR","PRE"],PHRASING_ELEMS:["ABBR","AUDIO","B","BDO","BR","BUTTON","CITE","CODE","DATA","DATALIST","DFN","EM","EMBED","I","IMG","INPUT","KBD","LABEL","MARK","MATH","METER","NOSCRIPT","OBJECT","OUTPUT","PROGRESS","Q","RUBY","SAMP","SCRIPT","SELECT","SMALL","SPAN","STRONG","SUB","SUP","TEXTAREA","TIME","VAR","WBR"],CLASSES_TO_PRESERVE:["page"],HTML_ESCAPE_MAP:{lt:"<",gt:">",amp:"&",quot:'"',apos:"'"},_postProcessContent:function(e){this._fixRelativeUris(e),this._simplifyNestedElements(e),this._keepClasses||this._cleanClasses(e)},_removeNodes:function(e,t){if(this._docJSDOMParser&&e._isLiveNodeList)throw new Error("Do not pass live node lists to _removeNodes");for(var i=e.length-1;i>=0;i--){var n=e[i],a=n.parentNode;a&&(t&&!t.call(this,n,i,e)||a.removeChild(n))}},_replaceNodeTags:function(e,t){if(this._docJSDOMParser&&e._isLiveNodeList)throw new Error("Do not pass live node lists to _replaceNodeTags");for(const i of e)this._setNodeTag(i,t)},_forEachNode:function(e,t){Array.prototype.forEach.call(e,t,this)},_findNode:function(e,t){return Array.prototype.find.call(e,t,this)},_someNode:function(e,t){return Array.prototype.some.call(e,t,this)},_everyNode:function(e,t){return Array.prototype.every.call(e,t,this)},_concatNodeLists:function(){var e=Array.prototype.slice,t=e.call(arguments),i=t.map((function(t){return e.call(t)}));return Array.prototype.concat.apply([],i)},_getAllNodesWithTag:function(e,t){return e.querySelectorAll?e.querySelectorAll(t.join(",")):[].concat.apply([],t.map((function(t){var i=e.getElementsByTagName(t);return Array.isArray(i)?i:Array.from(i)})))},_cleanClasses:function(e){var t=this._classesToPreserve,i=(e.getAttribute("class")||"").split(/\s+/).filter((function(e){return-1!=t.indexOf(e)})).join(" ");for(i?e.setAttribute("class",i):e.removeAttribute("class"),e=e.firstElementChild;e;e=e.nextElementSibling)this._cleanClasses(e)},_fixRelativeUris:function(e){var t=this._doc.baseURI,i=this._doc.documentURI;function n(e){if(t==i&&"#"==e.charAt(0))return e;try{return new URL(e,t).href}catch(e){}return e}var a=this._getAllNodesWithTag(e,["a"]);this._forEachNode(a,(function(e){var t=e.getAttribute("href");if(t)if(0===t.indexOf("javascript:"))if(1===e.childNodes.length&&e.childNodes[0].nodeType===this.TEXT_NODE){var i=this._doc.createTextNode(e.textContent);e.parentNode.replaceChild(i,e)}else{for(var a=this._doc.createElement("span");e.firstChild;)a.appendChild(e.firstChild);e.parentNode.replaceChild(a,e)}else e.setAttribute("href",n(t))}));var r=this._getAllNodesWithTag(e,["img","picture","figure","video","audio","source"]);this._forEachNode(r,(function(e){var t=e.getAttribute("src"),i=e.getAttribute("poster"),a=e.getAttribute("srcset");if(t&&e.setAttribute("src",n(t)),i&&e.setAttribute("poster",n(i)),a){var r=a.replace(this.REGEXPS.srcsetUrl,(function(e,t,i,a){return n(t)+(i||"")+a}));e.setAttribute("srcset",r)}}))},_simplifyNestedElements:function(e){for(var t=e;t;){if(t.parentNode&&["DIV","SECTION"].includes(t.tagName)&&(!t.id||!t.id.startsWith("readability"))){if(this._isElementWithoutContent(t)){t=this._removeAndGetNext(t);continue}if(this._hasSingleTagInsideElement(t,"DIV")||this._hasSingleTagInsideElement(t,"SECTION")){for(var i=t.children[0],n=0;n»] /.test(t))n=/ [\\\/>»] /.test(t),a(t=i.replace(/(.*)[\|\-\\\/>»] .*/gi,"$1"))<3&&(t=i.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi,"$1"));else if(-1!==t.indexOf(": ")){var r=this._concatNodeLists(e.getElementsByTagName("h1"),e.getElementsByTagName("h2")),o=t.trim();this._someNode(r,(function(e){return e.textContent.trim()===o}))||(a(t=i.substring(i.lastIndexOf(":")+1))<3?t=i.substring(i.indexOf(":")+1):a(i.substr(0,i.indexOf(":")))>5&&(t=i))}else if(t.length>150||t.length<15){var s=e.getElementsByTagName("h1");1===s.length&&(t=this._getInnerText(s[0]))}var l=a(t=t.trim().replace(this.REGEXPS.normalize," "));return l<=4&&(!n||l!=a(i.replace(/[\|\-\\\/>»]+/g,""))-1)&&(t=i),t},_prepDocument:function(){var e=this._doc;this._removeNodes(this._getAllNodesWithTag(e,["style"])),e.body&&this._replaceBrs(e.body),this._replaceNodeTags(this._getAllNodesWithTag(e,["font"]),"SPAN")},_nextNode:function(e){for(var t=e;t&&t.nodeType!=this.ELEMENT_NODE&&this.REGEXPS.whitespace.test(t.textContent);)t=t.nextSibling;return t},_replaceBrs:function(e){this._forEachNode(this._getAllNodesWithTag(e,["br"]),(function(e){for(var t=e.nextSibling,i=!1;(t=this._nextNode(t))&&"BR"==t.tagName;){i=!0;var n=t.nextSibling;t.parentNode.removeChild(t),t=n}if(i){var a=this._doc.createElement("p");for(e.parentNode.replaceChild(a,e),t=a.nextSibling;t;){if("BR"==t.tagName){var r=this._nextNode(t.nextSibling);if(r&&"BR"==r.tagName)break}if(!this._isPhrasingContent(t))break;var o=t.nextSibling;a.appendChild(t),t=o}for(;a.lastChild&&this._isWhitespace(a.lastChild);)a.removeChild(a.lastChild);"P"===a.parentNode.tagName&&this._setNodeTag(a.parentNode,"DIV")}}))},_setNodeTag:function(e,t){if(this.log("_setNodeTag",e,t),this._docJSDOMParser)return e.localName=t.toLowerCase(),e.tagName=t.toUpperCase(),e;for(var i=e.ownerDocument.createElement(t);e.firstChild;)i.appendChild(e.firstChild);e.parentNode.replaceChild(i,e),e.readability&&(i.readability=e.readability);for(var n=0;n!i.includes(e))).join(" ").length/n.join(" ").length:0},_checkByline:function(e,t){if(this._articleByline)return!1;if(void 0!==e.getAttribute)var i=e.getAttribute("rel"),n=e.getAttribute("itemprop");return!(!("author"===i||n&&-1!==n.indexOf("author")||this.REGEXPS.byline.test(t))||!this._isValidByline(e.textContent)||(this._articleByline=e.textContent.trim(),0))},_getNodeAncestors:function(e,t){t=t||0;for(var i=0,n=[];e.parentNode&&(n.push(e.parentNode),!t||++i!==t);)e=e.parentNode;return n},_grabArticle:function(e){this.log("**** grabArticle ****");var t=this._doc,i=null!==e;if(!(e=e||this._doc.body))return this.log("No body found in document. Abort."),null;for(var n=e.innerHTML;;){this.log("Starting grabArticle loop");var a=this._flagIsActive(this.FLAG_STRIP_UNLIKELYS),r=[],o=this._doc.documentElement;let V=!0;for(;o;){"HTML"===o.tagName&&(this._articleLang=o.getAttribute("lang"));var s=o.className+" "+o.id;if(this._isProbablyVisible(o))if("true"!=o.getAttribute("aria-modal")||"dialog"!=o.getAttribute("role"))if(this._checkByline(o,s))o=this._removeAndGetNext(o);else if(V&&this._headerDuplicatesTitle(o))this.log("Removing header: ",o.textContent.trim(),this._articleTitle.trim()),V=!1,o=this._removeAndGetNext(o);else{if(a){if(this.REGEXPS.unlikelyCandidates.test(s)&&!this.REGEXPS.okMaybeItsACandidate.test(s)&&!this._hasAncestorTag(o,"table")&&!this._hasAncestorTag(o,"code")&&"BODY"!==o.tagName&&"A"!==o.tagName){this.log("Removing unlikely candidate - "+s),o=this._removeAndGetNext(o);continue}if(this.UNLIKELY_ROLES.includes(o.getAttribute("role"))){this.log("Removing content with role "+o.getAttribute("role")+" - "+s),o=this._removeAndGetNext(o);continue}}if("DIV"!==o.tagName&&"SECTION"!==o.tagName&&"HEADER"!==o.tagName&&"H1"!==o.tagName&&"H2"!==o.tagName&&"H3"!==o.tagName&&"H4"!==o.tagName&&"H5"!==o.tagName&&"H6"!==o.tagName||!this._isElementWithoutContent(o)){if(-1!==this.DEFAULT_TAGS_TO_SCORE.indexOf(o.tagName)&&r.push(o),"DIV"===o.tagName){for(var l=null,c=o.firstChild;c;){var d=c.nextSibling;if(this._isPhrasingContent(c))null!==l?l.appendChild(c):this._isWhitespace(c)||(l=t.createElement("p"),o.replaceChild(l,c),l.appendChild(c));else if(null!==l){for(;l.lastChild&&this._isWhitespace(l.lastChild);)l.removeChild(l.lastChild);l=null}c=d}if(this._hasSingleTagInsideElement(o,"P")&&this._getLinkDensity(o)<.25){var h=o.children[0];o.parentNode.replaceChild(h,o),o=h,r.push(o)}else this._hasChildBlockElement(o)||(o=this._setNodeTag(o,"P"),r.push(o))}o=this._getNextNode(o)}else o=this._removeAndGetNext(o)}else o=this._removeAndGetNext(o);else this.log("Removing hidden node - "+s),o=this._removeAndGetNext(o)}var u=[];this._forEachNode(r,(function(e){if(e.parentNode&&void 0!==e.parentNode.tagName){var t=this._getInnerText(e);if(!(t.length<25)){var i=this._getNodeAncestors(e,5);if(0!==i.length){var n=0;n+=1,n+=t.split(",").length,n+=Math.min(Math.floor(t.length/100),3),this._forEachNode(i,(function(e,t){if(e.tagName&&e.parentNode&&void 0!==e.parentNode.tagName){if(void 0===e.readability&&(this._initializeNode(e),u.push(e)),0===t)var i=1;else i=1===t?2:3*t;e.readability.contentScore+=n/i}}))}}}}));for(var m=[],g=0,f=u.length;gN.readability.contentScore){m.splice(b,0,p),m.length>this._nbTopCandidates&&m.pop();break}}}var E,y=m[0]||null,T=!1;if(null===y||"BODY"===y.tagName){for(y=t.createElement("DIV"),T=!0;e.firstChild;)this.log("Moving child out:",e.firstChild),y.appendChild(e.firstChild);e.appendChild(y),this._initializeNode(y)}else if(y){for(var A=[],v=1;v=.75&&A.push(this._getNodeAncestors(m[v]));if(A.length>=3)for(E=y.parentNode;"BODY"!==E.tagName;){for(var S=0,C=0;C=3){y=E;break}E=E.parentNode}y.readability||this._initializeNode(y),E=y.parentNode;for(var x=y.readability.contentScore,L=x/3;"BODY"!==E.tagName;)if(E.readability){var w=E.readability.contentScore;if(wx){y=E;break}x=E.readability.contentScore,E=E.parentNode}else E=E.parentNode;for(E=y.parentNode;"BODY"!=E.tagName&&1==E.children.length;)E=(y=E).parentNode;y.readability||this._initializeNode(y)}var R=t.createElement("DIV");i&&(R.id="readability-content");for(var I=Math.max(10,.2*y.readability.contentScore),D=(E=y.parentNode).children,M=0,O=D.length;M=I)P=!0;else if("P"===k.nodeName){var B=this._getLinkDensity(k),U=this._getInnerText(k),G=U.length;(G>80&&B<.25||G<80&&G>0&&0===B&&-1!==U.search(/\.( |$)/))&&(P=!0)}}P&&(this.log("Appending node:",k),-1===this.ALTER_TO_DIV_EXCEPTIONS.indexOf(k.nodeName)&&(this.log("Altering sibling:",k,"to div."),k=this._setNodeTag(k,"DIV")),R.appendChild(k),D=E.children,M-=1,O-=1)}if(this._debug&&this.log("Article content pre-prep: "+R.innerHTML),this._prepArticle(R),this._debug&&this.log("Article content post-prep: "+R.innerHTML),T)y.id="readability-page-1",y.className="page";else{var F=t.createElement("DIV");for(F.id="readability-page-1",F.className="page";R.firstChild;)F.appendChild(R.firstChild);R.appendChild(F)}this._debug&&this.log("Article content after paging: "+R.innerHTML);var W=!0,z=this._getInnerText(R,!0).length;if(z0&&e.length<100},_unescapeHtmlEntities:function(e){if(!e)return e;var t=this.HTML_ESCAPE_MAP;return e.replace(/&(quot|amp|apos|lt|gt);/g,(function(e,i){return t[i]})).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi,(function(e,t,i){var n=parseInt(t||i,t?16:10);return String.fromCharCode(n)}))},_getJSONLD:function(e){var t,i=this._getAllNodesWithTag(e,["script"]);return this._forEachNode(i,(function(e){if(!t&&"application/ld+json"===e.getAttribute("type"))try{var i=e.textContent.replace(/^\s*\s*$/g,""),n=JSON.parse(i);if(!n["@context"]||!n["@context"].match(/^https?\:\/\/schema\.org$/))return;if(!n["@type"]&&Array.isArray(n["@graph"])&&(n=n["@graph"].find((function(e){return(e["@type"]||"").match(this.REGEXPS.jsonLdArticleTypes)}))),!n||!n["@type"]||!n["@type"].match(this.REGEXPS.jsonLdArticleTypes))return;if(t={},"string"==typeof n.name&&"string"==typeof n.headline&&n.name!==n.headline){var a=this._getArticleTitle(),r=this._textSimilarity(n.name,a)>.75,o=this._textSimilarity(n.headline,a)>.75;t.title=o&&!r?n.headline:n.name}else"string"==typeof n.name?t.title=n.name.trim():"string"==typeof n.headline&&(t.title=n.headline.trim());return n.author&&("string"==typeof n.author.name?t.byline=n.author.name.trim():Array.isArray(n.author)&&n.author[0]&&"string"==typeof n.author[0].name&&(t.byline=n.author.filter((function(e){return e&&"string"==typeof e.name})).map((function(e){return e.name.trim()})).join(", "))),"string"==typeof n.description&&(t.excerpt=n.description.trim()),void(n.publisher&&"string"==typeof n.publisher.name&&(t.siteName=n.publisher.name.trim()))}catch(e){this.log(e.message)}})),t||{}},_getArticleMetadata:function(e){var t={},i={},n=this._doc.getElementsByTagName("meta"),a=/\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi,r=/^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i;return this._forEachNode(n,(function(e){var t=e.getAttribute("name"),n=e.getAttribute("property"),o=e.getAttribute("content");if(o){var s=null,l=null;n&&(s=n.match(a))&&(l=s[0].toLowerCase().replace(/\s/g,""),i[l]=o.trim()),!s&&t&&r.test(t)&&(l=t,o&&(l=l.toLowerCase().replace(/\s/g,"").replace(/\./g,":"),i[l]=o.trim()))}})),t.title=e.title||i["dc:title"]||i["dcterm:title"]||i["og:title"]||i["weibo:article:title"]||i["weibo:webpage:title"]||i.title||i["twitter:title"],t.title||(t.title=this._getArticleTitle()),t.byline=e.byline||i["dc:creator"]||i["dcterm:creator"]||i.author,t.excerpt=e.excerpt||i["dc:description"]||i["dcterm:description"]||i["og:description"]||i["weibo:article:description"]||i["weibo:webpage:description"]||i.description||i["twitter:description"],t.siteName=e.siteName||i["og:site_name"],t.title=this._unescapeHtmlEntities(t.title),t.byline=this._unescapeHtmlEntities(t.byline),t.excerpt=this._unescapeHtmlEntities(t.excerpt),t.siteName=this._unescapeHtmlEntities(t.siteName),t},_isSingleImage:function(e){return"IMG"===e.tagName||1===e.children.length&&""===e.textContent.trim()&&this._isSingleImage(e.children[0])},_unwrapNoscriptImages:function(e){var t=Array.from(e.getElementsByTagName("img"));this._forEachNode(t,(function(e){for(var t=0;t0&&a>i)return!1;if(e.parentNode.tagName===t&&(!n||n(e.parentNode)))return!0;e=e.parentNode,a++}return!1},_getRowAndColumnCount:function(e){for(var t=0,i=0,n=e.getElementsByTagName("tr"),a=0;a0)n._readabilityDataTable=!0;else if(["col","colgroup","tfoot","thead","th"].some((function(e){return!!n.getElementsByTagName(e)[0]})))this.log("Data table because found data-y descendant"),n._readabilityDataTable=!0;else if(n.getElementsByTagName("table")[0])n._readabilityDataTable=!1;else{var r=this._getRowAndColumnCount(n);r.rows>=10||r.columns>4?n._readabilityDataTable=!0:n._readabilityDataTable=r.rows*r.columns>10}}else n._readabilityDataTable=!1;else n._readabilityDataTable=!1}},_fixLazyImages:function(e){this._forEachNode(this._getAllNodesWithTag(e,["img","picture","figure"]),(function(e){if(e.src&&this.REGEXPS.b64DataUrl.test(e.src)){if("image/svg+xml"===this.REGEXPS.b64DataUrl.exec(e.src)[1])return;for(var t=!1,i=0;in+=this._getInnerText(e,!0).length)),n/i},_cleanConditionally:function(e,t){this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)&&this._removeNodes(this._getAllNodesWithTag(e,[t]),(function(e){var i=function(e){return e._readabilityDataTable},n="ul"===t||"ol"===t;if(!n){var a=0,r=this._getAllNodesWithTag(e,["ul","ol"]);this._forEachNode(r,(e=>a+=this._getInnerText(e).length)),n=a/this._getInnerText(e).length>.9}if("table"===t&&i(e))return!1;if(this._hasAncestorTag(e,"table",-1,i))return!1;if(this._hasAncestorTag(e,"code"))return!1;var o=this._getClassWeight(e);if(this.log("Cleaning Conditionally",e),o+0<0)return!0;if(this._getCharCount(e,",")<10){for(var s=e.getElementsByTagName("p").length,l=e.getElementsByTagName("img").length,c=e.getElementsByTagName("li").length-100,d=e.getElementsByTagName("input").length,h=this._getTextDensity(e,["h1","h2","h3","h4","h5","h6"]),u=0,m=this._getAllNodesWithTag(e,["object","embed","iframe"]),g=0;g1&&s/l<.5&&!this._hasAncestorTag(e,"figure")||!n&&c>s||d>Math.floor(s/3)||!n&&h<.9&&_<25&&(0===l||l>2)&&!this._hasAncestorTag(e,"figure")||!n&&o<25&&p>.2||o>=25&&p>.5||1===u&&_<75||u>1;if(n&&b){for(var N=0;N1)return b;if(l==e.getElementsByTagName("li").length)return!1}return b}return!1}))},_cleanMatchedNodes:function(e,t){for(var i=this._getNextNode(e,!0),n=this._getNextNode(e);n&&n!=i;)n=t.call(this,n,n.className+" "+n.id)?this._removeAndGetNext(n):this._getNextNode(n)},_cleanHeaders:function(e){let t=this._getAllNodesWithTag(e,["h1","h2"]);this._removeNodes(t,(function(e){let t=this._getClassWeight(e)<0;return t&&this.log("Removing header with low class weight:",e),t}))},_headerDuplicatesTitle:function(e){if("H1"!=e.tagName&&"H2"!=e.tagName)return!1;var t=this._getInnerText(e,!1);return this.log("Evaluating similarity of header:",t,this._articleTitle),this._textSimilarity(this._articleTitle,t)>.75},_flagIsActive:function(e){return(this._flags&e)>0},_removeFlag:function(e){this._flags=this._flags&~e},_isProbablyVisible:function(e){return(!e.style||"none"!=e.style.display)&&!e.hasAttribute("hidden")&&(!e.hasAttribute("aria-hidden")||"true"!=e.getAttribute("aria-hidden")||e.className&&e.className.indexOf&&-1!==e.className.indexOf("fallback-image"))},parse:function(){if(this._maxElemsToParse>0){var e=this._doc.getElementsByTagName("*").length;if(e>this._maxElemsToParse)throw new Error("Aborting parsing document; "+e+" elements found")}this._unwrapNoscriptImages(this._doc);var t=this._disableJSONLD?{}:this._getJSONLD(this._doc);this._removeScripts(this._doc),this._prepDocument();var i=this._getArticleMetadata(t);this._articleTitle=i.title;var n=this._grabArticle();if(!n)return null;if(this.log("Grabbed: "+n.innerHTML),this._postProcessContent(n),!i.excerpt){var a=n.getElementsByTagName("p");a.length>0&&(i.excerpt=a[0].textContent.trim())}var r=n.textContent;return{title:this._articleTitle,byline:i.byline||this._articleByline,dir:this._articleDir,lang:this._articleLang,content:this._serializer(n),textContent:r,length:r.length,excerpt:i.excerpt,siteName:i.siteName||this._articleSiteName}}},e.exports=t},107:(e,t,i)=>{var n=i(174),a=i(893);e.exports={Readability:n,isProbablyReaderable:a}},856:function(e){e.exports=function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:i,getPrototypeOf:n,getOwnPropertyDescriptor:a}=Object;let{freeze:r,seal:o,create:s}=Object,{apply:l,construct:c}="undefined"!=typeof Reflect&&Reflect;r||(r=function(e){return e}),o||(o=function(e){return e}),l||(l=function(e,t,i){return e.apply(t,i)}),c||(c=function(e,t){return new e(...t)});const d=T(Array.prototype.forEach),h=T(Array.prototype.pop),u=T(Array.prototype.push),m=T(String.prototype.toLowerCase),g=T(String.prototype.toString),f=T(String.prototype.match),p=T(String.prototype.replace),_=T(String.prototype.indexOf),b=T(String.prototype.trim),N=T(RegExp.prototype.test),E=(y=TypeError,function(){for(var e=arguments.length,t=new Array(e),i=0;i1?i-1:0),a=1;a2&&void 0!==arguments[2]?arguments[2]:m;t&&t(e,null);let r=n.length;for(;r--;){let t=n[r];if("string"==typeof t){const e=a(t);e!==t&&(i(n)||(n[r]=e),t=e)}e[t]=!0}return e}function v(t){const i=s(null);for(const[n,r]of e(t))void 0!==a(t,n)&&(i[n]=r);return i}function S(e,t){for(;null!==e;){const i=a(e,t);if(i){if(i.get)return T(i.get);if("function"==typeof i.value)return T(i.value)}e=n(e)}return function(e){return console.warn("fallback value for",e),null}}const C=r(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),x=r(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),L=r(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),w=r(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),R=r(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),I=r(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),D=r(["#text"]),M=r(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),O=r(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),k=r(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),P=r(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),H=o(/\{\{[\w\W]*|[\w\W]*\}\}/gm),B=o(/<%[\w\W]*|[\w\W]*%>/gm),U=o(/\${[\w\W]*}/gm),G=o(/^data-[\-\w.\u00B7-\uFFFF]/),F=o(/^aria-[\-\w]+$/),W=o(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),z=o(/^(?:\w+script|data):/i),j=o(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),V=o(/^html$/i);var X=Object.freeze({__proto__:null,MUSTACHE_EXPR:H,ERB_EXPR:B,TMPLIT_EXPR:U,DATA_ATTR:G,ARIA_ATTR:F,IS_ALLOWED_URI:W,IS_SCRIPT_OR_DATA:z,ATTR_WHITESPACE:j,DOCTYPE_NAME:V});const Y=function(){return"undefined"==typeof window?null:window},$=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let i=null;const n="data-tt-policy-suffix";t&&t.hasAttribute(n)&&(i=t.getAttribute(n));const a="dompurify"+(i?"#"+i:"");try{return e.createPolicy(a,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};return function t(){let i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Y();const n=e=>t(e);if(n.version="3.0.6",n.removed=[],!i||!i.document||9!==i.document.nodeType)return n.isSupported=!1,n;let{document:a}=i;const o=a,l=o.currentScript,{DocumentFragment:c,HTMLTemplateElement:y,Node:T,Element:H,NodeFilter:B,NamedNodeMap:U=i.NamedNodeMap||i.MozNamedAttrMap,HTMLFormElement:G,DOMParser:F,trustedTypes:z}=i,j=H.prototype,q=S(j,"cloneNode"),K=S(j,"nextSibling"),J=S(j,"childNodes"),Z=S(j,"parentNode");if("function"==typeof y){const e=a.createElement("template");e.content&&e.content.ownerDocument&&(a=e.content.ownerDocument)}let Q,ee="";const{implementation:te,createNodeIterator:ie,createDocumentFragment:ne,getElementsByTagName:ae}=a,{importNode:re}=o;let oe={};n.isSupported="function"==typeof e&&"function"==typeof Z&&te&&void 0!==te.createHTMLDocument;const{MUSTACHE_EXPR:se,ERB_EXPR:le,TMPLIT_EXPR:ce,DATA_ATTR:de,ARIA_ATTR:he,IS_SCRIPT_OR_DATA:ue,ATTR_WHITESPACE:me}=X;let{IS_ALLOWED_URI:ge}=X,fe=null;const pe=A({},[...C,...x,...L,...R,...D]);let _e=null;const be=A({},[...M,...O,...k,...P]);let Ne=Object.seal(s(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ee=null,ye=null,Te=!0,Ae=!0,ve=!1,Se=!0,Ce=!1,xe=!1,Le=!1,we=!1,Re=!1,Ie=!1,De=!1,Me=!0,Oe=!1;const ke="user-content-";let Pe=!0,He=!1,Be={},Ue=null;const Ge=A({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Fe=null;const We=A({},["audio","video","img","source","image","track"]);let ze=null;const je=A({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",Xe="http://www.w3.org/2000/svg",Ye="http://www.w3.org/1999/xhtml";let $e=Ye,qe=!1,Ke=null;const Je=A({},[Ve,Xe,Ye],g);let Ze=null;const Qe=["application/xhtml+xml","text/html"],et="text/html";let tt=null,it=null;const nt=a.createElement("form"),at=function(e){return e instanceof RegExp||e instanceof Function},rt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!it||it!==e){if(e&&"object"==typeof e||(e={}),e=v(e),Ze=Ze=-1===Qe.indexOf(e.PARSER_MEDIA_TYPE)?et:e.PARSER_MEDIA_TYPE,tt="application/xhtml+xml"===Ze?g:m,fe="ALLOWED_TAGS"in e?A({},e.ALLOWED_TAGS,tt):pe,_e="ALLOWED_ATTR"in e?A({},e.ALLOWED_ATTR,tt):be,Ke="ALLOWED_NAMESPACES"in e?A({},e.ALLOWED_NAMESPACES,g):Je,ze="ADD_URI_SAFE_ATTR"in e?A(v(je),e.ADD_URI_SAFE_ATTR,tt):je,Fe="ADD_DATA_URI_TAGS"in e?A(v(We),e.ADD_DATA_URI_TAGS,tt):We,Ue="FORBID_CONTENTS"in e?A({},e.FORBID_CONTENTS,tt):Ge,Ee="FORBID_TAGS"in e?A({},e.FORBID_TAGS,tt):{},ye="FORBID_ATTR"in e?A({},e.FORBID_ATTR,tt):{},Be="USE_PROFILES"in e&&e.USE_PROFILES,Te=!1!==e.ALLOW_ARIA_ATTR,Ae=!1!==e.ALLOW_DATA_ATTR,ve=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Se=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Ce=e.SAFE_FOR_TEMPLATES||!1,xe=e.WHOLE_DOCUMENT||!1,Re=e.RETURN_DOM||!1,Ie=e.RETURN_DOM_FRAGMENT||!1,De=e.RETURN_TRUSTED_TYPE||!1,we=e.FORCE_BODY||!1,Me=!1!==e.SANITIZE_DOM,Oe=e.SANITIZE_NAMED_PROPS||!1,Pe=!1!==e.KEEP_CONTENT,He=e.IN_PLACE||!1,ge=e.ALLOWED_URI_REGEXP||W,$e=e.NAMESPACE||Ye,Ne=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&at(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ne.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&at(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ne.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ne.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Ce&&(Ae=!1),Ie&&(Re=!0),Be&&(fe=A({},[...D]),_e=[],!0===Be.html&&(A(fe,C),A(_e,M)),!0===Be.svg&&(A(fe,x),A(_e,O),A(_e,P)),!0===Be.svgFilters&&(A(fe,L),A(_e,O),A(_e,P)),!0===Be.mathMl&&(A(fe,R),A(_e,k),A(_e,P))),e.ADD_TAGS&&(fe===pe&&(fe=v(fe)),A(fe,e.ADD_TAGS,tt)),e.ADD_ATTR&&(_e===be&&(_e=v(_e)),A(_e,e.ADD_ATTR,tt)),e.ADD_URI_SAFE_ATTR&&A(ze,e.ADD_URI_SAFE_ATTR,tt),e.FORBID_CONTENTS&&(Ue===Ge&&(Ue=v(Ue)),A(Ue,e.FORBID_CONTENTS,tt)),Pe&&(fe["#text"]=!0),xe&&A(fe,["html","head","body"]),fe.table&&(A(fe,["tbody"]),delete Ee.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw E('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw E('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');Q=e.TRUSTED_TYPES_POLICY,ee=Q.createHTML("")}else void 0===Q&&(Q=$(z,l)),null!==Q&&"string"==typeof ee&&(ee=Q.createHTML(""));r&&r(e),it=e}},ot=A({},["mi","mo","mn","ms","mtext"]),st=A({},["foreignobject","desc","title","annotation-xml"]),lt=A({},["title","style","font","a","script"]),ct=A({},x);A(ct,L),A(ct,w);const dt=A({},R);A(dt,I);const ht=function(e){let t=Z(e);t&&t.tagName||(t={namespaceURI:$e,tagName:"template"});const i=m(e.tagName),n=m(t.tagName);return!!Ke[e.namespaceURI]&&(e.namespaceURI===Xe?t.namespaceURI===Ye?"svg"===i:t.namespaceURI===Ve?"svg"===i&&("annotation-xml"===n||ot[n]):Boolean(ct[i]):e.namespaceURI===Ve?t.namespaceURI===Ye?"math"===i:t.namespaceURI===Xe?"math"===i&&st[n]:Boolean(dt[i]):e.namespaceURI===Ye?!(t.namespaceURI===Xe&&!st[n])&&!(t.namespaceURI===Ve&&!ot[n])&&!dt[i]&&(lt[i]||!ct[i]):!("application/xhtml+xml"!==Ze||!Ke[e.namespaceURI]))},ut=function(e){u(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},mt=function(e,t){try{u(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){u(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!_e[e])if(Re||Ie)try{ut(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},gt=function(e){let t=null,i=null;if(we)e=""+e;else{const t=f(e,/^[\r\n\t ]+/);i=t&&t[0]}"application/xhtml+xml"===Ze&&$e===Ye&&(e=''+e+"");const n=Q?Q.createHTML(e):e;if($e===Ye)try{t=(new F).parseFromString(n,Ze)}catch(e){}if(!t||!t.documentElement){t=te.createDocument($e,"template",null);try{t.documentElement.innerHTML=qe?ee:n}catch(e){}}const r=t.body||t.documentElement;return e&&i&&r.insertBefore(a.createTextNode(i),r.childNodes[0]||null),$e===Ye?ae.call(t,xe?"html":"body")[0]:xe?t.documentElement:r},ft=function(e){return ie.call(e.ownerDocument||e,e,B.SHOW_ELEMENT|B.SHOW_COMMENT|B.SHOW_TEXT,null)},pt=function(e){return e instanceof G&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof U)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},_t=function(e){return"function"==typeof T&&e instanceof T},bt=function(e,t,i){oe[e]&&d(oe[e],(e=>{e.call(n,t,i,it)}))},Nt=function(e){let t=null;if(bt("beforeSanitizeElements",e,null),pt(e))return ut(e),!0;const i=tt(e.nodeName);if(bt("uponSanitizeElement",e,{tagName:i,allowedTags:fe}),e.hasChildNodes()&&!_t(e.firstElementChild)&&N(/<[/\w]/g,e.innerHTML)&&N(/<[/\w]/g,e.textContent))return ut(e),!0;if(!fe[i]||Ee[i]){if(!Ee[i]&&yt(i)){if(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,i))return!1;if(Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(i))return!1}if(Pe&&!Ue[i]){const t=Z(e)||e.parentNode,i=J(e)||e.childNodes;if(i&&t)for(let n=i.length-1;n>=0;--n)t.insertBefore(q(i[n],!0),K(e))}return ut(e),!0}return e instanceof H&&!ht(e)?(ut(e),!0):"noscript"!==i&&"noembed"!==i&&"noframes"!==i||!N(/<\/no(script|embed|frames)/i,e.innerHTML)?(Ce&&3===e.nodeType&&(t=e.textContent,d([se,le,ce],(e=>{t=p(t,e," ")})),e.textContent!==t&&(u(n.removed,{element:e.cloneNode()}),e.textContent=t)),bt("afterSanitizeElements",e,null),!1):(ut(e),!0)},Et=function(e,t,i){if(Me&&("id"===t||"name"===t)&&(i in a||i in nt))return!1;if(Ae&&!ye[t]&&N(de,t));else if(Te&&N(he,t));else if(!_e[t]||ye[t]){if(!(yt(e)&&(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,e)||Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(e))&&(Ne.attributeNameCheck instanceof RegExp&&N(Ne.attributeNameCheck,t)||Ne.attributeNameCheck instanceof Function&&Ne.attributeNameCheck(t))||"is"===t&&Ne.allowCustomizedBuiltInElements&&(Ne.tagNameCheck instanceof RegExp&&N(Ne.tagNameCheck,i)||Ne.tagNameCheck instanceof Function&&Ne.tagNameCheck(i))))return!1}else if(ze[t]);else if(N(ge,p(i,me,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==_(i,"data:")||!Fe[e])if(ve&&!N(ue,p(i,me,"")));else if(i)return!1;return!0},yt=function(e){return e.indexOf("-")>0},Tt=function(e){bt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const i={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:_e};let a=t.length;for(;a--;){const r=t[a],{name:o,namespaceURI:s,value:l}=r,c=tt(o);let u="value"===o?l:b(l);if(i.attrName=c,i.attrValue=u,i.keepAttr=!0,i.forceKeepAttr=void 0,bt("uponSanitizeAttribute",e,i),u=i.attrValue,i.forceKeepAttr)continue;if(mt(o,e),!i.keepAttr)continue;if(!Se&&N(/\/>/i,u)){mt(o,e);continue}Ce&&d([se,le,ce],(e=>{u=p(u,e," ")}));const m=tt(e.nodeName);if(Et(m,c,u)){if(!Oe||"id"!==c&&"name"!==c||(mt(o,e),u=ke+u),Q&&"object"==typeof z&&"function"==typeof z.getAttributeType)if(s);else switch(z.getAttributeType(m,c)){case"TrustedHTML":u=Q.createHTML(u);break;case"TrustedScriptURL":u=Q.createScriptURL(u)}try{s?e.setAttributeNS(s,o,u):e.setAttribute(o,u),h(n.removed)}catch(e){}}}bt("afterSanitizeAttributes",e,null)},At=function e(t){let i=null;const n=ft(t);for(bt("beforeSanitizeShadowDOM",t,null);i=n.nextNode();)bt("uponSanitizeShadowNode",i,null),Nt(i)||(i.content instanceof c&&e(i.content),Tt(i));bt("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,a=null,r=null,s=null;if(qe=!e,qe&&(e="\x3c!--\x3e"),"string"!=typeof e&&!_t(e)){if("function"!=typeof e.toString)throw E("toString is not a function");if("string"!=typeof(e=e.toString()))throw E("dirty is not a string, aborting")}if(!n.isSupported)return e;if(Le||rt(t),n.removed=[],"string"==typeof e&&(He=!1),He){if(e.nodeName){const t=tt(e.nodeName);if(!fe[t]||Ee[t])throw E("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof T)i=gt("\x3c!----\x3e"),a=i.ownerDocument.importNode(e,!0),1===a.nodeType&&"BODY"===a.nodeName||"HTML"===a.nodeName?i=a:i.appendChild(a);else{if(!Re&&!Ce&&!xe&&-1===e.indexOf("<"))return Q&&De?Q.createHTML(e):e;if(i=gt(e),!i)return Re?null:De?ee:""}i&&we&&ut(i.firstChild);const l=ft(He?e:i);for(;r=l.nextNode();)Nt(r)||(r.content instanceof c&&At(r.content),Tt(r));if(He)return e;if(Re){if(Ie)for(s=ne.call(i.ownerDocument);i.firstChild;)s.appendChild(i.firstChild);else s=i;return(_e.shadowroot||_e.shadowrootmode)&&(s=re.call(o,s,!0)),s}let h=xe?i.outerHTML:i.innerHTML;return xe&&fe["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&N(V,i.ownerDocument.doctype.name)&&(h="\n"+h),Ce&&d([se,le,ce],(e=>{h=p(h,e," ")})),Q&&De?Q.createHTML(h):h},n.setConfig=function(){rt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Le=!0},n.clearConfig=function(){it=null,Le=!1},n.isValidAttribute=function(e,t,i){it||rt({});const n=tt(e),a=tt(t);return Et(n,a,i)},n.addHook=function(e,t){"function"==typeof t&&(oe[e]=oe[e]||[],u(oe[e],t))},n.removeHook=function(e){if(oe[e])return h(oe[e])},n.removeHooks=function(e){oe[e]&&(oe[e]=[])},n.removeAllHooks=function(){oe={}},n}()}()}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n].call(r.exports,r,r.exports,i),r.exports}(()=>{"use strict";Object.defineProperty(window.__firefox__,"NightMode",{enumerable:!1,configurable:!1,writable:!1,value:{enabled:!1}});const e="brightness(80%) invert(100%) hue-rotate(180deg)",t=`html {\n -webkit-filter: hue-rotate(180deg) invert(100%) !important;\n}\niframe,img,video {\n -webkit-filter: ${e} !important;\n}`;var i;function n(){return i||((i=document.createElement("style")).type="text/css",i.appendChild(document.createTextNode(t)),i)}function a(t){t.querySelectorAll('[style*="background"]').forEach((function(t){(t.style.backgroundImage||"").startsWith("url")&&function(t){o.push(t),t.__firefox__NightMode_originalFilter=t.style.webkitFilter,t.style.webkitFilter=e}(t)}))}function r(e){e.style.webkitFilter=e.__firefox__NightMode_originalFilter,delete e.__firefox__NightMode_originalFilter}var o=null,s=new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){e.nodeType===Node.ELEMENT_NODE&&a(e)}))}))}));Object.defineProperty(window.__firefox__.NightMode,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(e){if(e!==window.__firefox__.NightMode.enabled){window.__firefox__.NightMode.enabled=e;var t=n();if(e)return o=[],document.documentElement.appendChild(t),a(document),void s.observe(document.documentElement,{childList:!0,subtree:!0});s.disconnect(),o.forEach(r);var i=t.parentNode;i&&i.removeChild(t),o=null,"rgba(0, 0, 0, 0)"===getComputedStyle(document.documentElement)["background-color"]&&(document.documentElement.style.backgroundColor="#fff")}}}),window.addEventListener("DOMContentLoaded",(function(){window.__firefox__.NightMode.setEnabled(window.__firefox__.NightMode.enabled)}))})(),(()=>{"use strict";Object.defineProperty(window.__firefox__,"NoImageMode",{enumerable:!1,configurable:!1,writable:!1,value:{enabled:!1}});const e="__firefox__NoImageMode";Object.defineProperty(window.__firefox__.NoImageMode,"setEnabled",{enumerable:!1,configurable:!1,writable:!1,value:function(t){if(t!==window.__firefox__.NoImageMode.enabled)if(window.__firefox__.NoImageMode.enabled=t,t)!function(){var t="*{background-image:none !important;}img,iframe{visibility:hidden !important;}",i=document.getElementById(e);if(i)i.innerHTML=t;else{var n=document.createElement("style");n.type="text/css",n.id=e,n.appendChild(document.createTextNode(t)),document.documentElement.appendChild(n)}}();else{var i=document.getElementById(e);i&&i.remove(),[].slice.apply(document.getElementsByTagName("img")).forEach((function(e){var t=e.src;e.src="",e.src=t})),[].slice.apply(document.querySelectorAll('[style*="background"]')).forEach((function(e){var t=e.style.backgroundImage;e.style.backgroundImage="none",e.style.backgroundImage=t})),[].slice.apply(document.styleSheets).forEach((function(e){[].slice.apply(e.rules||[]).forEach((function(e){var t=e.style;if(t){var i=t.backgroundImage;t.backgroundImage=i}}))}))}}}),window.addEventListener("DOMContentLoaded",(function(){window.__firefox__.NoImageMode.setEnabled(window.__firefox__.NoImageMode.enabled)}))})(),(()=>{"use strict";var e=null,t=null;const n=/^http:\/\/localhost:\d+\/reader-mode\/page/;function a(e){t&&t.theme&&document.body.classList.remove(t.theme),e&&e.theme&&document.body.classList.add(e.theme),t&&t.fontSize&&document.body.classList.remove("font-size"+t.fontSize),e&&e.fontSize&&document.body.classList.add("font-size"+e.fontSize),t&&t.fontType&&document.body.classList.remove(t.fontType),e&&e.fontType&&document.body.classList.add(e.fontType),t=e}function r(e){return"string"!=typeof e?"":e.replace(/\&/g,"&").replace(/\/g,">").replace(/\"/g,""").replace(/\'/g,"'")}Object.defineProperty(window.__firefox__,"reader",{enumerable:!1,configurable:!1,writable:!1,value:Object.freeze({checkReadability:function(){setTimeout((function(){if(document.location.href.match(n))webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Active"});else{if(("http:"===document.location.protocol||"https:"===document.location.protocol)&&"/"!==document.location.pathname){if(e&&e.content)return webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Available"}),void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderContentParsed",Value:e});var{Readability:t}=i(107),a={spec:document.location.href,host:document.location.host,prePath:document.location.protocol+"//"+document.location.host,scheme:document.location.protocol.substr(0,document.location.protocol.indexOf(":")),pathBase:document.location.protocol+"//"+document.location.host+location.pathname.substr(0,location.pathname.lastIndexOf("/")+1)},o=(new XMLSerializer).serializeToString(document);if(o.indexOf("-1)return void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"});const n=i(856).sanitize(o,{WHOLE_DOCUMENT:!0});var s=new t(a,(new DOMParser).parseFromString(n,"text/html"),{debug:!1});return(e=s.parse())?(e.title=r(e.title),e.byline=r(e.byline),webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:null!==e?"Available":"Unavailable"}),void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderContentParsed",Value:e})):void webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"})}webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderModeStateChange",Value:"Unavailable"})}}),100)},readerize:function(){return e},setStyle:a})}),window.addEventListener("load",(function(e){document.location.href.match(n)&&(a(JSON.parse(document.body.getAttribute("data-readerStyle"))),function(){var e=document.getElementById("reader-message");e&&(e.style.display="none");var t=document.getElementById("reader-header");t&&(t.style.display="block");var i=document.getElementById("reader-content");i&&(i.style.display="block")}(),function(){var e=document.getElementById("reader-content");if(e)for(var t=window.innerWidth,i=e.offsetWidth,n=t+"px !important",a=function(e){e._originalWidth||(e._originalWidth=e.offsetWidth);var a=e._originalWidth;a.55*t&&(a=t);var r=Math.max((i-t)/2,(i-a)/2)+"px !important",o="max-width: "+n+";width: "+a+"px !important;margin-left: "+r+";margin-right: "+r+";";e.style.cssText=o},r=document.querySelectorAll(".content p > img:only-child, .content p > a:only-child > img:only-child, .content .wp-caption img, .content figure img"),o=r.length;--o>=0;){var s=r[o];s.width>0?a(s):s.onload=function(){a(s)}}}())})),window.addEventListener("pageshow",(function(e){document.location.href.match(n)&&webkit.messageHandlers.readerModeMessageHandler.postMessage({Type:"ReaderPageEvent",Value:"PageShow"})}))})()})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js b/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js new file mode 100644 index 000000000000..34092295e7ff --- /dev/null +++ b/SampleBrowser/SampleBrowser/Engine/Scripts/WebcompatAllFramesAtDocumentStart.js @@ -0,0 +1 @@ +(()=>{var e=!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled),t=void 0!==HTMLVideoElement.prototype.webkitEnterFullscreen;e||!t||/mobile/i.test(navigator.userAgent)||(HTMLElement.prototype.requestFullscreen=function(){if(void 0!==this.webkitRequestFullscreen)return this.webkitRequestFullscreen(),!0;if(void 0!==this.webkitEnterFullscreen)return this.webkitEnterFullscreen(),!0;var e=this.querySelector("video");return void 0!==e&&(e.webkitEnterFullscreen(),!0)},Object.defineProperty(document,"fullscreenEnabled",{get:function(){return!0}}),Object.defineProperty(document.documentElement,"fullscreenEnabled",{get:function(){return!0}}))})(); \ No newline at end of file diff --git a/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift b/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift index 2a3a24a07c1d..ab9a640a744c 100644 --- a/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/Browser/BrowserViewController.swift @@ -9,10 +9,16 @@ protocol NavigationDelegate: AnyObject { func onURLChange(url: String) func onLoadingStateChange(loading: Bool) func onNavigationStateChange(canGoBack: Bool, canGoForward: Bool) + + func onFindInPage(selected: String) + func onFindInPage(currentResult: Int) + func onFindInPage(totalResults: Int) } // Holds different type of browser views, communicating through protocols with them -class BrowserViewController: UIViewController, EngineSessionDelegate { +class BrowserViewController: UIViewController, + EngineSessionDelegate, + FindInPageHelperDelegate { weak var navigationDelegate: NavigationDelegate? private lazy var progressView: UIProgressView = .build { _ in } private var engineSession: EngineSession! @@ -27,6 +33,8 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { self.engineView = engineProvider.view self.urlFormatter = urlFormatter super.init(nibName: nil, bundle: nil) + + engineSession.findInPageDelegate = self } required init?(coder: NSCoder) { @@ -98,6 +106,14 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { engineSession.scrollToTop() } + func findInPage(text: String, function: FindInPageFunction) { + engineSession.findInPage(text: text, function: function) + } + + func findInPageDone() { + engineSession.findInPageDone() + } + // MARK: - Search func loadUrlOrSearch(_ searchTerm: SearchTerm) { @@ -145,10 +161,20 @@ class BrowserViewController: UIViewController, EngineSessionDelegate { // MARK: - EngineSessionDelegate Menu items func findInPage(with selection: String) { - // FXIOS-8087: Handle find in page in WebEngine + navigationDelegate?.onFindInPage(selected: selection) } func search(with selection: String) { loadUrlOrSearch(SearchTerm(term: selection)) } + + // MARK: - FindInPageHelperDelegate + + func findInPageHelper(didUpdateCurrentResult currentResult: Int) { + navigationDelegate?.onFindInPage(currentResult: currentResult) + } + + func findInPageHelper(didUpdateTotalResults totalResults: Int) { + navigationDelegate?.onFindInPage(totalResults: totalResults) + } } diff --git a/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift b/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift new file mode 100644 index 000000000000..b0d5287888d7 --- /dev/null +++ b/SampleBrowser/SampleBrowser/UI/Components/FindInPage.swift @@ -0,0 +1,191 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Common +import UIKit + +protocol FindInPageBarDelegate: AnyObject { + func findInPage(_ findInPage: FindInPageBar, textChanged text: String) + func findInPage(_ findInPage: FindInPageBar, findPreviousWithText text: String) + func findInPage(_ findInPage: FindInPageBar, findNextWithText text: String) + func findInPageDidPressClose(_ findInPage: FindInPageBar) +} + +class FindInPageBar: UIView, UITextFieldDelegate, ThemeApplicable { + private struct UX { + static let fontSize: CGFloat = 16 + static let totalResultsMax = 500 + } + + weak var delegate: FindInPageBarDelegate? + + private lazy var topBorder: UIView = .build() + + private lazy var searchText: UITextField = .build { textField in + textField.addTarget(self, action: #selector(self.didTextChange), for: .editingChanged) + textField.font = DefaultDynamicFontHelper.preferredFont(withTextStyle: .callout, size: UX.fontSize) + textField.setContentHuggingPriority(.defaultLow, for: .horizontal) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.adjustsFontForContentSizeCategory = true + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.inputAssistantItem.leadingBarButtonGroups = [] + textField.inputAssistantItem.trailingBarButtonGroups = [] + textField.enablesReturnKeyAutomatically = true + textField.returnKeyType = .search + textField.delegate = self + } + + private lazy var matchCountView: UILabel = .build { label in + label.font = DefaultDynamicFontHelper.preferredFont(withTextStyle: .callout, size: UX.fontSize) + label.isHidden = true + label.setContentHuggingPriority(.defaultHigh, for: .horizontal) + label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + label.adjustsFontForContentSizeCategory = true + } + + private lazy var previousButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Large.chevronUp), for: .normal) + button.addTarget(self, action: #selector(self.didFindPrevious), for: .touchUpInside) + } + + private lazy var nextButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Large.chevronDown), for: .normal) + button.addTarget(self, action: #selector(self.didFindNext), for: .touchUpInside) + } + + private lazy var closeButton: UIButton = .build { button in + button.setImage(UIImage(named: StandardImageIdentifiers.Medium.cross), for: .normal) + button.addTarget(self, action: #selector(self.didPressClose), for: .touchUpInside) + } + + var currentResult = 0 { + didSet { + if totalResults > UX.totalResultsMax { + matchCountView.text = "\(currentResult)/\(UX.totalResultsMax)+" + } else { + matchCountView.text = "\(currentResult)/\(totalResults)" + } + } + } + + var totalResults = 0 { + didSet { + if totalResults > UX.totalResultsMax { + matchCountView.text = "\(currentResult)/\(UX.totalResultsMax)+" + } else { + matchCountView.text = "\(currentResult)/\(totalResults)" + } + previousButton.isEnabled = totalResults > 1 + nextButton.isEnabled = previousButton.isEnabled + } + } + + var text: String? { + get { + return searchText.text + } + + set { + searchText.text = newValue + didTextChange(searchText) + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = .white + + addSubviews(searchText, matchCountView, previousButton, nextButton, closeButton, topBorder) + + NSLayoutConstraint.activate([ + searchText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), + searchText.topAnchor.constraint(equalTo: topAnchor), + searchText.bottomAnchor.constraint(equalTo: bottomAnchor), + + matchCountView.leadingAnchor.constraint(equalTo: searchText.trailingAnchor), + matchCountView.centerYAnchor.constraint(equalTo: centerYAnchor), + + previousButton.leadingAnchor.constraint(equalTo: matchCountView.trailingAnchor), + previousButton.widthAnchor.constraint(equalTo: heightAnchor), + previousButton.heightAnchor.constraint(equalTo: heightAnchor), + previousButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + nextButton.leadingAnchor.constraint(equalTo: previousButton.trailingAnchor), + nextButton.widthAnchor.constraint(equalTo: heightAnchor), + nextButton.heightAnchor.constraint(equalTo: heightAnchor), + nextButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + closeButton.leadingAnchor.constraint(equalTo: nextButton.trailingAnchor), + closeButton.widthAnchor.constraint(equalTo: heightAnchor), + closeButton.heightAnchor.constraint(equalTo: heightAnchor), + closeButton.trailingAnchor.constraint(equalTo: trailingAnchor), + closeButton.centerYAnchor.constraint(equalTo: centerYAnchor), + + topBorder.heightAnchor.constraint(equalToConstant: 1), + topBorder.leadingAnchor.constraint(equalTo: leadingAnchor), + topBorder.trailingAnchor.constraint(equalTo: trailingAnchor), + topBorder.topAnchor.constraint(equalTo: topAnchor) + ]) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + searchText.becomeFirstResponder() + return super.becomeFirstResponder() + } + + @objc + private func didFindPrevious(_ sender: UIButton) { + delegate?.findInPage(self, findPreviousWithText: searchText.text ?? "") + } + + @objc + private func didFindNext(_ sender: UIButton) { + delegate?.findInPage(self, findNextWithText: searchText.text ?? "") + } + + @objc + private func didTextChange(_ sender: UITextField) { + matchCountView.isHidden = searchText.text?.trimmingCharacters(in: .whitespaces).isEmpty ?? true + delegate?.findInPage(self, textChanged: searchText.text ?? "") + } + + @objc + private func didPressClose(_ sender: UIButton) { + delegate?.findInPageDidPressClose(self) + } + + // MARK: - Theme Applicable + + func applyTheme(theme: Theme) { + let colors = theme.colors + topBorder.backgroundColor = colors.borderPrimary + searchText.textColor = theme.type == .light ? colors.textPrimary : colors.textInverted + matchCountView.textColor = colors.actionSecondary + previousButton.setTitleColor(colors.iconPrimary, for: .normal) + nextButton.setTitleColor(colors.iconPrimary, for: .normal) + closeButton.setTitleColor(colors.iconPrimary, for: .normal) + } + + // MARK: - UITextFieldDelegate + + // Keyboard with a .search returnKeyType doesn't dismiss when return pressed. Handle this manually. + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if string == "\n" { + textField.resignFirstResponder() + return false + } + return true + } +} diff --git a/SampleBrowser/SampleBrowser/UI/RootViewController.swift b/SampleBrowser/SampleBrowser/UI/RootViewController.swift index 0dcb6bc109f5..6ddde93686b5 100644 --- a/SampleBrowser/SampleBrowser/UI/RootViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/RootViewController.swift @@ -2,6 +2,7 @@ // 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/ +import Common import UIKit // Holds toolbar, search bar, search and browser VCs @@ -11,7 +12,8 @@ class RootViewController: UIViewController, SearchBarDelegate, SearchSuggestionDelegate, MenuDelegate, - SettingsDelegate { + SettingsDelegate, + FindInPageBarDelegate { private lazy var toolbar: BrowserToolbar = .build { _ in } private lazy var searchBar: BrowserSearchBar = .build { _ in } private lazy var statusBarFiller: UIView = .build { view in @@ -20,6 +22,7 @@ class RootViewController: UIViewController, private var browserVC: BrowserViewController private var searchVC: SearchViewController + private var findInPageBar: FindInPageBar? // MARK: - Init @@ -148,6 +151,18 @@ class RootViewController: UIViewController, searchBar.setSearchBarText(url) } + func onFindInPage(selected: String) { + showFindInPage() + } + + func onFindInPage(currentResult: Int) { + findInPageBar?.currentResult = currentResult + } + + func onFindInPage(totalResults: Int) { + findInPageBar?.totalResults = totalResults + } + // MARK: - SearchBarDelegate func searchSuggestions(searchTerm: String) { @@ -191,4 +206,41 @@ class RootViewController: UIViewController, func scrollToTop() { browserVC.scrollToTop() } + + func showFindInPage() { + let findInPageBar = FindInPageBar() + findInPageBar.translatesAutoresizingMaskIntoConstraints = false + findInPageBar.delegate = self + self.findInPageBar = findInPageBar + + view.addSubview(findInPageBar) + + NSLayoutConstraint.activate([ + findInPageBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + findInPageBar.bottomAnchor.constraint(equalTo: toolbar.topAnchor), + findInPageBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), + findInPageBar.heightAnchor.constraint(equalToConstant: 46) + ]) + } + + // MARK: - FindInPageBarDelegate + + func findInPage(_ findInPage: FindInPageBar, textChanged text: String) { + browserVC.findInPage(text: text, function: .find) + } + + func findInPage(_ findInPage: FindInPageBar, findPreviousWithText text: String) { + browserVC.findInPage(text: text, function: .findPrevious) + } + + func findInPage(_ findInPage: FindInPageBar, findNextWithText text: String) { + browserVC.findInPage(text: text, function: .findNext) + } + + func findInPageDidPressClose(_ findInPage: FindInPageBar) { + browserVC.findInPageDone() + findInPageBar?.endEditing(true) + findInPageBar?.removeFromSuperview() + findInPageBar = nil + } } diff --git a/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift b/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift index ce844667521f..62d566cec4eb 100644 --- a/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift +++ b/SampleBrowser/SampleBrowser/UI/Settings/SettingsDelegate.swift @@ -6,4 +6,5 @@ import Foundation protocol SettingsDelegate: AnyObject { func scrollToTop() + func showFindInPage() } diff --git a/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift b/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift index 9cc7b57a4944..6a2d83d9f505 100644 --- a/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift +++ b/SampleBrowser/SampleBrowser/UI/Settings/SettingsViewController.swift @@ -84,7 +84,7 @@ class SettingsViewController: UIViewController, UITableViewDelegate { case .contentBlocking: break // TODO: FXIOS-8088 - Handle content blocking in WebEngine case .findInPage: - break // TODO: FXIOS-8087 - Handle find in page in WebEngine + delegate?.showFindInPage() case .scrollingToTop: delegate?.scrollToTop() case .zoom: diff --git a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 92c5104bbae6..5dbbdde82475 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,15 +9,6 @@ "version" : "3.0.0-beta-1" } }, - { - "identity" : "dip", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/Dip.git", - "state" : { - "revision" : "c3b601df0ff22b06b6d5ff4943faf05c0bd3f9bb", - "version" : "7.1.1" - } - }, { "identity" : "fuzi", "kind" : "remoteSourceControl", @@ -63,15 +54,6 @@ "version" : "3.8.9" } }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "3ec0ab0bca4feb56e8b33e289c9496e89059dd08", - "version" : "7.10.2" - } - }, { "identity" : "lottie-ios", "kind" : "remoteSourceControl", @@ -116,15 +98,6 @@ "revision" : "e74fe2a978d1216c3602b129447c7301573cc2d8", "version" : "5.7.0" } - }, - { - "identity" : "swiftybeaver", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SwiftyBeaver/SwiftyBeaver.git", - "state" : { - "revision" : "1080914828ef1c9ca9cd2bad50667b3d847dabff", - "version" : "2.0.0" - } } ], "version" : 2 From 3746144cb7941e3595a33a19d23706824b029fd1 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:22:12 -0500 Subject: [PATCH 04/10] Add find in page content script tests --- .../Scripts/FindInPageContentScript.swift | 5 +- .../WKWebview/Scripts/WKContentScript.swift | 5 +- .../Scripts/WKContentScriptManager.swift | 2 +- .../FindInPageContentScriptTests.swift | 78 +++++++++++++++++++ .../Mock/MockFindInPageDelegate.swift | 23 ++++++ .../Mock/MockWKContentScript.swift | 4 +- .../Mock/MockWKContentScriptManager.swift | 1 + 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift create mode 100644 BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift index a15bddf9f1f1..19518c9ad0de 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/FindInPageContentScript.swift @@ -39,10 +39,9 @@ class FindInPageContentScript: WKContentScript { } func userContentController( - _ userContentController: WKUserContentController, - didReceiveScriptMessage message: WKScriptMessage + didReceiveMessage message: Any ) { - guard let parameters = message.body as? [String: Int] else { + guard let parameters = message as? [String: Int] else { // TODO: FXIOS-6463 - Integrate message handler check logger.log("FindInPage.js sent wrong type of message", level: .warning, diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift index c392e341ad50..20606c083c38 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScript.swift @@ -12,10 +12,7 @@ protocol WKContentScript { func scriptMessageHandlerNames() -> [String] - func userContentController( - _ userContentController: WKUserContentController, - didReceiveScriptMessage message: WKScriptMessage - ) + func userContentController(didReceiveMessage message: Any) func prepareForDeinit() } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift index a3b02734758f..ed8d26359e3f 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKContentScriptManager.swift @@ -60,7 +60,7 @@ class DefaultContentScriptManager: NSObject, WKContentScriptManager { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { for script in scripts.values where script.scriptMessageHandlerNames().contains(message.name) { - script.userContentController(userContentController, didReceiveScriptMessage: message) + script.userContentController(didReceiveMessage: message.body) return } } diff --git a/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift b/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift new file mode 100644 index 000000000000..2b6675d94761 --- /dev/null +++ b/BrowserKit/Tests/WebEngineTests/FindInPageContentScriptTests.swift @@ -0,0 +1,78 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import XCTest +@testable import WebEngine + +final class FindInPageContentScriptTests: XCTestCase { + private var findInPageDelegate: MockFindInPageHelperDelegate! + + override func setUp() { + super.setUp() + findInPageDelegate = MockFindInPageHelperDelegate() + } + + override func tearDown() { + super.tearDown() + findInPageDelegate = nil + } + + func testDidReceiveMessageGivenEmptyMessageThenNoDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + + subject.userContentController(didReceiveMessage: []) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenStringMessageThenNoDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + + subject.userContentController(didReceiveMessage: ["": ""]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenCurrentResultMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let currentResult = 1 + + subject.userContentController(didReceiveMessage: ["currentResult": currentResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + XCTAssertEqual(findInPageDelegate.savedCurrentResult, currentResult) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 0) + } + + func testDidReceiveMessageGivenTotalResultMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let totalResult = 10 + + subject.userContentController(didReceiveMessage: ["totalResults": totalResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 0) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 1) + XCTAssertEqual(findInPageDelegate.savedTotalResults, totalResult) + } + + func testDidReceiveMessageGivenTotalAndCurrentResultsMessageThenDelegateCalled() { + let subject = FindInPageContentScript() + subject.delegate = findInPageDelegate + let totalResult = 15 + let currentResult = 20 + + subject.userContentController(didReceiveMessage: ["totalResults": totalResult, "currentResult": currentResult]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + XCTAssertEqual(findInPageDelegate.savedCurrentResult, currentResult) + XCTAssertEqual(findInPageDelegate.didUpdateTotalResultsCalled, 1) + XCTAssertEqual(findInPageDelegate.savedTotalResults, totalResult) + } +} diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift new file mode 100644 index 000000000000..619c5b9f0aee --- /dev/null +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Foundation +@testable import WebEngine + +class MockFindInPageHelperDelegate: FindInPageHelperDelegate { + var didUpdateCurrentResultCalled = 0 + var didUpdateTotalResultsCalled = 0 + var savedCurrentResult = 0 + var savedTotalResults = 0 + + func findInPageHelper(didUpdateCurrentResult currentResult: Int) { + savedCurrentResult = currentResult + didUpdateCurrentResultCalled += 1 + } + + func findInPageHelper(didUpdateTotalResults totalResults: Int) { + savedTotalResults = totalResults + didUpdateTotalResultsCalled += 1 + } +} diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift index 7c15a6b2a7b1..14edad32fb1c 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScript.swift @@ -3,7 +3,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Foundation -import WebKit @testable import WebEngine class MockWKContentScript: WKContentScript { @@ -20,8 +19,7 @@ class MockWKContentScript: WKContentScript { return ["MockWKContentScriptHandler"] } - func userContentController(_ userContentController: WKUserContentController, - didReceiveScriptMessage message: WKScriptMessage) { + func userContentController(didReceiveMessage message: Any) { userContentControllerCalled += 1 } diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift index 7cb71681d480..9f23c71db409 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift @@ -7,6 +7,7 @@ import WebKit @testable import WebEngine class MockWKContentScriptManager: NSObject, WKContentScriptManager { + var scripts = [String: WKContentScript]() var addContentScriptCalled = 0 var addContentScriptToPageCalled = 0 var uninstallCalled = 0 From b68cd6f46afe143d1b3952031b1cbdd09261a5f0 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:24:30 -0500 Subject: [PATCH 05/10] Revert change --- .../WebEngine/WKWebview/Scripts/WKUserScriptManager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift index 7745e6370f1c..afd1af0ded54 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/WKUserScriptManager.swift @@ -12,7 +12,7 @@ protocol WKUserScriptManager { class DefaultUserScriptManager: WKUserScriptManager { // Scripts can use this to verify the application (not JS on the web) is calling into them - static let appIdToken = UUID().uuidString + private let appIdToken = UUID().uuidString private let scriptProvider: UserScriptProvider var compiledUserScripts = [String: WKUserScript]() @@ -68,7 +68,7 @@ class DefaultUserScriptManager: WKUserScriptManager { private func injectFrameScript(name: String, userScriptInfo: UserScriptInfo) { guard let source = scriptProvider.getScript(for: name) else { return } - let wrappedSource = "(function() { const APP_ID_TOKEN = '\(DefaultUserScriptManager.appIdToken)'; \(source) })()" + let wrappedSource = "(function() { const APP_ID_TOKEN = '\(appIdToken)'; \(source) })()" // Create in default content world let userScript = WKUserScript(source: wrappedSource, injectionTime: userScriptInfo.injectionTime, @@ -81,7 +81,7 @@ class DefaultUserScriptManager: WKUserScriptManager { let webcompatName = "Webcompat\(name)" guard let source = scriptProvider.getScript(for: webcompatName) else { return } - let wrappedSource = "(function() { const APP_ID_TOKEN = '\(DefaultUserScriptManager.appIdToken)'; \(source) })()" + let wrappedSource = "(function() { const APP_ID_TOKEN = '\(appIdToken)'; \(source) })()" // Create in page content world let userScript = WKUserScript(source: wrappedSource, injectionTime: userScriptInfo.injectionTime, From f16764a355d0f10debcf20314b18bf3eaceae96d Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:26:31 -0500 Subject: [PATCH 06/10] Swiftlint --- BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift | 3 ++- .../Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index 49b7a4cfd212..6bea2ed15158 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -14,7 +14,8 @@ class WKEngineSession: NSObject, weak var delegate: EngineSessionDelegate? weak var findInPageDelegate: FindInPageHelperDelegate? { didSet { - guard let findInPage = contentScriptManager.scripts[FindInPageContentScript.name()] as? FindInPageContentScript else { return } + let script = contentScriptManager.scripts[FindInPageContentScript.name()] + guard let findInPage = script as? FindInPageContentScript else { return } findInPage.delegate = findInPageDelegate } } diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift index 619c5b9f0aee..f4d42b22c7d7 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockFindInPageDelegate.swift @@ -15,7 +15,7 @@ class MockFindInPageHelperDelegate: FindInPageHelperDelegate { savedCurrentResult = currentResult didUpdateCurrentResultCalled += 1 } - + func findInPageHelper(didUpdateTotalResults totalResults: Int) { savedTotalResults = totalResults didUpdateTotalResultsCalled += 1 From 069fc475d040f12a13db2283035c52d92441666b Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:48:11 -0500 Subject: [PATCH 07/10] Add sanitization input tests --- .../WebEngine/WKWebview/WKEngineSession.swift | 4 +- .../Mock/MockWKEngineWebView.swift | 10 +++ .../WebEngineTests/WKEngineSessionTests.swift | 63 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index 6bea2ed15158..cce854a80988 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -126,8 +126,8 @@ class WKEngineSession: NSObject, } func findInPage(text: String, function: FindInPageFunction) { - let escaped = text.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") - webView.evaluateJavascriptInDefaultContentWorld("__firefox__.\(function.rawValue)(\"\(escaped)\")") + let sanitizedInput = text.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") + webView.evaluateJavascriptInDefaultContentWorld("__firefox__.\(function.rawValue)(\"\(sanitizedInput)\")") } func findInPageDone() { diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift index 4fdc2741f238..f75c85a0f757 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift @@ -36,6 +36,8 @@ class MockWKEngineWebView: UIView, WKEngineWebView { var removeFromSuperviewCalled = 0 var addObserverCalled = 0 var removeObserverCalled = 0 + var evaluateJavaScriptCalled = 0 + var savedJavaScript: String? var loadFileReadAccessURL: URL? @@ -105,4 +107,12 @@ class MockWKEngineWebView: UIView, WKEngineWebView { forKeyPath keyPath: String) { removeObserverCalled += 1 } + + func evaluateJavaScript(_ javaScript: String, + in frame: WKFrameInfo?, + in contentWorld: WKContentWorld, + completionHandler: ((Result) -> Void)?) { + evaluateJavaScriptCalled += 1 + savedJavaScript = javaScript + } } diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift index 74247b68ad9b..ed1b5c8856d8 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift @@ -134,6 +134,69 @@ final class WKEngineSessionTests: XCTestCase { prepareForTearDown(subject!) } + // MARK: Find in page + + func testFindInPageTextGivenFindAllThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"Banana\")") + prepareForTearDown(subject!) + } + + + func testFindInPageTextGivenFindNextThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .findNext) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findNext(\"Banana\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenFindPreviousThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPage(text: "Banana", function: .findPrevious) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findPrevious(\"Banana\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenMaliciousAlertCodeThenIsSanitized() { + let subject = createSubject() + let maliciousTextWithAlert = "'; alert('Malicious code injected!'); '" + subject?.findInPage(text: maliciousTextWithAlert, function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"\'; alert(\'Malicious code injected!\'); \'\")") + prepareForTearDown(subject!) + } + + func testFindInPageTextGivenMaliciousBrokenJsStringCodeThenIsSanitized() { + let subject = createSubject() + let maliciousText = "; maliciousFunction(); "; + subject?.findInPage(text: maliciousText, function: .find) + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"; maliciousFunction(); \")") + prepareForTearDown(subject!) + } + + func testFindInPageDoneThenJavascriptCalled() { + let subject = createSubject() + + subject?.findInPageDone() + + XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) + XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.findDone()") + prepareForTearDown(subject!) + } + // MARK: Reload func testReloadThenCallsReloadFromOrigin() { From 03e1ef8b772e799e2085db26603494e79beb75e8 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:53:08 -0500 Subject: [PATCH 08/10] Add find in page delegate test --- .../Mock/MockWKContentScriptManager.swift | 2 ++ .../WebEngineTests/WKEngineSessionTests.swift | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift index 9f23c71db409..249db89909f4 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKContentScriptManager.swift @@ -19,6 +19,7 @@ class MockWKContentScriptManager: NSObject, WKContentScriptManager { func addContentScript(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + scripts[name] = script savedContentScriptNames.append(name) addContentScriptCalled += 1 } @@ -26,6 +27,7 @@ class MockWKContentScriptManager: NSObject, WKContentScriptManager { func addContentScriptToPage(_ script: WKContentScript, name: String, forSession session: WKEngineSession) { + scripts[name] = script savedContentScriptPageNames.append(name) addContentScriptToPageCalled += 1 } diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift index ed1b5c8856d8..f5d3a65d4004 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift @@ -11,6 +11,7 @@ final class WKEngineSessionTests: XCTestCase { private var contentScriptManager: MockWKContentScriptManager! private var userScriptManager: MockWKUserScriptManager! private var engineSessionDelegate: MockEngineSessionDelegate! + private var findInPageDelegate: MockFindInPageHelperDelegate! override func setUp() { super.setUp() @@ -19,6 +20,7 @@ final class WKEngineSessionTests: XCTestCase { contentScriptManager = MockWKContentScriptManager() userScriptManager = MockWKUserScriptManager() engineSessionDelegate = MockEngineSessionDelegate() + findInPageDelegate = MockFindInPageHelperDelegate() } override func tearDown() { @@ -28,6 +30,7 @@ final class WKEngineSessionTests: XCTestCase { contentScriptManager = nil userScriptManager = nil engineSessionDelegate = nil + findInPageDelegate = nil } // MARK: Load URL @@ -197,6 +200,17 @@ final class WKEngineSessionTests: XCTestCase { prepareForTearDown(subject!) } + func testFindInPageDelegateIsSetProperly() { + let subject = createSubject() + + subject?.findInPageDelegate = findInPageDelegate + let script = contentScriptManager.scripts[FindInPageContentScript.name()] as! FindInPageContentScript + script.userContentController(didReceiveMessage: ["currentResult": 10]) + + XCTAssertEqual(findInPageDelegate.didUpdateCurrentResultCalled, 1) + prepareForTearDown(subject!) + } + // MARK: Reload func testReloadThenCallsReloadFromOrigin() { From 76312b279c2fc587546852a1b021d600c1f10f2f Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:55:24 -0500 Subject: [PATCH 09/10] Revert package change --- .../xcshareddata/swiftpm/Package.resolved | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5dbbdde82475..92c5104bbae6 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "3.0.0-beta-1" } }, + { + "identity" : "dip", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/Dip.git", + "state" : { + "revision" : "c3b601df0ff22b06b6d5ff4943faf05c0bd3f9bb", + "version" : "7.1.1" + } + }, { "identity" : "fuzi", "kind" : "remoteSourceControl", @@ -54,6 +63,15 @@ "version" : "3.8.9" } }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "3ec0ab0bca4feb56e8b33e289c9496e89059dd08", + "version" : "7.10.2" + } + }, { "identity" : "lottie-ios", "kind" : "remoteSourceControl", @@ -98,6 +116,15 @@ "revision" : "e74fe2a978d1216c3602b129447c7301573cc2d8", "version" : "5.7.0" } + }, + { + "identity" : "swiftybeaver", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftyBeaver/SwiftyBeaver.git", + "state" : { + "revision" : "1080914828ef1c9ca9cd2bad50667b3d847dabff", + "version" : "2.0.0" + } } ], "version" : 2 From 1dbaf6af753012c8c1296643837b36b8f0f22737 Mon Sep 17 00:00:00 2001 From: Laurie Marceau Date: Wed, 7 Feb 2024 12:58:09 -0500 Subject: [PATCH 10/10] Swiftlint --- .../Tests/WebEngineTests/Mock/MockWKEngineWebView.swift | 2 +- BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift index f75c85a0f757..d7adcc6c09de 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift @@ -108,7 +108,7 @@ class MockWKEngineWebView: UIView, WKEngineWebView { removeObserverCalled += 1 } - func evaluateJavaScript(_ javaScript: String, + func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo?, in contentWorld: WKContentWorld, completionHandler: ((Result) -> Void)?) { diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift index f5d3a65d4004..3146644e3c88 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineSessionTests.swift @@ -149,7 +149,6 @@ final class WKEngineSessionTests: XCTestCase { prepareForTearDown(subject!) } - func testFindInPageTextGivenFindNextThenJavascriptCalled() { let subject = createSubject() @@ -176,13 +175,14 @@ final class WKEngineSessionTests: XCTestCase { subject?.findInPage(text: maliciousTextWithAlert, function: .find) XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1) - XCTAssertEqual(webViewProvider.webView.savedJavaScript, "__firefox__.find(\"\'; alert(\'Malicious code injected!\'); \'\")") + let result = "__firefox__.find(\"\'; alert(\'Malicious code injected!\'); \'\")" + XCTAssertEqual(webViewProvider.webView.savedJavaScript, result) prepareForTearDown(subject!) } func testFindInPageTextGivenMaliciousBrokenJsStringCodeThenIsSanitized() { let subject = createSubject() - let maliciousText = "; maliciousFunction(); "; + let maliciousText = "; maliciousFunction(); " subject?.findInPage(text: maliciousText, function: .find) XCTAssertEqual(webViewProvider.webView.evaluateJavaScriptCalled, 1)