Passing messages between Safari app extensions and injected scripts
Communicate between your Safari app extension and injected scripts.
Overview
The injected script and your Safari app extension live in different sandboxed worlds, each with specific limits on what it can access. You can’t call directly from the native code in your app extension to the injected script running in the browser. However, some sort of communication between the two is almost always desirable. For example, you may have a toolbar or contextual menu item that you want to use to affect web content.
The solution is to pass messages between the injected script and the app extension. The two runtime environments share a common format for message passing, and each provides an interface for sending and receiving messages.
The complete workflow appears below:
[Image]
Within the scripting environment, you have access to the window and document objects for the browser content, as well as a safari object. The safari object is an instance of the SafariAppExtensionNamespace class. It provides details about your app extension and support for passing messages between your injected script and the app extension.
This table describes your app extension’s key properties:
Property | Description |
|---|---|
| A proxy for the app extension. Use it to retrieve information about your app extension and to pass messages to it. |
| A proxy for your injected script. Use it to install event listeners to respond to messages from your app extension. |
Send messages to the app extension
Call safari.extension.dispatchMessage.
safari.extension.dispatchMessage("Hello World");
The parameter for dispatchMessage(withName:toExtensionWithIdentifier:userInfo:completionHandler:) is a string that identifies the message you want to send. You create your own message names and decide what those messages mean for your app extension.
You can optionally send additional user data to accompany the message.
safari.extension.dispatchMessage("InterestingMessage", { "key": "value" });The user data must be a JavaScript object made up of keys and values that conform to the W3C standard for safe passing of structured data, such as Boolean objects, numeric values, strings, arrays, and so on.
For example, the following code sends an array in a message:
var myArray = ["a", "b", "c"];
safari.extension.dispatchMessage("passArray", { "key": myArray });When the app extension receives the message, the system calls the extension handler’s messageReceived(withName:from:userInfo:) method. This method’s parameters include the message name, the page that sends the message, and, if part of the message, a user dictionary:
In Safari 17 and later, check whether the user is browsing with a profile if you need to limit any extension logic to the profile, such as fetching or storing data. To do that, implement the beginRequest(with:) method.
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
NSExtensionItem *item = context.inputItems.firstObject;
NSDictionary *userInfo = item.userInfo;
NSUUID *profileIdentifier = userInfo[SFExtensionProfileKey];
if (profileIdentifier != nil) {
// Remember profile identifier for future method calls.
} else {
// Handle normal browsing.
}
}Then, get the profile identifier from the context.inputItems dictionary using SFExtensionProfileKey as the key. Use the profile identifier for any profile-specific logic.
Send messages to the injected script
When the app extension needs to send a message to an injected script, it calls the dispatchMessageToScript(withName:userInfo:) method on the target page.
The message is a packaged event with a type of message. To respond to the message, the injected script registers an event listener for message events using safari.self.addEventListener.
safari.self.addEventListener("message", handleMessage);
The event that passes into the event handler is a SafariExtensionMessageEvent object. Its name property identifies the message, and its message property contains the dictionary of user data.
function handleMessage(event) {
console.log(event.name);
console.log(event.message);
}