macossafarisafari-extensionsafari-app-extension

Safari App Extensions: Load HTML file on HTTPS page


I'm developing a Safari App Extension (because Safari Extensions are now officially deprecated) and I want to inject some HTML into a page via JS. But when I make a request to my safari-extension:// URL the request is made without SSL, and Safari currently blocks mixed content and does not allow any way to change that policy. So I have two questions.

  1. How can I get around this issue for my development environment?

  2. I read in the comments here that the production packaged extension (old Safari Extension) will load resources with SSL. Is this true for Safari App Extensions?

EDIT

I got an Apple Developer account, signed my extension and still no luck.


Solution

  • I use the messaging protocol safari exposes to the Safari App Extension to send the HTML as a string in a response.

    Content script requests HTML template (JS)

    let sendMessage = (msgObj, callback) => {
      msgObj.callbackIndex = (safariCallbackCount++).toString(); 
      let callbackKey = msgObj['type']+'_'+msgObj.callbackIndex;
      if (typeof callback === 'function')
        messageCallbacks[callbackKey] = callback;
      safari.extension.dispatchMessage(msgObj['type'], msgObj);
    }
    sendMessage({type: 'loadTemplate', path: path}, callback );
    

    Note the messageCallbacks hash that stores the callback function as well as a counter used for index uniqueness. You are passing messaged to a Swift process and the responses can come back out of order when sending multiple messages.

    SFSafariExtensionHandler listens for messages and handles (swift)

    func loadTemplate (withPage page: SFSafariPage, withUrl url: URL, withCallbackIndex callbackIndex: String) {
        let pathExtention = url.pathExtension
        let pathPrefix = url.path.replacingOccurrences(of: "." + pathExtention, with: "", options: .literal, range: nil)
        if let filepath = Bundle.main.path(forResource: pathPrefix, ofType: pathExtention) {
            do {
                let contents = try String(contentsOfFile: filepath, encoding: .utf8)
                page.dispatchMessageToScript(withName: "loadTemplate", userInfo: ["htmlData": contents, "callbackIndex": callbackIndex])
            } catch {
                // contents could not be loaded
                NSLog("ERROR CONTENTS COULD NOT BE LOADED FROM \(filepath)")
            }
        } else {
            NSLog("Could not build file path")
        }
    }
            // This method will be called when a content script provided by your extension calls safari.extension.dispatchMessage("message").
    override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
        page.getPropertiesWithCompletionHandler { properties in
            switch messageName {
            case "loadTemplate":
                self.loadTemplate(withPage: page, withUrl: URL(string: userInfo?["path"] as! String)!, withCallbackIndex: userInfo?["callbackIndex"] as! String)
            default:
                NSLog("NO DEFINITION FOR STRING VALUE")
            }
        }
    }
    

    Content script handles response (JS)

    let handleMessage = event => {
      let callbackIndex = event.message.callbackIndex;
      delete event.message.callbackIndex;
      let callbackKey = event.name+'_'+callbackIndex;
      if ( typeof messageCallbacks[callbackKey] === 'function' ) {
        messageCallbacks[callbackKey](event.message);
        delete messageCallbacks[callbackKey]
      }
    };
    safari.self.addEventListener("message", handleMessage);
    //// html data is accessible from event.message.htmlData