Follow along at https://www.hackingwithswift.com/100/68.
This day covers the second part of
Project 19: JavaScript Injection
in Hacking with Swift.I previously created projects alongside Hacking with Swift in a separate repository, and you can find Project 19 here. Even better, though, I copied it over to Day 67's folder so I could extend it for 100 Days of Swift.
With that in mind, Day 68 focuses on several specific topics:
- Establishing communication
- Finalizing our Extension Action
For Safari to process our extension's JavaScript, it needs to follow a few rules:
- Define a custom JavaScript class.
- Have this class implement a
run
function. - Create a global object (done in JavaScript with the
var
keyword) that’s namedExtensionPreprocessingJS
. - Assign a new instance of our custom JavaScript class to the
ExtensionPreprocessingJS
object.
I was able to find Apple’s official documentation for how all of this is supposed to work here (see the “Accessing a Webpage” section), and then implement the following:
class ExtensionAction {
run(params) {
/**
* Pass data to our extension when the script is run on the page.
*
* Here, we'll pass:
* - the URL of the current page,
* - the page title
*/
params.completionFunction({
URL: document.URL,
title: document.title
});
}
/**
* Handle anything passed back from an extension when it's runtime completes
*/
finalize(params) {
eval(params.userJavaScript);
}
};
var ExtensionPreprocessingJS = new ExtensionAction();
The extensionContext
gives us a completeRequest
method where we can return NSExtensionItem
s to Safari. We'll return an item with an "attachment"... which is an NSItemProvider
... which contains a NSDictionary
... which contains an item keyed by NSExtensionJavaScriptFinalizeArgumentKey
.
This item is also an NSDictionary
— and that's what will be passed to our JavaScript action class's finalize
method:
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .done, target: self, action: #selector(extensionCompleted)
)
@IBAction func extensionCompleted() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
extensionContext!.completeRequest(
returningItems: [userJavaScriptExtensionItem],
completionHandler: nil
)
}
var userJavaScriptExtensionItem: NSExtensionItem {
let argument: NSDictionary = ["userJavaScript": scriptTextView.text]
// 🔑 This is what will be sent as the argument to our script's `finalize` function
let webDictionary: NSDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: argument]
let customJSProvider = NSItemProvider(
item: webDictionary,
typeIdentifier: kUTTypePropertyList as String
)
let extensionItem = NSExtensionItem()
extensionItem.attachments = [customJSProvider]
return extensionItem
}