<commit_before>/* 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 UIKit
import WebKit
import Telemetry
import OnePasswordExtension
import PassKit

protocol BrowserState {
    var url: URL? { get }
    var isLoading: Bool { get }
    var canGoBack: Bool { get }
    var canGoForward: Bool { get }
    var estimatedProgress: Double { get }
}

protocol WebController {
    var delegate: WebControllerDelegate? { get set }
    var canGoBack: Bool { get }
    var canGoForward: Bool { get }

    func load(_ request: URLRequest)
}

protocol WebControllerDelegate: class {
    func webControllerDidStartProvisionalNavigation(_ controller: WebController)
    func webControllerDidStartNavigation(_ controller: WebController)
    func webControllerDidFinishNavigation(_ controller: WebController)
    func webControllerDidNavigateBack(_ controller: WebController)
    func webControllerDidNavigateForward(_ controller: WebController)
    func webControllerDidReload(_ controller: WebController)
    func webControllerURLDidChange(_ controller: WebController, url: URL)
    func webController(_ controller: WebController, didFailNavigationWithError error: Error)
    func webController(_ controller: WebController, didUpdateCanGoBack canGoBack: Bool)
    func webController(_ controller: WebController, didUpdateCanGoForward canGoForward: Bool)
    func webController(_ controller: WebController, didUpdateEstimatedProgress estimatedProgress: Double)
    func webController(_ controller: WebController, scrollViewWillBeginDragging scrollView: UIScrollView)
    func webController(_ controller: WebController, scrollViewDidEndDragging scrollView: UIScrollView)
    func webController(_ controller: WebController, scrollViewDidScroll scrollView: UIScrollView)
    func webController(_ controller: WebController, stateDidChange state: BrowserState)
    func webControllerShouldScrollToTop(_ controller: WebController) -> Bool
    func webController(_ controller: WebController, didUpdateTrackingProtectionStatus trackingStatus: TrackingProtectionStatus)
    func webController(_ controller: WebController, didUpdateFindInPageResults currentResult: Int?, totalResults: Int?)
}

class WebViewController: UIViewController, WebController {
    private enum ScriptHandlers: String {
        case focusTrackingProtection
        case focusTrackingProtectionPostLoad
        case findInPageHandler

        static var allValues: [ScriptHandlers] { return [.focusTrackingProtection, .focusTrackingProtectionPostLoad, .findInPageHandler] }
    }

    private enum KVOConstants: String, CaseIterable {
        case URL = "URL"
        case canGoBack = "canGoBack"
        case canGoForward = "canGoForward"
    }

    weak var delegate: WebControllerDelegate?

    private var browserView: WKWebView!
    var onePasswordExtensionItem: NSExtensionItem!
    private var progressObserver: NSKeyValueObservation?
    private var urlObserver: NSKeyValueObservation?
    private var currentBackForwardItem: WKBackForwardListItem?
    private var userAgent: UserAgent?
    private var trackingProtectionStatus = TrackingProtectionStatus.on(TPPageStats()) {
        didSet {
            delegate?.webController(self, didUpdateTrackingProtectionStatus: trackingProtectionStatus)
        }
    }

    private var trackingInformation = TPPageStats() {
        didSet {
            if case .on = trackingProtectionStatus {
                trackingProtectionStatus = .on(trackingInformation)
            }
        }
    }

    var pageTitle: String? {
        return browserView.title
    }

    var userAgentString: String? {
        return self.userAgent?.getUserAgent()
    }

    var printFormatter: UIPrintFormatter { return browserView.viewPrintFormatter() }
    var scrollView: UIScrollView { return browserView.scrollView }

    convenience init(userAgent: UserAgent = UserAgent.shared) {
        self.init(nibName: nil, bundle: nil)

        self.userAgent = userAgent

        setupWebview()
        ContentBlockerHelper.shared.handler = reloadBlockers(_:)
    }

    func reset() {
        userAgent?.setup()
        browserView.load(URLRequest(url: URL(string: "about:blank")!))
        browserView.navigationDelegate = nil
        browserView.removeFromSuperview()
        trackingProtectionStatus = .on(TPPageStats())
        setupWebview()
        self.browserView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
    }

    // Browser proxy methods
    func load(_ request: URLRequest) { browserView.load(request) }
    func goBack() { browserView.goBack() }
    func goForward() { browserView.goForward() }
    func reload() { browserView.reload() }

    @available(iOS 9, *)
    func requestUserAgentChange() {
        guard let currentItem = browserView.backForwardList.currentItem else {
            return
        }

        userAgent?.changeUserAgent()
        browserView.customUserAgent = userAgent?.getUserAgent()

        if currentItem.url != currentItem.initialURL {
            // Reload the initial URL to avoid UA specific redirection
            browserView.load(URLRequest(url: currentItem.initialURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 60))
        } else {
            reload() // Reload the current URL. We cannot use loadRequest in this case because it seems to leverage caching.
        }
        UserDefaults.standard.set(false, forKey: TipManager.TipKey.requestDesktopTip)
    }

    func resetUA() {
        browserView.customUserAgent = userAgent?.browserUserAgent
    }

    func stop() { browserView.stopLoading() }

    private func setupWebview() {
        let wvConfig = WKWebViewConfiguration()
        wvConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
        wvConfig.allowsInlineMediaPlayback = true
        browserView = WKWebView(frame: .zero, configuration: wvConfig)

        browserView.allowsBackForwardNavigationGestures = true
        browserView.allowsLinkPreview = false
        browserView.scrollView.clipsToBounds = false
        browserView.scrollView.delegate = self
        browserView.navigationDelegate = self
        browserView.uiDelegate = self

        progressObserver = browserView.observe(\WKWebView.estimatedProgress) { (webView, value) in
            self.delegate?.webController(self, didUpdateEstimatedProgress: webView.estimatedProgress)
        }

        setupBlockLists()
        setupTrackingProtectionScripts()<commit_msg>Fixes #1692 - Long pressing on links does not open the context menu<commit_after>/* 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 UIKit
import WebKit
import Telemetry
import OnePasswordExtension
import PassKit

protocol BrowserState {
    var url: URL? { get }
    var isLoading: Bool { get }
    var canGoBack: Bool { get }
    var canGoForward: Bool { get }
    var estimatedProgress: Double { get }
}

protocol WebController {
    var delegate: WebControllerDelegate? { get set }
    var canGoBack: Bool { get }
    var canGoForward: Bool { get }

    func load(_ request: URLRequest)
}

protocol WebControllerDelegate: class {
    func webControllerDidStartProvisionalNavigation(_ controller: WebController)
    func webControllerDidStartNavigation(_ controller: WebController)
    func webControllerDidFinishNavigation(_ controller: WebController)
    func webControllerDidNavigateBack(_ controller: WebController)
    func webControllerDidNavigateForward(_ controller: WebController)
    func webControllerDidReload(_ controller: WebController)
    func webControllerURLDidChange(_ controller: WebController, url: URL)
    func webController(_ controller: WebController, didFailNavigationWithError error: Error)
    func webController(_ controller: WebController, didUpdateCanGoBack canGoBack: Bool)
    func webController(_ controller: WebController, didUpdateCanGoForward canGoForward: Bool)
    func webController(_ controller: WebController, didUpdateEstimatedProgress estimatedProgress: Double)
    func webController(_ controller: WebController, scrollViewWillBeginDragging scrollView: UIScrollView)
    func webController(_ controller: WebController, scrollViewDidEndDragging scrollView: UIScrollView)
    func webController(_ controller: WebController, scrollViewDidScroll scrollView: UIScrollView)
    func webController(_ controller: WebController, stateDidChange state: BrowserState)
    func webControllerShouldScrollToTop(_ controller: WebController) -> Bool
    func webController(_ controller: WebController, didUpdateTrackingProtectionStatus trackingStatus: TrackingProtectionStatus)
    func webController(_ controller: WebController, didUpdateFindInPageResults currentResult: Int?, totalResults: Int?)
}

class WebViewController: UIViewController, WebController {
    private enum ScriptHandlers: String {
        case focusTrackingProtection
        case focusTrackingProtectionPostLoad
        case findInPageHandler

        static var allValues: [ScriptHandlers] { return [.focusTrackingProtection, .focusTrackingProtectionPostLoad, .findInPageHandler] }
    }

    private enum KVOConstants: String, CaseIterable {
        case URL = "URL"
        case canGoBack = "canGoBack"
        case canGoForward = "canGoForward"
    }

    weak var delegate: WebControllerDelegate?

    private var browserView: WKWebView!
    var onePasswordExtensionItem: NSExtensionItem!
    private var progressObserver: NSKeyValueObservation?
    private var urlObserver: NSKeyValueObservation?
    private var currentBackForwardItem: WKBackForwardListItem?
    private var userAgent: UserAgent?
    private var trackingProtectionStatus = TrackingProtectionStatus.on(TPPageStats()) {
        didSet {
            delegate?.webController(self, didUpdateTrackingProtectionStatus: trackingProtectionStatus)
        }
    }

    private var trackingInformation = TPPageStats() {
        didSet {
            if case .on = trackingProtectionStatus {
                trackingProtectionStatus = .on(trackingInformation)
            }
        }
    }

    var pageTitle: String? {
        return browserView.title
    }

    var userAgentString: String? {
        return self.userAgent?.getUserAgent()
    }

    var printFormatter: UIPrintFormatter { return browserView.viewPrintFormatter() }
    var scrollView: UIScrollView { return browserView.scrollView }

    convenience init(userAgent: UserAgent = UserAgent.shared) {
        self.init(nibName: nil, bundle: nil)

        self.userAgent = userAgent

        setupWebview()
        ContentBlockerHelper.shared.handler = reloadBlockers(_:)
    }

    func reset() {
        userAgent?.setup()
        browserView.load(URLRequest(url: URL(string: "about:blank")!))
        browserView.navigationDelegate = nil
        browserView.removeFromSuperview()
        trackingProtectionStatus = .on(TPPageStats())
        setupWebview()
        self.browserView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
    }

    // Browser proxy methods
    func load(_ request: URLRequest) { browserView.load(request) }
    func goBack() { browserView.goBack() }
    func goForward() { browserView.goForward() }
    func reload() { browserView.reload() }

    @available(iOS 9, *)
    func requestUserAgentChange() {
        guard let currentItem = browserView.backForwardList.currentItem else {
            return
        }

        userAgent?.changeUserAgent()
        browserView.customUserAgent = userAgent?.getUserAgent()

        if currentItem.url != currentItem.initialURL {
            // Reload the initial URL to avoid UA specific redirection
            browserView.load(URLRequest(url: currentItem.initialURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 60))
        } else {
            reload() // Reload the current URL. We cannot use loadRequest in this case because it seems to leverage caching.
        }
        UserDefaults.standard.set(false, forKey: TipManager.TipKey.requestDesktopTip)
    }

    func resetUA() {
        browserView.customUserAgent = userAgent?.browserUserAgent
    }

    func stop() { browserView.stopLoading() }

    private func setupWebview() {
        let wvConfig = WKWebViewConfiguration()
        wvConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
        wvConfig.allowsInlineMediaPlayback = true
        browserView = WKWebView(frame: .zero, configuration: wvConfig)

        browserView.allowsBackForwardNavigationGestures = true
        browserView.allowsLinkPreview = false
        browserView.scrollView.clipsToBounds = false
        browserView.scrollView.delegate = self
        browserView.navigationDelegate = self
        browserView.uiDelegate = self
        browserView.allowsLinkPreview = true

        progressObserver = browserView.observe(\WKWebView.estimatedProgress) { (webView, value) in
            self.delegate?.webController(self, didUpdateEstimatedProgress: webView.estimatedProgress)
        }

        setupBlockLists()
        setupTrackingProtectionScripts()
{'fraction_non_alphanumeric': 0.07159509663833948, 'fraction_numerical': 0.0013536888019854102, 'mean_word_length': 3.665964912280702, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5319082', 'n_tokens_mistral': 3487, 'n_tokens_neox': 3369, 'n_words': 902}