From 1a66460442c0781e08c1d777131f78105d37a254 Mon Sep 17 00:00:00 2001 From: Tom Whitwell Date: Wed, 13 Apr 2022 13:36:55 +0100 Subject: [PATCH] Implement firefox container tab support Firefox can have container tabs enabled, and an extension exists that will allow routing urls with a specific format into these containers [Open External links in a container](https://addons.mozilla.org/en-GB/firefox/addon/open-url-in-container/). If configured in a `handler`, rewrite the outgoing URL into this format and open that. This has an improvement over using a rewrite rule, in that the config can be kept DRY, and container selection logic is left to the handler. Example configuration: ```js handlers: [ { // Some work site match: "https://some-site.com/*", browser: { name: "Firefox", container: "Work", }, }, ], ``` --- Finicky/Finicky/AppDescriptor.swift | 3 ++ Finicky/Finicky/Browsers.swift | 37 +++++++++++++++-- Finicky/Finicky/Config.swift | 2 + Finicky/FinickyTests/FinickyUnitTests.swift | 46 +++++++++++++++++++++ config-api/src/schemas.ts | 2 + config-api/src/types.ts | 1 + 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/Finicky/Finicky/AppDescriptor.swift b/Finicky/Finicky/AppDescriptor.swift index 08bebf2..978062f 100644 --- a/Finicky/Finicky/AppDescriptor.swift +++ b/Finicky/Finicky/AppDescriptor.swift @@ -22,6 +22,7 @@ public struct BrowserOpts: CustomStringConvertible { public var bundleId: String? public var appPath: String? public var profile: String? + public var container: String? public var args: [String] public var description: String { @@ -39,6 +40,7 @@ public struct BrowserOpts: CustomStringConvertible { appType: AppDescriptorType, openInBackground: Bool?, profile: String?, + container: String?, args: [String] ) throws { self.name = name @@ -54,6 +56,7 @@ public struct BrowserOpts: CustomStringConvertible { } self.profile = profile + self.container = container self.args = args if appType == AppDescriptorType.bundleId { diff --git a/Finicky/Finicky/Browsers.swift b/Finicky/Finicky/Browsers.swift index 6ffea21..7f920b4 100644 --- a/Finicky/Finicky/Browsers.swift +++ b/Finicky/Finicky/Browsers.swift @@ -30,7 +30,8 @@ public func getActiveApp(browsers: [BrowserOpts]) -> BrowserOpts? { public func openUrlWithBrowser(_ url: URL, browserOpts: BrowserOpts) { print("Opening \(browserOpts) at: " + url.absoluteString) - let command = getBrowserCommand(browserOpts, url: url) + let browserUrl = getBrowserUrl(browserOpts, incomingUrl: url) + let command = getBrowserCommand(browserOpts, url: browserUrl) shell(command) } @@ -52,7 +53,37 @@ enum Browser: String { case Wavebox = "com.bookry.wavebox" } -public func getBrowserCommand(_ browserOpts: BrowserOpts, url: URL) -> [String] { +public func getBrowserUrl(_ browserOpts: BrowserOpts, incomingUrl: URL) -> String { + var url = incomingUrl + if let container = browserOpts.container, let bundleId: String = browserOpts.bundleId { + if let browserUrl: URL = getContainerUrl(bundleId: bundleId, container: container, url: url) { + url = browserUrl + } + } + return url.absoluteString +} + +private func getContainerUrl(bundleId: String, container: String, url: URL) -> URL? { + var containerUrl: URL? { + switch bundleId.lowercased() { + case + Browser.Firefox.rawValue, + Browser.FirefoxDeveloperEdition.rawValue: + var urlComponents = URLComponents() + urlComponents.scheme = "ext+container" + urlComponents.queryItems = [ + URLQueryItem(name: "name", value: container), + URLQueryItem(name: "url", value: url.absoluteString) + ] + return urlComponents.url + + default: return nil + } + } + return containerUrl +} + +public func getBrowserCommand(_ browserOpts: BrowserOpts, url: String) -> [String] { var command = ["open"] var commandArgs: [String] = [] var appendUrl = true @@ -88,7 +119,7 @@ public func getBrowserCommand(_ browserOpts: BrowserOpts, url: URL) -> [String] } if appendUrl { - command.append(url.absoluteString) + command.append(url) } return command diff --git a/Finicky/Finicky/Config.swift b/Finicky/Finicky/Config.swift index cb59a37..b3c347c 100644 --- a/Finicky/Finicky/Config.swift +++ b/Finicky/Finicky/Config.swift @@ -313,6 +313,7 @@ open class FinickyConfig { let openInBackground: Bool? = dict["openInBackground"] as? Bool let browserName = dict["name"] as! String let browserProfile: String? = dict["profile"] as? String + let browserContainer: String? = dict["container"] as? String let args: [String] = dict["args"] as? [String] ?? [] if browserName == "" { @@ -326,6 +327,7 @@ open class FinickyConfig { appType: appType!, openInBackground: openInBackground, profile: browserProfile, + container: browserContainer, args: args ) return browser diff --git a/Finicky/FinickyTests/FinickyUnitTests.swift b/Finicky/FinickyTests/FinickyUnitTests.swift index 08e9652..ef7592e 100644 --- a/Finicky/FinickyTests/FinickyUnitTests.swift +++ b/Finicky/FinickyTests/FinickyUnitTests.swift @@ -14,6 +14,31 @@ struct Fixture { } } +func createBrowserOpts(name: String = "", + appType: String = "appName", + openInBackground: Bool = false, + profile: String? = nil, + container: String? = nil, + args: [String] = []) -> BrowserOpts? { + if let appType = AppDescriptorType(rawValue: appType){ + do { + let browserOpts = try BrowserOpts( + name: name, + appType: appType, + openInBackground: openInBackground, + profile: profile, + container: container, + args: args + ) + return browserOpts + } + catch _ { + return nil + } + } + return nil +} + class FinickyUnitTests: XCTestCase { func testRewrite() { XCTAssertEqual(try compareVersions("1.2.3", "0.9"), ComparisonResult.orderedDescending) @@ -52,6 +77,27 @@ class FinickyUnitTests: XCTestCase { center.removeDeliveredNotification(notification) } + + func test_getContainerUrl() throws { + if let browserOpts = createBrowserOpts(name: "Firefox"), let incomingUrl = URL(string: "https://www.example.com"){ + let url = getBrowserUrl(browserOpts, incomingUrl: incomingUrl) + XCTAssertEqual(url, "https://www.example.com") + } else { + XCTFail() + } + if let browserOpts = createBrowserOpts(name: "Firefox", container: "TestContainer"), let incomingUrl = URL(string: "https://www.example.com"){ + let url = getBrowserUrl(browserOpts, incomingUrl: incomingUrl) + XCTAssertEqual(url, "ext+container:?name=TestContainer&url=https://www.example.com") + } + if let browserOpts = createBrowserOpts(name: "Firefox", container: "TestContainer"), let incomingUrl = URL(string: "https://www.example.com?test=123&test1=1234"){ + let url = getBrowserUrl(browserOpts, incomingUrl: incomingUrl) + XCTAssertEqual(url, "ext+container:?name=TestContainer&url=https://www.example.com?test%3D123%26test1%3D1234") + } + if let browserOpts = createBrowserOpts(name: "Safari", container: "TestContainer"), let incomingUrl = URL(string: "https://www.example.com"){ + let url = getBrowserUrl(browserOpts, incomingUrl: incomingUrl) + XCTAssertEqual(url, "https://www.example.com") + } + } func test_makeVersionParts_error() { var capturedError: VersionParseError? diff --git a/config-api/src/schemas.ts b/config-api/src/schemas.ts index 1e29dc4..1da1272 100644 --- a/config-api/src/schemas.ts +++ b/config-api/src/schemas.ts @@ -32,6 +32,7 @@ const browserSchema = validate.oneOf([ appType: validate.oneOf(["appName", "appPath", "bundleId"]), openInBackground: validate.boolean, profile: validate.string, + container: validate.string, args: validate.arrayOf(validate.string), }), validate.function("options"), @@ -100,5 +101,6 @@ export const appDescriptorSchema = { ]).isRequired, openInBackground: validate.boolean, profile: validate.string, + container: validate.string, args: validate.arrayOf(validate.string), }; diff --git a/config-api/src/types.ts b/config-api/src/types.ts index b60c2b6..2a0abd6 100644 --- a/config-api/src/types.ts +++ b/config-api/src/types.ts @@ -126,6 +126,7 @@ export interface BrowserObject { appType?: AppType; openInBackground?: boolean; profile?: string; + container?: string; args?: string[]; }