// // IndeedJobBrowserWindowController.swift // App for Indeed // import Cocoa import WebKit /// Indeed job listing and apply flow in a `WKWebView`, embedded in the dashboard main panel or hosted in a window. final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelegate, WKUIDelegate { /// When set, a leading **Home** control calls this so the host can hide the embedded browser (same-window UX). var onDismissEmbedded: (() -> Void)? private let webView: WKWebView = { let configuration = WKWebViewConfiguration() configuration.preferences.javaScriptCanOpenWindowsAutomatically = true return WKWebView(frame: .zero, configuration: configuration) }() private var pendingURL: URL? private let backButton = NSButton() private let forwardButton = NSButton() private let reloadButton = NSButton() private let dismissEmbeddedButton = NSButton(title: "Home", target: nil, action: nil) override func loadView() { view = NSView(frame: NSRect(x: 0, y: 0, width: 920, height: 720)) } override func viewDidLoad() { super.viewDidLoad() webView.translatesAutoresizingMaskIntoConstraints = false webView.navigationDelegate = self webView.uiDelegate = self webView.customUserAgent = Self.desktopSafariLikeUserAgent configureToolbarButton(backButton, symbolName: "chevron.backward", action: #selector(goBack)) configureToolbarButton(forwardButton, symbolName: "chevron.forward", action: #selector(goForward)) configureToolbarButton(reloadButton, symbolName: "arrow.clockwise", action: #selector(reload)) dismissEmbeddedButton.translatesAutoresizingMaskIntoConstraints = false dismissEmbeddedButton.bezelStyle = .rounded dismissEmbeddedButton.isBordered = true dismissEmbeddedButton.target = self dismissEmbeddedButton.action = #selector(dismissEmbedded) dismissEmbeddedButton.toolTip = "Return to the previous screen" let toolbar = NSView() toolbar.translatesAutoresizingMaskIntoConstraints = false toolbar.wantsLayer = true toolbar.layer?.backgroundColor = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1).cgColor let barStack: NSStackView if onDismissEmbedded != nil { barStack = NSStackView(views: [dismissEmbeddedButton, backButton, forwardButton, reloadButton, NSView()]) } else { barStack = NSStackView(views: [backButton, forwardButton, reloadButton, NSView()]) } barStack.orientation = .horizontal barStack.spacing = 8 barStack.alignment = .centerY barStack.distribution = .fill barStack.translatesAutoresizingMaskIntoConstraints = false toolbar.addSubview(barStack) view.addSubview(toolbar) view.addSubview(webView) var layoutConstraints: [NSLayoutConstraint] = [ toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor), toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), toolbar.topAnchor.constraint(equalTo: view.topAnchor), toolbar.heightAnchor.constraint(equalToConstant: 48), barStack.leadingAnchor.constraint(equalTo: toolbar.leadingAnchor, constant: 12), barStack.trailingAnchor.constraint(equalTo: toolbar.trailingAnchor, constant: -12), barStack.centerYAnchor.constraint(equalTo: toolbar.centerYAnchor), backButton.widthAnchor.constraint(equalToConstant: 32), backButton.heightAnchor.constraint(equalToConstant: 28), forwardButton.widthAnchor.constraint(equalToConstant: 32), forwardButton.heightAnchor.constraint(equalToConstant: 28), reloadButton.widthAnchor.constraint(equalToConstant: 32), reloadButton.heightAnchor.constraint(equalToConstant: 28), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), webView.topAnchor.constraint(equalTo: toolbar.bottomAnchor), webView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ] if onDismissEmbedded != nil { layoutConstraints.append(dismissEmbeddedButton.heightAnchor.constraint(equalToConstant: 28)) } NSLayoutConstraint.activate(layoutConstraints) updateNavigationButtons() if let pendingURL { webView.load(URLRequest(url: pendingURL)) self.pendingURL = nil } } func loadPage(_ url: URL) { if isViewLoaded { webView.load(URLRequest(url: url)) } else { pendingURL = url } updateNavigationButtons() } /// Adds this controller as a child of `parent` and pins `view` to `host` (used by the dashboard main panel). func embed(in host: NSView, parent: NSViewController) { parent.addChild(self) view.translatesAutoresizingMaskIntoConstraints = false host.addSubview(view) NSLayoutConstraint.activate([ view.leadingAnchor.constraint(equalTo: host.leadingAnchor), view.trailingAnchor.constraint(equalTo: host.trailingAnchor), view.topAnchor.constraint(equalTo: host.topAnchor), view.bottomAnchor.constraint(equalTo: host.bottomAnchor) ]) } private func configureToolbarButton(_ button: NSButton, symbolName: String, action: Selector) { button.translatesAutoresizingMaskIntoConstraints = false button.bezelStyle = .texturedRounded button.isBordered = true button.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil) button.imagePosition = .imageOnly button.target = self button.action = action } private func updateNavigationButtons() { backButton.isEnabled = webView.canGoBack forwardButton.isEnabled = webView.canGoForward } @objc private func goBack() { webView.goBack() } @objc private func goForward() { webView.goForward() } @objc private func reload() { webView.reload() } @objc private func dismissEmbedded() { onDismissEmbedded?() } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { updateNavigationButtons() } func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { updateNavigationButtons() } /// Target=_blank / `window.open` without a frame: load in this view so apply flows stay in-app. func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if navigationAction.targetFrame == nil { webView.load(navigationAction.request) } return nil } /// Desktop Safari UA helps Indeed serve a full desktop apply experience. private static let desktopSafariLikeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15" }