AppRTCDemo(iOS): remote-video reliability fixes

Previously GAE Channel callbacks would be handled by JS string-encoding the
payload into a URL.  Unfortunately this is limited to the (undocumented,
silently problematic) maximum URL length UIWebView supports.  Replaced this
scheme by a notification from JS to ObjC and a getter from ObjC to JS (which
happens out-of-line to avoid worrying about UIWebView's re-entrancy, or lack
thereof).  Part of this change also moved from a combination of: JSON,
URL-escaping, and ad-hoc :-separated values to simply JSON.

Also incidentally:
- Removed outdated TODO about onRenegotiationNeeded, which is unneeded
- Move handling of PeerConnection callbacks to the main queue to avoid having
  to think about concurrency too hard.
- Replaced a bunch of NSOrderedSame with isEqualToString for clearer code and
  not having to worry about the fact that [nil compare:@"foo"]==NSOrderedSame
  is always true (yay ObjC!).
- Auto-scroll messages view.

BUG=3117
R=noahric@google.com

Review URL: https://webrtc-codereview.appspot.com/10899006

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5814 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
index 1b5e559..fcd0787 100644
--- a/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
+++ b/talk/examples/ios/AppRTCDemo/GAEChannelClient.m
@@ -63,41 +63,54 @@
 
 #pragma mark - UIWebViewDelegate method
 
++ (NSDictionary*)jsonStringToDictionary:(NSString*)str {
+  NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
+  NSError* error;
+  NSDictionary* dict =
+      [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
+  NSAssert(!error, @"Invalid JSON? %@", str);
+  return dict;
+}
+
 - (BOOL)webView:(UIWebView*)webView
     shouldStartLoadWithRequest:(NSURLRequest*)request
                 navigationType:(UIWebViewNavigationType)navigationType {
   NSString* scheme = [request.URL scheme];
-  if ([scheme compare:@"js-frame"] != NSOrderedSame) {
+  NSAssert(scheme, @"scheme is nil: %@", request);
+  if (![scheme isEqualToString:@"js-frame"]) {
     return YES;
   }
-  NSString* resourceSpecifier = [request.URL resourceSpecifier];
-  NSRange range = [resourceSpecifier rangeOfString:@":"];
-  NSString* method;
-  NSString* message;
-  if (range.length == 0 && range.location == NSNotFound) {
-    method = resourceSpecifier;
-  } else {
-    method = [resourceSpecifier substringToIndex:range.location];
-    message = [resourceSpecifier substringFromIndex:range.location + 1];
-  }
+
   dispatch_async(dispatch_get_main_queue(), ^(void) {
-      if ([method compare:@"onopen"] == NSOrderedSame) {
+      NSString* queuedMessage = [webView
+          stringByEvaluatingJavaScriptFromString:@"popQueuedMessage();"];
+      NSAssert([queuedMessage length], @"Empty queued message from JS");
+
+      NSDictionary* queuedMessageDict =
+          [GAEChannelClient jsonStringToDictionary:queuedMessage];
+      NSString* method = queuedMessageDict[@"type"];
+      NSAssert(method, @"Missing method: %@", queuedMessageDict);
+      NSDictionary* payload = queuedMessageDict[@"payload"];  // May be nil.
+
+      if ([method isEqualToString:@"onopen"]) {
         [self.delegate onOpen];
-      } else if ([method compare:@"onmessage"] == NSOrderedSame) {
-        [self.delegate onMessage:message];
-      } else if ([method compare:@"onclose"] == NSOrderedSame) {
+      } else if ([method isEqualToString:@"onmessage"]) {
+        NSDictionary* payloadData =
+            [GAEChannelClient jsonStringToDictionary:payload[@"data"]];
+        [self.delegate onMessage:payloadData];
+      } else if ([method isEqualToString:@"onclose"]) {
         [self.delegate onClose];
-      } else if ([method compare:@"onerror"] == NSOrderedSame) {
-        // TODO(hughv): Get error.
-        int code = -1;
-        NSString* description = message;
-        [self.delegate onError:code withDescription:description];
+      } else if ([method isEqualToString:@"onerror"]) {
+        NSNumber* codeNumber = payload[@"code"];
+        int code = [codeNumber intValue];
+        NSAssert([codeNumber isEqualToNumber:[NSNumber numberWithInt:code]],
+                 @"Unexpected non-integral code: %@", payload);
+        [self.delegate onError:code withDescription:payload[@"description"]];
       } else {
-        NSAssert(
-            NO, @"Invalid message sent from UIWebView: %@", resourceSpecifier);
+        NSAssert(NO, @"Invalid message sent from UIWebView: %@", queuedMessage);
       }
   });
-  return YES;
+  return NO;
 }
 
 @end