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;
 }

Reply via email to