Updated Branches: refs/heads/master d2ffc92a5 -> c9f233250
[CB-2653] Delay executeScript/insertCSS callback until resources have loaded; pass JS results to callback Project: http://git-wip-us.apache.org/repos/asf/cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-ios/commit/c9f23325 Tree: http://git-wip-us.apache.org/repos/asf/cordova-ios/tree/c9f23325 Diff: http://git-wip-us.apache.org/repos/asf/cordova-ios/diff/c9f23325 Branch: refs/heads/master Commit: c9f233250d4b800f3412eeded811daaafb17b2cc Parents: d2ffc92 Author: Ian Clelland <[email protected]> Authored: Mon Mar 25 14:55:16 2013 -0400 Committer: Andrew Grieve <[email protected]> Committed: Thu Apr 4 14:45:56 2013 -0400 ---------------------------------------------------------------------- CordovaLib/Classes/CDVInAppBrowser.h | 1 - CordovaLib/Classes/CDVInAppBrowser.m | 120 +++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/c9f23325/CordovaLib/Classes/CDVInAppBrowser.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVInAppBrowser.h b/CordovaLib/Classes/CDVInAppBrowser.h index aa3e7a5..cb16569 100644 --- a/CordovaLib/Classes/CDVInAppBrowser.h +++ b/CordovaLib/Classes/CDVInAppBrowser.h @@ -41,7 +41,6 @@ - (void)open:(CDVInvokedUrlCommand*)command; - (void)close:(CDVInvokedUrlCommand*)command; - (void)injectScriptCode:(CDVInvokedUrlCommand*)command; -- (void)injectCSS:(CDVInvokedUrlCommand*)command; @end http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/c9f23325/CordovaLib/Classes/CDVInAppBrowser.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVInAppBrowser.m b/CordovaLib/Classes/CDVInAppBrowser.m index ef0acb3..1844a7f 100644 --- a/CordovaLib/Classes/CDVInAppBrowser.m +++ b/CordovaLib/Classes/CDVInAppBrowser.m @@ -20,6 +20,7 @@ #import "CDVInAppBrowser.h" #import "CDVPluginResult.h" #import "CDVUserAgentUtil.h" +#import "CDVJSON.h" #define kInAppBrowserTargetSelf @"_self" #define kInAppBrowserTargetSystem @"_system" @@ -159,24 +160,124 @@ } } +// This is a helper method for the inject{Script|Style}{Code|File} API calls, which +// provides a consistent method for injecting JavaScript code into the document. +// +// If a wrapper string is supplied, then the source string will be JSON-encoded (adding +// quotes) and wrapped using string formatting. (The wrapper string should have a single +// '%@' marker). +// +// If no wrapper is supplied, then the source string is executed directly. + +- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper +{ + if (jsWrapper != nil) { + NSString* sourceArrayString = [@[source] JSONString]; + if (sourceArrayString) { + NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)]; + NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString]; + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject]; + } + } else { + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source]; + } +} + - (void)injectScriptCode:(CDVInvokedUrlCommand*)command { - NSString* source = [command argumentAtIndex:0]; - CDVPluginResult* pluginResult; + NSString* jsWrapper = nil; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"document.getElementById('cdv-iab-bridge').src='gap-iab://%@/'+window.escape(JSON.stringify([eval(%%@)]));", command.callbackId]; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} - [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source]; +- (void)injectScriptFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { document.getElementById('cdv-iab-bridge').src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleCode:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { document.getElementById('cdv-iab-bridge').src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { document.getElementById('cdv-iab-bridge').src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; } #pragma mark CDVInAppBrowserNavigationDelegate +/** + * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging + * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no + * other code execution is possible. + * + * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form: + * + * gap-iab://<callbackId>/<arguments> + * + * where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789") + * + * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded + * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION + * is returned if the JSON is invalid. + */ - (void)browserLoadStart:(NSURL*)url { + CDVPluginResult* pluginResult; + + // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute, + // and the path, if present, should be a JSON-encoded value to pass to the callback. + if ([[url scheme] isEqualToString:@"gap-iab"]) { + NSString* scriptCallbackId = [url host]; + + if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) { + NSString* scriptResult = [url path]; + NSError* __autoreleasing error = nil; + + // The message should be a JSON-encoded array of the result of the script which executed. + if ((scriptResult != nil) && ([scriptResult length] > 1)) { + scriptResult = [scriptResult substringFromIndex:1]; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; + } + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; + return; + } + } if (self.callbackId != nil) { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; @@ -185,6 +286,8 @@ - (void)browserLoadStop:(NSURL*)url { + // Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e=d.createElement('iframe');e.id='cdv-iab-bridge';e.style.display='none';d.body.appendChild(e);})(document)"]; if (self.callbackId != nil) { CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"type":@"loadstop", @"url":[url absoluteString]}]; @@ -452,6 +555,9 @@ url = _requestedURL; } [self.navigationDelegate browserLoadStart:url]; + // Return NO if the URL uses the 'gap-iab' protocol, as this is used for the + // IAB bridge, rather than any actual web content. + return ![[url scheme] isEqualToString:@"gap-iab"]; } return YES; }
