用Action Extension实现Safari的JavaScript注入

关于Action Extension的创建这里不详细说,其实官方文档已经说得很清楚了,里面也详细介绍了Action ExtensionJavaScript如何交互.这里主要是讲讲如何利用官方提供的API来实现对Safari的JavaScript注入以及一些简单的应用.


先来看看官方文档中的这段话:

In Share extensions (on both platforms) and Action extensions (iOS only), you can give users access to web content by asking Safari to run a JavaScript file and return the results to the extension. You can also use the JavaScript file to access a webpage before your extension runs (on both platforms), or to access or modify the webpage after your extension completes its task (iOS only).

这段话介绍了JavaScript在Share Extension和Action Extension中能有什么作用,其中最有一句提到了,能在Extension完成任务后访问或修改当前网页,这就是为什么能让Safari执行任意JavaScript的原因.


要让Extension访问网页并执行JS,主要有以下步骤:

  • 首先要创建一个JavaScript文件,格式为.js,并且这个文件必须有一个叫ExtensionPreprocessingJS的全局变量,并将一个实例化的类赋值给它.其实直接照着官方文档的来写就OK了,这里顺便贴一下文档中的js文件代码:
MyExtensionJavaScriptClass = function() {};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
MyExtensionJavaScriptClass.prototype = {
run: function(arguments) {
// Pass the baseURI of the webpage to the extension.
arguments.completionFunction({"baseURI": document.baseURI});
},

// Note that the finalize function is only available in iOS.
finalize: function(arguments) {
// arguments contains the value the extension provides in [NSExtensionContext completeRequestReturningItems:completion:].
// In this example, the extension provides a color as a returning item.
document.body.style.backgroundColor = arguments["bgColor"];
}
};

// The JavaScript file must contain a global object named "ExtensionPreprocessingJS".
var ExtensionPreprocessingJS = new MyExtensionJavaScriptClass;
  • 然后info.plist中作如下配置:

设置NSExtensionActivationSupportsWebPageWithMaxCount为不为零的值,为了让Extension支持对网页的访问.
设置NSExtensionJavaScriptPreprocessingFileJavaScript文件的名称,不含后缀.图中蓝色框圈住的就是我的JavaScript文件名.

  • 剩下的事就在ActionViewController和我们的JavaScript文件中去写代码了.

JavaScript文件分析:


简单来说JavaScript文件中在run()方法中的内容就是Extension执行前,网页会执行的内容,在finalize()方法中就是Extension结束后执行的内容.

官方的例子中,run()方法中用arguments.completionFunction({"baseURI": document.baseURI});将网页的baseURI传递给了Extension,然后在finalize()中用document.body.style.backgroundColor = arguments["bgColor"];通过Extension传递过来的bgColor参数获取到颜色并设置成网页的背景色.

Controller分析:

1
2
3
4
5
6
7
[imageProvider loadItemForTypeIdentifier:kUTTypePropertyList options:nil completionHandler:^(NSDictionary *item, NSError *error) {

NSDictionary *results = (NSDictionary *)item;

NSString *baseURI = [[results objectForKey:NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:@"baseURI"];

}];

上面这段代码是如何获取网页传递过来的baseURI.

1
2
3
4
5
NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];

extensionItem.attachments = @[[[NSItemProvider alloc] initWithItem: @{NSExtensionJavaScriptFinalizeArgumentKey: @{@"bgColor":@"red"}} typeIdentifier:(NSString *)kUTTypePropertyList]];

[[self extensionContext] completeRequestReturningItems:@[extensionItem] completion:nil];

上面这段是如何将bgColor传递回网页.

效果:

在Safari中Action Extension栏拖到最后,点击更多,打开自己写的Action Extension开关,点击并运行结束后,页面成功的变成了红色.


实现任意JavaScript的注入:

上面介绍完了ExtensionJavaScript交互的一个流程,可以看到JavaScript成功的修改了网页的背景颜色,成功实现了一个注入.但是,这并不能实现我们要注入任意JavaScript的效果,只能执行提前写好在JavaScript文件中的代码.

要实现执行任意JavaScript代码的注入,需要用的JS中的一个函数eval(),这个函数有个参数位string类型,也就是一个字符串,这个函数的作用就是能执行一段字符串形式的JavaScript代码,有了这个函数,就能轻松的实现任意JavaScript代码的注入需求了.

来看一下JavaScript文件修改后:

然后Controller中回传到网页的代码也要做些修改,我们尝试将alert('Hello World');这段JavaScript代码传到网页中去.

1
2
3
4
5
NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];

extensionItem.attachments = @[[[NSItemProvider alloc] initWithItem: @{NSExtensionJavaScriptFinalizeArgumentKey: @{@"jsCode" : @"alert('Hello World');"}} typeIdentifier:(NSString *)kUTTypePropertyList]];

[self.extensionContext completeRequestReturningItems:@[extensionItem] completionHandler:nil];

然后重新运行一下:

可以看到,alert代码执行了.

最后,只需要将上面的JavaScript代码当做参数来动态设置,就能实现任意JavaScript代码的执行了.


我自己也写了一个小程序,来方便的在手机端编写JavaScript代码和在Safari中注入,而且顺便集成了一下Firebug,可以在移动端进行一些网页的简单调试,有兴趣的可以看看.

项目地址:https://github.com/JNTian/Extensions-For-Safari