<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 Foundation
import WebKit
import Storage
import Shared

protocol BrowserHelper {
    static func name() -> String
    func scriptMessageHandlerName() -> String?
    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
}

@objc
protocol BrowserDelegate {
    func browser(browser: Browser, didAddSnackbar bar: SnackBar)
    func browser(browser: Browser, didRemoveSnackbar bar: SnackBar)
    optional func browser(browser: Browser, didCreateWebView webView: WKWebView)
    optional func browser(browser: Browser, willDeleteWebView webView: WKWebView)
}

class Browser: NSObject {
    var webView: WKWebView? = nil
    var browserDelegate: BrowserDelegate? = nil
    var bars = [SnackBar]()
    var favicons = [Favicon]()
    var sessionData: SessionData?

    var screenshot: UIImage?
    private var helperManager: HelperManager? = nil
    var lastRequest: NSURLRequest? = nil
    private var configuration: WKWebViewConfiguration? = nil

    init(configuration: WKWebViewConfiguration) {
        self.configuration = configuration
    }

    class func toTab(browser: Browser) -> RemoteTab? {
        if let displayURL = browser.displayURL {
            return RemoteTab(clientGUID: nil,
                URL: displayURL,
                title: browser.displayTitle,
                history: browser.historyList,
                lastUsed: Timestamp(),
                icon: nil)
        } else {
            return nil
        }
    }

    weak var navigationDelegate: WKNavigationDelegate? {
        didSet {
            if let webView = webView {
                webView.navigationDelegate = navigationDelegate
            }
        }
    }

    func createWebview() {
        if webView == nil {
            assert(configuration != nil, "Create webview can only be called once")
            configuration!.userContentController = WKUserContentController()
            configuration!.preferences = WKPreferences()
            configuration!.preferences.javaScriptCanOpenWindowsAutomatically = false
            let webView = WKWebView(frame: CGRectZero, configuration: configuration!)
            configuration = nil

            webView.accessibilityLabel = NSLocalizedString("Web content", comment: "Accessibility label for the main web content view")
            webView.allowsBackForwardNavigationGestures = true
            webView.backgroundColor = UIColor.lightGrayColor()

            // Turning off masking allows the web content to flow outside of the scrollView's frame
            // which allows the content appear beneath the toolbars in the BrowserViewController
            webView.scrollView.layer.masksToBounds = false
            webView.navigationDelegate = navigationDelegate
            helperManager = HelperManager(webView: webView)

            // Pulls restored session data from a previous SavedTab to load into the Browser. If it's nil, a session restore 
            // has already been triggered via custom URL, so we use the last request to trigger it again; otherwise,
            // we extract the information needed to restore the tabs and create a NSURLRequest with the custom session restore URL
            // to trigger the session restore via custom handlers
            if let sessionData = self.sessionData {
                var updatedURLs = [String]()
                for url in sessionData.urls {
                    let updatedURL = WebServer.sharedInstance.updateLocalURL(url)!.absoluteString!
                    updatedURLs.append(updatedURL)
                }
                let currentPage = sessionData.currentPage
                self.sessionData = nil
                var jsonDict = [String: AnyObject]()
                jsonDict["history"] = updatedURLs
                jsonDict["currentPage"] = currentPage
                let escapedJSON = JSON.stringify(jsonDict, pretty: false).stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
                let restoreURL = NSURL(string: "\(WebServer.sharedInstance.base)/about/sessionrestore?history=\(escapedJSON)")
                webView.loadRequest(NSURLRequest(URL: restoreURL!))
            } else if let request = lastRequest {
                webView.loadRequest(request)
            }

            self.webView = webView
            browserDelegate?.browser?(self, didCreateWebView: webView)
        }
    }

    deinit {
        if let webView = webView {
            browserDelegate?.browser?(self, willDeleteWebView: webView)
        }
    }

    var loading: Bool {
        return webView?.loading ?? false
    }

    var estimatedProgress: Double {
        return webView?.estimatedProgress ?? 0
    }

    var backList: [WKBackForwardListItem]? {
        return webView?.backForwardList.backList as? [WKBackForwardListItem]
    }

    var forwardList: [WKBackForwardListItem]? {
        return webView?.backForwardList.forwardList as? [WKBackForwardListItem]
    }

    var historyList: [NSURL] {
        func listToUrl(item: WKBackForwardListItem) -> NSURL { return item.URL }
        var tabs = self.backList?.map(listToUrl) ?? [NSURL]()
        tabs.append(self.url!)
        return tabs
    }

    var title: String? {
        return webView?.title
    }

    var displayTitle: String {
        if let title = webView?.title {
            if !title.isEmpty {
                return title
            }
        }
        return displayURL?.absoluteString ?? ""
    }

    var displayFavicon: Favicon? {
        var width = 0
        var largest: Favicon?
        for icon in favicons {
            if icon.width > width {
                width = icon.width!
                largest = icon
            }
        }
        return largest
    }

    var url: NSURL? {
        return webView?.URL ?? lastRequest?.URL
    }

    var displayURL: NSURL? {
        if let url = webView?.URL ?? lastRequest?.URL {
            if !AboutUtils.isAboutHomeURL(url) {
                if ReaderModeUtils.isReaderModeURL(url) {
                    return ReaderModeUtils.decodeURL(url)
                }

                if ErrorPageHelper.isErrorPageURL(url) {
                    return ErrorPageHelper.decodeURL(url)
                }

                return url
            }
        }
        return nil
    }

    var canGoBack: Bool {
        return webView?.canGoBack ?? false
    }

    var canGoForward: Bool {
        return webView?.canGoForward ?? false
    }

    func goBack() {
        webView?.goBack()
    }

    func goForward() {
        webView?.goForward()
    }

    func goToBackForwardListItem(item: WKBackForwardListItem) {
        webView?.goToBackForwardListItem(item)
    }

    func loadRequest(request: NSURLRequest) -> WKNavigation? {
        lastRequest = request
        if let webView = webView {
            return webView.loadRequest(request)
        }
        return nil
    }

    func stop() {
        webView?.stopLoading()
    }

    func reload() {
        webView?.reload()
    }

    func addHelper(helper: BrowserHelper, name: String) {
        helperManager!.addHelper(helper, name: name)
    }

    func getHelper(#name: String) -> BrowserHelper? {
        return helperManager!.getHelper(name: name)
    }

    func hideContent(animated: Bool = false) {
        webView?.userInteractionEnabled = false
        if animated {<commit_msg>Bug 1182488 - Changing theme color in ReaderView crashes the app<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 Foundation
import WebKit
import Storage
import Shared

protocol BrowserHelper {
    static func name() -> String
    func scriptMessageHandlerName() -> String?
    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
}

@objc
protocol BrowserDelegate {
    func browser(browser: Browser, didAddSnackbar bar: SnackBar)
    func browser(browser: Browser, didRemoveSnackbar bar: SnackBar)
    optional func browser(browser: Browser, didCreateWebView webView: WKWebView)
    optional func browser(browser: Browser, willDeleteWebView webView: WKWebView)
}

class Browser: NSObject {
    var webView: WKWebView? = nil
    var browserDelegate: BrowserDelegate? = nil
    var bars = [SnackBar]()
    var favicons = [Favicon]()
    var sessionData: SessionData?

    var screenshot: UIImage?
    private var helperManager: HelperManager? = nil
    var lastRequest: NSURLRequest? = nil
    private var configuration: WKWebViewConfiguration? = nil

    init(configuration: WKWebViewConfiguration) {
        self.configuration = configuration
    }

    class func toTab(browser: Browser) -> RemoteTab? {
        if let displayURL = browser.displayURL {
            return RemoteTab(clientGUID: nil,
                URL: displayURL,
                title: browser.displayTitle,
                history: browser.historyList,
                lastUsed: Timestamp(),
                icon: nil)
        } else {
            return nil
        }
    }

    weak var navigationDelegate: WKNavigationDelegate? {
        didSet {
            if let webView = webView {
                webView.navigationDelegate = navigationDelegate
            }
        }
    }

    func createWebview() {
        if webView == nil {
            assert(configuration != nil, "Create webview can only be called once")
            configuration!.userContentController = WKUserContentController()
            configuration!.preferences = WKPreferences()
            configuration!.preferences.javaScriptCanOpenWindowsAutomatically = false
            let webView = WKWebView(frame: CGRectZero, configuration: configuration!)
            configuration = nil

            webView.accessibilityLabel = NSLocalizedString("Web content", comment: "Accessibility label for the main web content view")
            webView.allowsBackForwardNavigationGestures = true
            webView.backgroundColor = UIColor.lightGrayColor()

            // Turning off masking allows the web content to flow outside of the scrollView's frame
            // which allows the content appear beneath the toolbars in the BrowserViewController
            webView.scrollView.layer.masksToBounds = false
            webView.navigationDelegate = navigationDelegate
            helperManager = HelperManager(webView: webView)

            // Pulls restored session data from a previous SavedTab to load into the Browser. If it's nil, a session restore 
            // has already been triggered via custom URL, so we use the last request to trigger it again; otherwise,
            // we extract the information needed to restore the tabs and create a NSURLRequest with the custom session restore URL
            // to trigger the session restore via custom handlers
            if let sessionData = self.sessionData {
                var updatedURLs = [String]()
                for url in sessionData.urls {
                    let updatedURL = WebServer.sharedInstance.updateLocalURL(url)!.absoluteString!
                    updatedURLs.append(updatedURL)
                }
                let currentPage = sessionData.currentPage
                self.sessionData = nil
                var jsonDict = [String: AnyObject]()
                jsonDict["history"] = updatedURLs
                jsonDict["currentPage"] = currentPage
                let escapedJSON = JSON.stringify(jsonDict, pretty: false).stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
                let restoreURL = NSURL(string: "\(WebServer.sharedInstance.base)/about/sessionrestore?history=\(escapedJSON)")
                webView.loadRequest(NSURLRequest(URL: restoreURL!))
            } else if let request = lastRequest {
                webView.loadRequest(request)
            }

            self.webView = webView
            browserDelegate?.browser?(self, didCreateWebView: webView)
        }
    }

    deinit {
        if let webView = webView {
            browserDelegate?.browser?(self, willDeleteWebView: webView)
        }
    }

    var loading: Bool {
        return webView?.loading ?? false
    }

    var estimatedProgress: Double {
        return webView?.estimatedProgress ?? 0
    }

    var backList: [WKBackForwardListItem]? {
        return webView?.backForwardList.backList as? [WKBackForwardListItem]
    }

    var forwardList: [WKBackForwardListItem]? {
        return webView?.backForwardList.forwardList as? [WKBackForwardListItem]
    }

    var historyList: [NSURL] {
        func listToUrl(item: WKBackForwardListItem) -> NSURL { return item.URL }
        var tabs = self.backList?.map(listToUrl) ?? [NSURL]()
        tabs.append(self.url!)
        return tabs
    }

    var title: String? {
        return webView?.title
    }

    var displayTitle: String {
        if let title = webView?.title {
            if !title.isEmpty {
                return title
            }
        }
        return displayURL?.absoluteString ?? ""
    }

    var displayFavicon: Favicon? {
        var width = 0
        var largest: Favicon?
        for icon in favicons {
            if icon.width > width {
                width = icon.width!
                largest = icon
            }
        }
        return largest
    }

    var url: NSURL? {
        return webView?.URL ?? lastRequest?.URL
    }

    var displayURL: NSURL? {
        if let url = webView?.URL ?? lastRequest?.URL {
            if !AboutUtils.isAboutHomeURL(url) {
                if ReaderModeUtils.isReaderModeURL(url) {
                    return ReaderModeUtils.decodeURL(url)
                }

                if ErrorPageHelper.isErrorPageURL(url) {
                    return ErrorPageHelper.decodeURL(url)
                }

                return url
            }
        }
        return nil
    }

    var canGoBack: Bool {
        return webView?.canGoBack ?? false
    }

    var canGoForward: Bool {
        return webView?.canGoForward ?? false
    }

    func goBack() {
        webView?.goBack()
    }

    func goForward() {
        webView?.goForward()
    }

    func goToBackForwardListItem(item: WKBackForwardListItem) {
        webView?.goToBackForwardListItem(item)
    }

    func loadRequest(request: NSURLRequest) -> WKNavigation? {
        lastRequest = request
        if let webView = webView {
            return webView.loadRequest(request)
        }
        return nil
    }

    func stop() {
        webView?.stopLoading()
    }

    func reload() {
        webView?.reload()
    }

    func addHelper(helper: BrowserHelper, name: String) {
        helperManager!.addHelper(helper, name: name)
    }

    func getHelper(#name: String) -> BrowserHelper? {
        return helperManager?.getHelper(name: name)
    }

    func hideContent(animated: Bool = false) {
        webView?.userInteractionEnabled = false
        if animated {
{'fraction_non_alphanumeric': 0.07750571335292197, 'fraction_numerical': 0.0012406137773424747, 'mean_word_length': 2.178912411789124, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 17, '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': '1993279', 'n_tokens_mistral': 4082, 'n_tokens_neox': 3847, 'n_words': 1083}