To provide better shopping experience for Onefill users, we want to support as many shopping site as we can by injecting our JavaScript engine into those sites. However, some of them are using iframe which is outdated HTML tag to implement some forms like payment and signup. And due to security issue JavaScript can’t communicate with iframe unless it’s same domain or it’s your domain. So here is the approach we did to support iframe under iOS web view and so we can communicate with it.
- Xcode: Version 8.2.1 (8C1002)
- Swift: Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
- Use WKWebView instead UIWebView or WebView for 2 reasons:
- Use built-in API WKUserScript inject JS, set
forMainFrameOnly
as false to inject JS into every frames (including iframe) - To communicate between iframe and app, use JS webkit API
Use WKUserScript
inject JavaScript into all frames
Example: Inject a script to make every h1 tag red.
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
let contentController = WKUserContentController()
let js: String = "var h1s = document.querySelectorAll('h1'); for (var i = 0; i < h1s.length; i++) { h1s[i].style.color = 'red' };"
let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
contentController.addUserScript(userScript)
webConfiguration.userContentController = contentController
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string: "http://localhost:3000")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
}
Use JavaScript webkit API
Almost same as “inject JS into iframe”, however, we need to implement WKScriptMessageHandler
protocol to receive message sent by JavaScript.
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
let contentController = WKUserContentController()
// Inject JavaScript which sending message to App
let js: String = "window.webkit.messageHandlers.callbackHandler.postMessage('Hello from JavaScript');"
let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
contentController.removeAllUserScripts()
contentController.addUserScript(userScript)
// Add ScriptMessageHandler
contentController.add(
self,
name: "callbackHandler"
)
webConfiguration.userContentController = contentController
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string: "http://localhost:3000")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
// Implement `WKScriptMessageHandler`,handle message which been sent by JavaScript
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "callbackHandler") {
print("JavaScript is sending a message \(message.body)")
}
}
}
I received this error: Argument type 'webView' does not conform to expected type 'WKScriptMessageHandler'