Permalink
Browse files

Add alternative mechanism of communicating into a *WebView.

Change the location URL fragment and observe the change via a "hashchange"
event on the JS side.

In theory this should have lower overhead than
stringByEvaluatingJavaScriptFromString:/evaluateJavaScript:completionHandler:
since itwe don't need to do arbitrary JS parsing (we can just JSON-encode the
data in). In practice it ends up being slower since the URL fragment change
triggers the (navigation) delegate's shouldStartLoadWithRequest:/
decidePolicyForNavigationAction: methods, incurring an additional roundtrip.
  • Loading branch information...
mihaip committed Aug 11, 2014
1 parent 43a6fcf commit 3df9ddd9a1a168d90c8acb3673872133959c46c7
@@ -7,26 +7,28 @@
enum Mechanism {
// UIWebView mechanisms
LocationHref = 0,
LocationHash,
LinkClick,
FrameSrc,
XhrSync,
XhrAsync,
CookieChange,
JavaScriptCore,
LocationHash = 1,
LocationHashInOut = 2,
LinkClick = 3,
FrameSrc = 4,
XhrSync = 5,
XhrAsync = 6,
CookieChange = 7,
JavaScriptCore = 8,
// WKWebView mechanisms
WKMessageHandler,
WKLocationHash,
WKMessageHandler = 9,
WKLocationHash = 10,
WKLocationHashInOut = 11,
// Not actual full mechanisms, but just ways of measuring the native -> web function call time.
UIWebViewExecuteJs,
WKWebViewExecuteJs,
UIWebViewExecuteJs = 12,
WKWebViewExecuteJs = 13,
kNumMechanisms
};
const NSUInteger kNumIterationsPerMechanisms = 1000;
const NSUInteger kNumIterationsPerMechanisms = 100;
const NSUInteger kNumIterations = kNumIterationsPerMechanisms * kNumMechanisms;
BOOL kOnIOS8;
@@ -46,6 +48,12 @@ @interface PongUrlProtocol : NSURLProtocol
@end
@interface NSString (URLEncode)
-(NSString *)URLEncode;
@end
@interface BenchmarkViewController () <TSWebViewDelegate, WebViewExport, WKNavigationDelegate, WKScriptMessageHandler>
@end
@@ -135,21 +143,8 @@ -(void)viewDidLoad {
[_uiWebView loadRequest:[NSURLRequest requestWithURL:benchmarkUrl]];
if (_wkWebView) {
// Serve HTML inline until https://devforums.apple.com/message/1009455 is fixed.
NSString *htmlPath = [NSBundle.mainBundle pathForResource:@"benchmark-wkwebview" ofType:@"html"];
NSString *jsPath = [NSBundle.mainBundle pathForResource:@"benchmark-wkwebview" ofType:@"js"];
NSString *cssPath = [NSBundle.mainBundle pathForResource:@"benchmark" ofType:@"css"];
NSString *benchmarkHtml = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
NSString *benchmarkJs = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
NSString *benchmarkCss = [NSString stringWithContentsOfFile:cssPath encoding:NSUTF8StringEncoding error:nil];
NSString *benchmarkScriptTag = [NSString stringWithFormat:@"<script>%@</script>", benchmarkJs];
NSString *benchmarkStyleTag = [NSString stringWithFormat:@"<style>%@</style>", benchmarkCss];
benchmarkHtml = [benchmarkHtml stringByReplacingOccurrencesOfString:@"<script src=\"benchmark-wkwebview.js\"></script>" withString:benchmarkScriptTag];
benchmarkHtml = [benchmarkHtml stringByReplacingOccurrencesOfString:@"<link rel=\"stylesheet\" href=\"benchmark.css\">" withString:benchmarkStyleTag];
[_wkWebView loadHTMLString:benchmarkHtml baseURL:nil];
// Serve HTML from a server until https://devforums.apple.com/message/1009455 is fixed.
[_wkWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://persistent.info/tmp/webview/benchmark-wkwebview.html"]]];
}
}
@@ -173,9 +168,15 @@ -(void)startIteration {
// Cookie changes don't seem to trigger delegate methods on iOS 8. Since it's a slower mechanism,
// it's not work investigating.
[self endIteration:0];
} else if (mechanism == WKMessageHandler || mechanism == WKLocationHash) {
} else if (mechanism == WKMessageHandler || mechanism == WKLocationHash || mechanism == WKLocationHashInOut) {
if (_wkWebView) {
[_wkWebView evaluateJavaScript:[NSString stringWithFormat:@"ping(%d, '%qu')", mechanism, start] completionHandler:nil];
if (mechanism == WKLocationHashInOut) {
NSString *pingParams = [NSString stringWithFormat:@"{\"mechanism\": %d, \"startTime\": \"%qu\"}", mechanism, start];
NSURL *pingUrl = [NSURL URLWithString:[NSString stringWithFormat:@"#ping%@", pingParams.URLEncode] relativeToURL:_wkWebView.URL];
[_wkWebView loadRequest:[NSURLRequest requestWithURL:pingUrl]];
} else {
[_wkWebView evaluateJavaScript:[NSString stringWithFormat:@"ping(%d, '%qu')", mechanism, start] completionHandler:nil];
}
} else {
[self endIteration:0];
}
@@ -190,6 +191,10 @@ -(void)startIteration {
} else {
[self endIteration:0];
}
} else if (mechanism == LocationHashInOut) {
NSString *pingParams = [NSString stringWithFormat:@"{\"mechanism\": %d, \"startTime\": \"%qu\"}", mechanism, start];
NSURL *pingUrl = [NSURL URLWithString:[NSString stringWithFormat:@"#ping%@", pingParams.URLEncode] relativeToURL:_uiWebView.request.URL];
[_uiWebView loadRequest:[NSURLRequest requestWithURL:pingUrl]];
} else {
[_uiWebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"ping(%d, '%qu')", mechanism, start]];
}
@@ -226,20 +231,22 @@ -(void)showBenchmarkResults {
for (size_t i = 0; i < kNumMechanisms; i++) {
NSString *name = @"";
switch (i) {
case LocationHref: name = @"location.href "; [results appendString:@"\nUIWebView\n"]; break;
case LocationHash: name = @"location.hash "; break;
case LinkClick: name = @"<a> click "; break;
case FrameSrc: name = @"frame.src "; break;
case XhrSync: name = @"XHR sync "; break;
case XhrAsync: name = @"XHR async "; break;
case CookieChange: name = @"document.cookie "; break;
case JavaScriptCore: name = @"JavaScriptCore "; break;
case WKMessageHandler: name = @"MessageHandler "; [results appendString:@"\nWKWebView\n"]; break;
case WKLocationHash: name = @"location.hash "; break;
case UIWebViewExecuteJs: name = @"UIWebView "; [results appendString:@"\nJS Execution\n"]; break;
case WKWebViewExecuteJs: name = @"WKWebView "; break;
case LocationHref: name = @"location.href "; [results appendString:@"\nUIWebView\n"]; break;
case LocationHash: name = @"location.hash "; break;
case LocationHashInOut: name = @"location.hash i/o"; break;
case LinkClick: name = @"<a> click "; break;
case FrameSrc: name = @"frame.src "; break;
case XhrSync: name = @"XHR sync "; break;
case XhrAsync: name = @"XHR async "; break;
case CookieChange: name = @"document.cookie "; break;
case JavaScriptCore: name = @"JavaScriptCore "; break;
case WKMessageHandler: name = @"MessageHandler "; [results appendString:@"\nWKWebView\n"]; break;
case WKLocationHash: name = @"location.hash "; break;
case WKLocationHashInOut: name = @"location.hash i/o"; break;
case UIWebViewExecuteJs: name = @"UIWebView "; [results appendString:@"\nJS Execution\n"]; break;
case WKWebViewExecuteJs: name = @"WKWebView "; break;
}
MechanismTiming *timing = &_mechanismTimings[i];
double averageMs = [self machTimeToMs:timing->sum]/(double)kNumIterationsPerMechanisms;
@@ -325,3 +332,11 @@ -(void)stopLoading {
}
@end
@implementation NSString (URLEncode)
-(NSString *)URLEncode {
return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)self, NULL, (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
}
@end
@@ -1,13 +1,14 @@
var Mechanism = {
LocationHref: 0,
LocationHash: 1,
LinkClick: 2,
FrameSrc: 3,
XhrSync: 4,
XhrAsync: 5,
CookieChange: 6,
JavaScriptCore: 7,
UIWebViewExecuteJs: 10
LocationHashInOut: 2,
LinkClick: 3,
FrameSrc: 4,
XhrSync: 5,
XhrAsync: 6,
CookieChange: 7,
JavaScriptCore: 8,
UIWebViewExecuteJs: 12
};
// The link does not need to be appended to the document, that avoids triggering
@@ -26,6 +27,7 @@ function ping(mechanism, startTime) {
location.href = pongUrl;
break;
case Mechanism.LocationHash:
case Mechanism.LocationHashInOut:
location.hash = "#" + pongUrl;
break;
case Mechanism.LinkClick:
@@ -55,6 +57,16 @@ function ping(mechanism, startTime) {
}
}
var kPingHashPrefix = "#ping";
onhashchange = function(e) {
var hash = location.hash;
if (hash.lastIndexOf(kPingHashPrefix, 0) == 0) {
var pingParamsSerialized = decodeURIComponent(hash.substring(kPingHashPrefix.length));
var pingParams = JSON.parse(pingParamsSerialized);
ping(pingParams.mechanism, pingParams.startTime);
}
};
// Set up a periodic timer to show that timers are not affected by any of the
// communication mechanisms.
var pingCountNode = document.getElementById("ping-count");
@@ -1,7 +1,8 @@
var Mechanism = {
WKMessageHandler: 8,
WKLocationHash: 9,
WKWebViewExecuteJs: 11
WKMessageHandler: 9,
WKLocationHash: 10,
WKLocationHashInOut: 11,
WKWebViewExecuteJs: 13
};
var pingCount = 0;
@@ -12,6 +13,7 @@ function ping(mechanism, startTime) {
window.webkit.messageHandlers.pong.postMessage(startTime);
break;
case Mechanism.WKLocationHash:
case Mechanism.WKLocationHashInOut:
location.hash = "#pong://" + startTime;
break;
case Mechanism.WKWebViewExecuteJs:
@@ -20,6 +22,16 @@ function ping(mechanism, startTime) {
}
}
var kPingHashPrefix = "#ping";
onhashchange = function(e) {
var hash = location.hash;
if (hash.lastIndexOf(kPingHashPrefix, 0) == 0) {
var pingParamsSerialized = decodeURIComponent(hash.substring(kPingHashPrefix.length));
var pingParams = JSON.parse(pingParamsSerialized);
ping(pingParams.mechanism, pingParams.startTime);
}
};
// Set up a periodic timer to show that timers are not affected by any of the
// communication mechanisms.
var pingCountNode = document.getElementById("ping-count");

0 comments on commit 3df9ddd

Please sign in to comment.