Diff
Modified: trunk/Tools/ChangeLog (251147 => 251148)
--- trunk/Tools/ChangeLog 2019-10-15 17:31:35 UTC (rev 251147)
+++ trunk/Tools/ChangeLog 2019-10-15 18:21:52 UTC (rev 251148)
@@ -1,3 +1,20 @@
+2019-10-15 Jiewen Tan <jiewen_...@apple.com>
+
+ [WebAuthn] Write more tests for _WKWebAuthenticationPanel
+ https://bugs.webkit.org/show_bug.cgi?id=202565
+ <rdar://problem/55974128>
+
+ Reviewed by Brent Fulgham.
+
+ * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+ * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
+ (-[TestWebAuthenticationPanelDelegate panel:dismissWebAuthenticationPanelWithResult:]):
+ (-[TestWebAuthenticationPanelUIDelegate init]):
+ (-[TestWebAuthenticationPanelUIDelegate webView:runWebAuthenticationPanel:initiatedByFrame:completionHandler:]):
+ (TestWebKitAPI::TEST):
+ * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html: Added.
+ * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html: Added.
+
2019-10-15 Alex Christensen <achristen...@webkit.org>
Pass CORS-enabled schemes through WebProcess instead of having them NetworkProcess-global
Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (251147 => 251148)
--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2019-10-15 17:31:35 UTC (rev 251147)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2019-10-15 18:21:52 UTC (rev 251148)
@@ -336,6 +336,8 @@
57599E281F071AA000A3FB8C /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57599E261F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm */; };
57599E2A1F071AA000A3FB8C /* IndexedDBStructuredCloneBackwardCompatibilityRead.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57599E251F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibilityRead.html */; };
57599E2B1F071AA000A3FB8C /* IndexedDBStructuredCloneBackwardCompatibilityWrite.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57599E231F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibilityWrite.html */; };
+ 57663DEA234EA66D00E85E09 /* web-authentication-get-assertion-nfc.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */; };
+ 57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */; };
5769C50B1D9B0002000847FB /* SerializedCryptoKeyWrap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5769C50A1D9B0001000847FB /* SerializedCryptoKeyWrap.mm */; };
5774AA6821FBBF7800AF2A1B /* TestSOAuthorization.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5774AA6721FBBF7800AF2A1B /* TestSOAuthorization.mm */; };
5778D05622110A2600899E3B /* LoadWebArchive.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5778D05522110A2600899E3B /* LoadWebArchive.mm */; };
@@ -1439,6 +1441,8 @@
CDC8E4971BC6F10800594FEC /* video-without-audio.mp4 in Copy Resources */,
2EBD9D0A2134730D002DA758 /* video.html in Copy Resources */,
CD577799211CE0E4001B371E /* web-audio-only.html in Copy Resources */,
+ 57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */,
+ 57663DEA234EA66D00E85E09 /* web-authentication-get-assertion-nfc.html in Copy Resources */,
57C624502346C21E00383FE7 /* web-authentication-get-assertion.html in Copy Resources */,
1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
@@ -1856,6 +1860,8 @@
57599E241F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3 */ = {isa = PBXFileReference; lastKnownFileType = file; path = IndexedDBStructuredCloneBackwardCompatibility.sqlite3; sourceTree = "<group>"; };
57599E251F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibilityRead.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = IndexedDBStructuredCloneBackwardCompatibilityRead.html; sourceTree = "<group>"; };
57599E261F07192C00A3FB8C /* IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm */ = {isa = PBXFileReference; lastKnownFileType = file; path = "IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm"; sourceTree = "<group>"; };
+ 57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-get-assertion-nfc.html"; sourceTree = "<group>"; };
+ 57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-get-assertion-hid.html"; sourceTree = "<group>"; };
5769C50A1D9B0001000847FB /* SerializedCryptoKeyWrap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SerializedCryptoKeyWrap.mm; sourceTree = "<group>"; };
5774AA6721FBBF7800AF2A1B /* TestSOAuthorization.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestSOAuthorization.mm; sourceTree = "<group>"; };
5778D05522110A2600899E3B /* LoadWebArchive.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LoadWebArchive.mm; sourceTree = "<group>"; };
@@ -3378,6 +3384,8 @@
F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */,
CD57779B211CE6CE001B371E /* video-with-audio-and-web-audio.html */,
CD577798211CDE8F001B371E /* web-audio-only.html */,
+ 57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */,
+ 57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */,
57C6244F2346C1EC00383FE7 /* web-authentication-get-assertion.html */,
51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm (251147 => 251148)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm 2019-10-15 17:31:35 UTC (rev 251147)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm 2019-10-15 18:21:52 UTC (rev 251148)
@@ -30,13 +30,16 @@
#import "PlatformUtilities.h"
#import "TestWKWebView.h"
+#import "WKWebViewConfigurationExtras.h"
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/_WKExperimentalFeature.h>
#import <WebKit/_WKWebAuthenticationPanel.h>
+#import <wtf/BlockPtr.h>
static bool webAuthenticationPanelRan = false;
-static bool webAuthenticationPanelDimissed = false;
+static bool webAuthenticationPanelFailed = false;
+static bool webAuthenticationPanelSucceded = false;
@interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
@end
@@ -46,20 +49,36 @@
- (void)panel:(_WKWebAuthenticationPanel *)panel dismissWebAuthenticationPanelWithResult:(_WKWebAuthenticationResult)result
{
ASSERT_NE(panel, nil);
- EXPECT_EQ(result, _WKWebAuthenticationResultFailed);
- webAuthenticationPanelDimissed = true;
+ if (result == _WKWebAuthenticationResultFailed) {
+ webAuthenticationPanelFailed = true;
+ return;
+ }
+ if (result == _WKWebAuthenticationResultSucceeded) {
+ webAuthenticationPanelSucceded = true;
+ return;
+ }
}
@end
@interface TestWebAuthenticationPanelUIDelegate : NSObject <WKUIDelegatePrivate>
+@property bool isRacy;
+- (instancetype)init;
@end
@implementation TestWebAuthenticationPanelUIDelegate {
RetainPtr<_WKWebAuthenticationPanel> _panel;
RetainPtr<TestWebAuthenticationPanelDelegate> _delegate;
+ BlockPtr<void(_WKWebAuthenticationPanelResult)> _callback;
}
+- (instancetype)init
+{
+ if (self = [super init])
+ self.isRacy = false;
+ return self;
+}
+
- (void)webView:(WKWebView *)webView runWebAuthenticationPanel:(_WKWebAuthenticationPanel *)panel initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(_WKWebAuthenticationPanelResult))completionHandler
{
webAuthenticationPanelRan = true;
@@ -71,6 +90,13 @@
EXPECT_WK_STREQ([_panel relyingPartyID], "");
+ if (_isRacy) {
+ if (!_callback) {
+ _callback = makeBlockPtr(completionHandler);
+ return;
+ }
+ _callback(_WKWebAuthenticationPanelResultUnavailable);
+ }
completionHandler(_WKWebAuthenticationPanelResultPresented);
}
@@ -79,7 +105,8 @@
namespace TestWebKitAPI {
namespace {
-_WKExperimentalFeature *webAuthenticationExperimentalFeature()
+
+static _WKExperimentalFeature *webAuthenticationExperimentalFeature()
{
static RetainPtr<_WKExperimentalFeature> theFeature;
if (theFeature)
@@ -94,10 +121,57 @@
}
return theFeature.get();
}
+
+// Only focused documents can trigger WebAuthn.
+static void focus(TestWKWebView *webView)
+{
+#if PLATFORM(MAC)
+ [[webView hostWindow] makeFirstResponder:webView];
+#elif PLATFORM(IOS)
+ [webView becomeFirstResponder];
+#endif
}
-TEST(WebAuthenticationPanel, BasicTimeout)
+static void reset()
{
+ webAuthenticationPanelRan = false;
+ webAuthenticationPanelFailed = false;
+ webAuthenticationPanelSucceded = false;
+}
+
+} // namesapce;
+
+TEST(WebAuthenticationPanel, NoPanelTimeout)
+{
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-nfc" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ focus(webView.get());
+
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ [webView waitForMessage:@"Operation timed out."];
+}
+
+TEST(WebAuthenticationPanel, NoPanelHidSuccess)
+{
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ focus(webView.get());
+
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ [webView waitForMessage:@"Succeeded!"];
+}
+
+TEST(WebAuthenticationPanel, PanelTimeout)
+{
+ reset();
RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
@@ -106,19 +180,104 @@
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration.get()]);
auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
[webView setUIDelegate:delegate.get()];
+ focus(webView.get());
- // Only focused documents can trigger WebAuthn.
-#if PLATFORM(MAC)
- [[webView hostWindow] makeFirstResponder:webView.get()];
-#elif PLATFORM(IOS)
- [webView becomeFirstResponder];
-#endif
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelRan);
+ Util::run(&webAuthenticationPanelFailed);
+}
+TEST(WebAuthenticationPanel, PanelHidSuccess)
+{
+ reset();
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+ [webView setUIDelegate:delegate.get()];
+ focus(webView.get());
+
[webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
Util::run(&webAuthenticationPanelRan);
- Util::run(&webAuthenticationPanelDimissed);
+ Util::run(&webAuthenticationPanelSucceded);
}
+#if HAVE(NEAR_FIELD)
+// This test aims to see if the callback for the first ceremony could affect the second one.
+// Therefore, the first callback will be held to return at the time when the second arrives.
+// The first callback will return _WKWebAuthenticationPanelResultUnavailable which leads to timeout for NFC.
+// The second callback will return _WKWebAuthenticationPanelResultPresented which leads to success.
+TEST(WebAuthenticationPanel, PanelRacy1)
+{
+ reset();
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-nfc" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+ [delegate setIsRacy:true];
+ [webView setUIDelegate:delegate.get()];
+ focus(webView.get());
+
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelRan);
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ [webView waitForMessage:@"Succeeded!"];
+}
+
+// Unlike the previous one, this one focuses on the order of the delegate callbacks.
+TEST(WebAuthenticationPanel, PanelRacy2)
+{
+ reset();
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-nfc" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+ [delegate setIsRacy:true];
+ [webView setUIDelegate:delegate.get()];
+ focus(webView.get());
+
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelRan);
+ webAuthenticationPanelRan = false;
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelFailed);
+ Util::run(&webAuthenticationPanelRan);
+ Util::run(&webAuthenticationPanelSucceded);
+}
+#endif // HAVE(NEAR_FIELD)
+
+TEST(WebAuthenticationPanel, PanelTwice)
+{
+ reset();
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+ auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+ [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+ auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+ [webView setUIDelegate:delegate.get()];
+ focus(webView.get());
+
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelRan);
+ Util::run(&webAuthenticationPanelSucceded);
+
+ reset();
+ [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ Util::run(&webAuthenticationPanelRan);
+ Util::run(&webAuthenticationPanelSucceded);
+}
+
} // namespace TestWebKitAPI
#endif // ENABLE(WEB_AUTHN)
Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html (0 => 251148)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html 2019-10-15 18:21:52 UTC (rev 251148)
@@ -0,0 +1,25 @@
+<script>
+ const testAssertionMessageBase64 =
+ "AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
+ "w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
+ "Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
+ "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
+ "QoJ1L7Fe64G9uBc=";
+ if (window.internals)
+ internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: [testAssertionMessageBase64] } });
+
+ const options = {
+ publicKey: {
+ challenge: new Uint8Array(16),
+ timeout: 100
+ }
+ };
+
+ navigator.credentials.get(options).then(credential => {
+ // console.log("Succeeded!");
+ window.webkit.messageHandlers.testHandler.postMessage("Succeeded!");
+ }, error => {
+ // console.log(error.message);
+ window.webkit.messageHandlers.testHandler.postMessage(error.message);
+ });
+</script>
Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html (0 => 251148)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html 2019-10-15 18:21:52 UTC (rev 251148)
@@ -0,0 +1,32 @@
+<script>
+ const testNfcCtapVersionBase64 = "RklET18yXzCQAA==";
+ const testGetInfoResponseApduBase64 =
+ "AKYBgmZVMkZfVjJoRklET18yXzACgWtobWFjLXNlY3JldANQbUS6m/bsLkm5MAyP" +
+ "6SDLcwSkYnJr9WJ1cPVkcGxhdPRpY2xpZW50UGlu9AUZBLAGgQGQAA==";
+ const testAssertionMessageApduBase64 =
+ "AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
+ "w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
+ "Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
+ "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
+ "QoJ1L7Fe64G9uBeQAA==";
+ if (window.internals)
+ internals.setMockWebAuthenticationConfiguration({ nfc: { error: "success", payloadBase64: [testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testAssertionMessageApduBase64] } });
+
+ const options = {
+ publicKey: {
+ challenge: new Uint8Array(16),
+ timeout: 100,
+ allowCredentials: [
+ { type: "public-key", id: new Uint8Array(16), transports: ["nfc"] }
+ ],
+ }
+ };
+
+ navigator.credentials.get(options).then(credential => {
+ // console.log("Succeeded!");
+ window.webkit.messageHandlers.testHandler.postMessage("Succeeded!");
+ }, error => {
+ // console.log(error.message);
+ window.webkit.messageHandlers.testHandler.postMessage(error.message);
+ });
+</script>