Bez popisu

OldWebViewJavascriptBridge.m 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. //
  2. // WebViewJavascriptBridge.m
  3. // ExampleApp-iOS
  4. //
  5. // Created by Marcus Westin on 6/14/13.
  6. // Copyright (c) 2013 Marcus Westin. All rights reserved.
  7. //
  8. #import "OldWebViewJavascriptBridge.h"
  9. #if __has_feature(objc_arc_weak)
  10. #define WVJB_WEAK __weak
  11. #else
  12. #define WVJB_WEAK __unsafe_unretained
  13. #endif
  14. typedef NSDictionary WVJBMessage;
  15. @implementation OldWebViewJavascriptBridge {
  16. WVJB_WEAK WVJB_WEBVIEW_TYPE* _webView;
  17. WVJB_WEAK id _webViewDelegate;
  18. NSMutableArray* _startupMessageQueue;
  19. NSMutableDictionary* _responseCallbacks;
  20. NSMutableDictionary* _messageHandlers;
  21. long _uniqueId;
  22. WVJBHandler _messageHandler;
  23. NSBundle *_resourceBundle;
  24. #if defined WVJB_PLATFORM_IOS
  25. NSUInteger _numRequestsLoading;
  26. #endif
  27. }
  28. /* API
  29. *****/
  30. static bool logging = false;
  31. + (void)enableLogging { logging = true; }
  32. + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView handler:(WVJBHandler)handler {
  33. return [self bridgeForWebView:webView webViewDelegate:nil handler:handler];
  34. }
  35. + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)messageHandler {
  36. return [self bridgeForWebView:webView webViewDelegate:webViewDelegate handler:messageHandler resourceBundle:nil];
  37. }
  38. + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle
  39. {
  40. OldWebViewJavascriptBridge* bridge = [[OldWebViewJavascriptBridge alloc] init];
  41. [bridge _platformSpecificSetup:webView webViewDelegate:webViewDelegate handler:messageHandler resourceBundle:bundle];
  42. [bridge reset];
  43. return bridge;
  44. }
  45. - (void)send:(id)data {
  46. [self send:data responseCallback:nil];
  47. }
  48. - (void)send:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
  49. [self _sendData:data responseCallback:responseCallback handlerName:nil];
  50. }
  51. - (void)callHandler:(NSString *)handlerName {
  52. [self callHandler:handlerName data:nil responseCallback:nil];
  53. }
  54. - (void)callHandler:(NSString *)handlerName data:(id)data {
  55. [self callHandler:handlerName data:data responseCallback:nil];
  56. }
  57. - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
  58. [self _sendData:data responseCallback:responseCallback handlerName:handlerName];
  59. }
  60. - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
  61. _messageHandlers[handlerName] = [handler copy];
  62. }
  63. - (void)reset {
  64. _startupMessageQueue = [NSMutableArray array];
  65. _responseCallbacks = [NSMutableDictionary dictionary];
  66. _uniqueId = 0;
  67. }
  68. /* Platform agnostic internals
  69. *****************************/
  70. - (void)dealloc {
  71. [self _platformSpecificDealloc];
  72. _webView = nil;
  73. _webViewDelegate = nil;
  74. _startupMessageQueue = nil;
  75. _responseCallbacks = nil;
  76. _messageHandlers = nil;
  77. _messageHandler = nil;
  78. }
  79. - (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
  80. NSMutableDictionary* message = [NSMutableDictionary dictionary];
  81. if (data) {
  82. message[@"data"] = data;
  83. }
  84. if (responseCallback) {
  85. NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
  86. _responseCallbacks[callbackId] = [responseCallback copy];
  87. message[@"callbackId"] = callbackId;
  88. }
  89. if (handlerName) {
  90. message[@"handlerName"] = handlerName;
  91. }
  92. [self _queueMessage:message];
  93. }
  94. - (void)_queueMessage:(WVJBMessage*)message {
  95. if (_startupMessageQueue) {
  96. [_startupMessageQueue addObject:message];
  97. } else {
  98. [self _dispatchMessage:message];
  99. }
  100. }
  101. - (void)_dispatchMessage:(WVJBMessage*)message {
  102. NSString *messageJSON = [self _serializeMessage:message];
  103. [self _log:@"SEND" json:messageJSON];
  104. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
  105. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
  106. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
  107. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
  108. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
  109. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
  110. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
  111. messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
  112. NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
  113. if ([[NSThread currentThread] isMainThread]) {
  114. [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
  115. } else {
  116. __strong WVJB_WEBVIEW_TYPE* strongWebView = _webView;
  117. dispatch_sync(dispatch_get_main_queue(), ^{
  118. [strongWebView stringByEvaluatingJavaScriptFromString:javascriptCommand];
  119. });
  120. }
  121. }
  122. - (void)_flushMessageQueue {
  123. NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
  124. id messages = [self _deserializeMessageJSON:messageQueueString];
  125. if (![messages isKindOfClass:[NSArray class]]) {
  126. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
  127. return;
  128. }
  129. for (WVJBMessage* message in messages) {
  130. if (![message isKindOfClass:[WVJBMessage class]]) {
  131. NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
  132. continue;
  133. }
  134. [self _log:@"RCVD" json:message];
  135. NSString* responseId = message[@"responseId"];
  136. if (responseId) {
  137. WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
  138. responseCallback(message[@"responseData"]);
  139. [_responseCallbacks removeObjectForKey:responseId];
  140. } else {
  141. WVJBResponseCallback responseCallback = NULL;
  142. NSString* callbackId = message[@"callbackId"];
  143. if (callbackId) {
  144. responseCallback = ^(id responseData) {
  145. if (responseData == nil) {
  146. responseData = [NSNull null];
  147. }
  148. WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
  149. [self _queueMessage:msg];
  150. };
  151. } else {
  152. responseCallback = ^(id ignoreResponseData) {
  153. // Do nothing
  154. };
  155. }
  156. WVJBHandler handler;
  157. if (message[@"handlerName"]) {
  158. handler = _messageHandlers[message[@"handlerName"]];
  159. } else {
  160. handler = _messageHandler;
  161. }
  162. if (!handler) {
  163. [NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
  164. }
  165. handler(message[@"data"], responseCallback);
  166. }
  167. }
  168. }
  169. - (NSString *)_serializeMessage:(id)message {
  170. return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:message options:0 error:nil] encoding:NSUTF8StringEncoding];
  171. }
  172. - (NSArray*)_deserializeMessageJSON:(NSString *)messageJSON {
  173. return [NSJSONSerialization JSONObjectWithData:[messageJSON dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
  174. }
  175. - (void)_log:(NSString *)action json:(id)json {
  176. if (!logging) { return; }
  177. if (![json isKindOfClass:[NSString class]]) {
  178. json = [self _serializeMessage:json];
  179. }
  180. if ([json length] > 500) {
  181. NSLog(@"WVJB %@: %@ [...]", action, [json substringToIndex:500]);
  182. } else {
  183. NSLog(@"WVJB %@: %@", action, json);
  184. }
  185. }
  186. /* Platform specific internals: OSX
  187. **********************************/
  188. #if defined WVJB_PLATFORM_OSX
  189. - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle{
  190. _messageHandler = messageHandler;
  191. _webView = webView;
  192. _webViewDelegate = webViewDelegate;
  193. _messageHandlers = [NSMutableDictionary dictionary];
  194. _webView.frameLoadDelegate = self;
  195. _webView.resourceLoadDelegate = self;
  196. _webView.policyDelegate = self;
  197. _resourceBundle = bundle;
  198. }
  199. - (void) _platformSpecificDealloc {
  200. _webView.frameLoadDelegate = nil;
  201. _webView.resourceLoadDelegate = nil;
  202. _webView.policyDelegate = nil;
  203. }
  204. - (void)webView:(WebView *)webView didFinishLoadForFrame:(WebFrame *)frame
  205. {
  206. if (webView != _webView) { return; }
  207. if (![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
  208. NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
  209. NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
  210. NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
  211. [webView stringByEvaluatingJavaScriptFromString:js];
  212. }
  213. if (_startupMessageQueue) {
  214. for (id queuedMessage in _startupMessageQueue) {
  215. [self _dispatchMessage:queuedMessage];
  216. }
  217. _startupMessageQueue = nil;
  218. }
  219. if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:didFinishLoadForFrame:)]) {
  220. [_webViewDelegate webView:webView didFinishLoadForFrame:frame];
  221. }
  222. }
  223. - (void)webView:(WebView *)webView didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame {
  224. if (webView != _webView) { return; }
  225. if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:didFailLoadWithError:forFrame:)]) {
  226. [_webViewDelegate webView:webView didFailLoadWithError:error forFrame:frame];
  227. }
  228. }
  229. - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
  230. {
  231. if (webView != _webView) { return; }
  232. NSURL *url = [request URL];
  233. if ([[url scheme] isEqualToString:kCustomProtocolScheme]) {
  234. if ([[url host] isEqualToString:kQueueHasMessage]) {
  235. [self _flushMessageQueue];
  236. } else {
  237. NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);
  238. }
  239. [listener ignore];
  240. } else if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:request:frame:decisionListener:)]) {
  241. [_webViewDelegate webView:webView decidePolicyForNavigationAction:actionInformation request:request frame:frame decisionListener:listener];
  242. } else {
  243. [listener use];
  244. }
  245. }
  246. - (void)webView:(WebView *)webView didCommitLoadForFrame:(WebFrame *)frame {
  247. if (webView != _webView) { return; }
  248. if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:didCommitLoadForFrame:)]) {
  249. [_webViewDelegate webView:webView didCommitLoadForFrame:frame];
  250. }
  251. }
  252. - (NSURLRequest *)webView:(WebView *)webView resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource {
  253. if (webView != _webView) { return request; }
  254. if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:resource:willSendRequest:redirectResponse:fromDataSource:)]) {
  255. return [_webViewDelegate webView:webView resource:identifier willSendRequest:request redirectResponse:redirectResponse fromDataSource:dataSource];
  256. }
  257. return request;
  258. }
  259. /* Platform specific internals: iOS
  260. **********************************/
  261. #elif defined WVJB_PLATFORM_IOS
  262. - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(id<UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle{
  263. _messageHandler = messageHandler;
  264. _webView = webView;
  265. _webViewDelegate = webViewDelegate;
  266. _messageHandlers = [NSMutableDictionary dictionary];
  267. _webView.delegate = self;
  268. _resourceBundle = bundle;
  269. }
  270. - (void) _platformSpecificDealloc {
  271. _webView.delegate = nil;
  272. }
  273. - (void)webViewDidFinishLoad:(UIWebView *)webView {
  274. if (webView != _webView) { return; }
  275. _numRequestsLoading--;
  276. if (_numRequestsLoading == 0 && ![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
  277. NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle];
  278. NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
  279. NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
  280. [webView stringByEvaluatingJavaScriptFromString:js];
  281. }
  282. if (_startupMessageQueue) {
  283. for (id queuedMessage in _startupMessageQueue) {
  284. [self _dispatchMessage:queuedMessage];
  285. }
  286. _startupMessageQueue = nil;
  287. }
  288. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  289. if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
  290. [strongDelegate webViewDidFinishLoad:webView];
  291. }
  292. }
  293. - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
  294. if (webView != _webView) { return; }
  295. _numRequestsLoading--;
  296. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  297. if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
  298. [strongDelegate webView:webView didFailLoadWithError:error];
  299. }
  300. }
  301. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  302. if (webView != _webView) { return YES; }
  303. NSURL *url = [request URL];
  304. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  305. if ([[url scheme] isEqualToString:kCustomProtocolScheme]) {
  306. if ([[url host] isEqualToString:kQueueHasMessage]) {
  307. [self _flushMessageQueue];
  308. } else {
  309. NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);
  310. }
  311. return NO;
  312. } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
  313. return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
  314. } else {
  315. return YES;
  316. }
  317. }
  318. - (void)webViewDidStartLoad:(UIWebView *)webView {
  319. if (webView != _webView) { return; }
  320. _numRequestsLoading++;
  321. __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
  322. if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
  323. [strongDelegate webViewDidStartLoad:webView];
  324. }
  325. }
  326. #endif
  327. @end