Search code examples
iossafari-web-extension

Is there a way to detect if extension is running on iPad vs iPhone


I am porting an extension from Chrome/Firefox to Safari on iOS. When the extension popup comes up there are two different behaviors on Safari on iOS. On iPhone it comes up as a full screenwidth menu from the bottom of the screen. On iPad it behaves like Safari on MacOS and the popup comes down from the extension icon in the toolbar and is dynamically sized.

I would like to detect these two different cases in my javascript code. In the former case I want to refrain from setting the popup window width (since that causes problems) in the latter case I do want to set the width (in the same way that I do in Chrome etc).

I don't know for sure if it is a case of detecting iPad vs iPhone (but I would be interested in knowing how to do that). There could be an iPhone with a large enough screen size that would cause it to use the latter behavior.


Solution

  • While figuring out macOS vs iOS is fairly simple through using browser.runtime.getPlatformInfo(), determining iOS vs iPadOS is more difficult.

    When the extension popup comes up there are two different behaviors on Safari on iOS. On iPhone it comes up as a full screenwidth menu from the bottom of the screen. On iPad it behaves like Safari on MacOS and the popup comes down from the extension icon in the toolbar and is dynamically sized.

    Keep in mind that on iPadOS the popup can also show up as it does on iOS when the browser is in split view. Because of this it's important to track window resize events and to test the popup in and out of split view, across all of the split view sizes.

    The only complete solution I've found is using a combination of Swift, Javascript and CSS.

    Swift side

    In your WebExtension, or wherever you like, you could create a simple function that returns which platform you're on. I use this over browser.runtime.getPlatformInfo() since the latter can't discern iOS vs iPadOS. Something like:

    func getPlatform() -> String {
        var platform:String
        #if os(iOS)
            if UIDevice.current.userInterfaceIdiom == .pad {
                platform = "ipados"
            }
            else {
                platform = "ios"
            }
        #elseif os(macOS)
            platform = "macos"
        #endif
        return platform
    }
    

    In order to communicate with the swift side of you application, you'll need to use native messaging from the javascript side - and put the appropriate message handlers on your swift side.

    background.js

    
    browser.runtime.sendNativeMessage({name: "getPlatformFromSwiftSide"}, response => {
        if (response.platform === "ipados") {
            ...
        } else if (response.platform === "ios") {
            ...
        }
    });
    

    SafariWebExtensionHandler.swift

    class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
        func beginRequest(with context: NSExtensionContext) {
            let item = context.inputItems[0] as? NSExtensionItem
            let message = item?.userInfo?[SFExtensionMessageKey] as? [String: Any]
            guard let name = message?["name"] as? String else {return}
            let response = NSExtensionItem()
            if name == "getPlatformFromSwiftSide" {
                let platform = getPlatform()
                response.userInfo = [SFExtensionMessageKey: ["platform": platform]]
            }
            context.completeRequest(returningItems: [response], completionHandler: nil)
        }
    }
    
    

    At this point you can distinguish between iPadOS and iOS. However you still have the issue of whether or not the browser is in split view or not. As mentioned, for certain sizes in split view, the popup on iPadOS is laid out differently and you must account for this.

    Currently I am unaware of any methods to easily determine this, outside of CSS media queries or Javascript matchMedia calls.

    Further, calling window from the popup (or using media queries in the popup context) won't indicate the browser's size, but rather the popups. Since we wan't to know the browser size to determine how to size the popup, the only reliable way I know of accomplishing this is by using browser.tabs.sendMessage, send a message to the content script requesting the browser window size.


    All of this seems more complicated than it need be, but I know of no other alternatives currently.