DWKWebView.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. #import "DWKWebView.h"
  2. #import "JSBUtil.h"
  3. #import "DSCallInfo.h"
  4. #import "InternalApis.h"
  5. #import <objc/message.h>
  6. @implementation DWKWebView
  7. {
  8. void (^alertHandler)(void);
  9. void (^confirmHandler)(BOOL);
  10. void (^promptHandler)(NSString *);
  11. void(^javascriptCloseWindowListener)(void);
  12. int dialogType;
  13. int callId;
  14. bool jsDialogBlock;
  15. NSMutableDictionary<NSString *,id> *javaScriptNamespaceInterfaces;
  16. NSMutableDictionary *handerMap;
  17. NSMutableArray<DSCallInfo *> * callInfoList;
  18. NSDictionary<NSString*,NSString*> *dialogTextDic;
  19. UITextField *txtName;
  20. UInt64 lastCallTime ;
  21. NSString *jsCache;
  22. bool isPending;
  23. bool isDebug;
  24. }
  25. -(instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
  26. {
  27. txtName=nil;
  28. dialogType=0;
  29. callId=0;
  30. alertHandler=nil;
  31. confirmHandler=nil;
  32. promptHandler=nil;
  33. jsDialogBlock=true;
  34. callInfoList=[NSMutableArray array];
  35. javaScriptNamespaceInterfaces=[NSMutableDictionary dictionary];
  36. handerMap=[NSMutableDictionary dictionary];
  37. lastCallTime = 0;
  38. jsCache=@"";
  39. isPending=false;
  40. isDebug=false;
  41. dialogTextDic=@{};
  42. WKUserScript *script = [[WKUserScript alloc] initWithSource:@"window._dswk=true;"
  43. injectionTime:WKUserScriptInjectionTimeAtDocumentStart
  44. forMainFrameOnly:YES];
  45. [configuration.userContentController addUserScript:script];
  46. self = [super initWithFrame:frame configuration: configuration];
  47. if (self) {
  48. super.UIDelegate=self;
  49. }
  50. // add internal Javascript Object
  51. InternalApis * interalApis= [[InternalApis alloc] init];
  52. interalApis.webview=self;
  53. [self addJavascriptObject:interalApis namespace:@"_dsb"];
  54. return self;
  55. }
  56. - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
  57. defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame
  58. completionHandler:(void (^)(NSString * _Nullable result))completionHandler
  59. {
  60. NSString * prefix=@"_dsbridge=";
  61. if ([prompt hasPrefix:prefix])
  62. {
  63. NSString *method= [prompt substringFromIndex:[prefix length]];
  64. NSString *result=nil;
  65. if(isDebug){
  66. result =[self call:method :defaultText ];
  67. }else{
  68. @try {
  69. result =[self call:method :defaultText ];
  70. }@catch(NSException *exception){
  71. NSLog(@"%@", exception);
  72. }
  73. }
  74. completionHandler(result);
  75. }else {
  76. if(!jsDialogBlock){
  77. completionHandler(nil);
  78. }
  79. if(self.DSUIDelegate && [self.DSUIDelegate respondsToSelector:
  80. @selector(webView:runJavaScriptTextInputPanelWithPrompt
  81. :defaultText:initiatedByFrame
  82. :completionHandler:)])
  83. {
  84. return [self.DSUIDelegate webView:webView runJavaScriptTextInputPanelWithPrompt:prompt
  85. defaultText:defaultText
  86. initiatedByFrame:frame
  87. completionHandler:completionHandler];
  88. }else{
  89. dialogType=3;
  90. if(jsDialogBlock){
  91. promptHandler=completionHandler;
  92. }
  93. UIAlertView *alert = [[UIAlertView alloc]
  94. initWithTitle:prompt
  95. message:@""
  96. delegate:self
  97. cancelButtonTitle:dialogTextDic[@"promptCancelBtn"]?dialogTextDic[@"promptCancelBtn"]:WFCString(@"Cancel")
  98. otherButtonTitles:dialogTextDic[@"promptOkBtn"]?dialogTextDic[@"promptOkBtn"]:WFCString(@"Ok"),
  99. nil];
  100. [alert setAlertViewStyle:UIAlertViewStylePlainTextInput];
  101. txtName = [alert textFieldAtIndex:0];
  102. txtName.text=defaultText;
  103. [alert show];
  104. }
  105. }
  106. }
  107. - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message
  108. initiatedByFrame:(WKFrameInfo *)frame
  109. completionHandler:(void (^)(void))completionHandler
  110. {
  111. if(!jsDialogBlock){
  112. completionHandler();
  113. }
  114. if( self.DSUIDelegate && [self.DSUIDelegate respondsToSelector:
  115. @selector(webView:runJavaScriptAlertPanelWithMessage
  116. :initiatedByFrame:completionHandler:)])
  117. {
  118. return [self.DSUIDelegate webView:webView runJavaScriptAlertPanelWithMessage:message
  119. initiatedByFrame:frame
  120. completionHandler:completionHandler];
  121. }else{
  122. dialogType=1;
  123. if(jsDialogBlock){
  124. alertHandler=completionHandler;
  125. }
  126. UIAlertView *alertView =
  127. [[UIAlertView alloc] initWithTitle:dialogTextDic[@"alertTitle"]?dialogTextDic[@"alertTitle"]:@"提示"
  128. message:message
  129. delegate:self
  130. cancelButtonTitle:dialogTextDic[@"alertBtn"]?dialogTextDic[@"alertBtn"]:WFCString(@"Ok")
  131. otherButtonTitles:nil,nil];
  132. [alertView show];
  133. }
  134. }
  135. -(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message
  136. initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
  137. {
  138. if(!jsDialogBlock){
  139. completionHandler(YES);
  140. }
  141. if( self.DSUIDelegate&& [self.DSUIDelegate respondsToSelector:
  142. @selector(webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:)])
  143. {
  144. return[self.DSUIDelegate webView:webView runJavaScriptConfirmPanelWithMessage:message
  145. initiatedByFrame:frame
  146. completionHandler:completionHandler];
  147. }else{
  148. dialogType=2;
  149. if(jsDialogBlock){
  150. confirmHandler=completionHandler;
  151. }
  152. UIAlertView *alertView =
  153. [[UIAlertView alloc] initWithTitle:dialogTextDic[@"confirmTitle"]?dialogTextDic[@"confirmTitle"]:@"提示"
  154. message:message
  155. delegate:self
  156. cancelButtonTitle:dialogTextDic[@"confirmCancelBtn"]?dialogTextDic[@"confirmCancelBtn"]:WFCString(@"Cancel")
  157. otherButtonTitles:dialogTextDic[@"confirmOkBtn"]?dialogTextDic[@"confirmOkBtn"]:WFCString(@"Ok"), nil];
  158. [alertView show];
  159. }
  160. }
  161. - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
  162. if( self.DSUIDelegate && [self.DSUIDelegate respondsToSelector:
  163. @selector(webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:)]){
  164. return [self.DSUIDelegate webView:webView createWebViewWithConfiguration:configuration forNavigationAction:navigationAction windowFeatures:windowFeatures];
  165. }
  166. return nil;
  167. }
  168. - (void)webViewDidClose:(WKWebView *)webView{
  169. if( self.DSUIDelegate && [self.DSUIDelegate respondsToSelector:
  170. @selector(webViewDidClose:)]){
  171. [self.DSUIDelegate webViewDidClose:webView];
  172. }
  173. }
  174. - (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo{
  175. if( self.DSUIDelegate
  176. && [self.DSUIDelegate respondsToSelector:
  177. @selector(webView:shouldPreviewElement:)]){
  178. return [self.DSUIDelegate webView:webView shouldPreviewElement:elementInfo];
  179. }
  180. return NO;
  181. }
  182. - (UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id<WKPreviewActionItem>> *)previewActions{
  183. if( self.DSUIDelegate &&
  184. [self.DSUIDelegate respondsToSelector:@selector(webView:previewingViewControllerForElement:defaultActions:)]){
  185. return [self.DSUIDelegate
  186. webView:webView
  187. previewingViewControllerForElement:elementInfo
  188. defaultActions:previewActions
  189. ];
  190. }
  191. return nil;
  192. }
  193. - (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController{
  194. if( self.DSUIDelegate
  195. && [self.DSUIDelegate respondsToSelector:@selector(webView:commitPreviewingViewController:)]){
  196. return [self.DSUIDelegate webView:webView commitPreviewingViewController:previewingViewController];
  197. }
  198. }
  199. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
  200. {
  201. if(dialogType==1 && alertHandler){
  202. alertHandler();
  203. alertHandler=nil;
  204. }else if(dialogType==2 && confirmHandler){
  205. confirmHandler(buttonIndex==1?YES:NO);
  206. confirmHandler=nil;
  207. }else if(dialogType==3 && promptHandler && txtName) {
  208. if(buttonIndex==1){
  209. promptHandler([txtName text]);
  210. }else{
  211. promptHandler(@"");
  212. }
  213. promptHandler=nil;
  214. txtName=nil;
  215. }
  216. }
  217. - (void) evalJavascript:(int) delay{
  218. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
  219. @synchronized(self){
  220. if([jsCache length]!=0){
  221. [self evaluateJavaScript :jsCache completionHandler:nil];
  222. isPending=false;
  223. jsCache=@"";
  224. lastCallTime=[[NSDate date] timeIntervalSince1970]*1000;
  225. }
  226. }
  227. });
  228. }
  229. -(NSString *)call:(NSString*) method :(NSString*) argStr
  230. {
  231. NSArray *nameStr=[JSBUtil parseNamespace:[method stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
  232. id JavascriptInterfaceObject=javaScriptNamespaceInterfaces[nameStr[0]];
  233. NSString *error=[NSString stringWithFormat:@"Error! \n Method %@ is not invoked, since there is not a implementation for it",method];
  234. NSMutableDictionary*result =[NSMutableDictionary dictionaryWithDictionary:@{@"code":@-1,@"data":@""}];
  235. if(!JavascriptInterfaceObject){
  236. NSLog(@"Js bridge called, but can't find a corresponded JavascriptObject , please check your code!");
  237. }else{
  238. method=nameStr[1];
  239. NSString *methodOne = [JSBUtil methodByNameArg:1 selName:method class:[JavascriptInterfaceObject class]];
  240. NSString *methodTwo = [JSBUtil methodByNameArg:2 selName:method class:[JavascriptInterfaceObject class]];
  241. SEL sel=NSSelectorFromString(methodOne);
  242. SEL selasyn=NSSelectorFromString(methodTwo);
  243. NSDictionary * args=[JSBUtil jsonStringToObject:argStr];
  244. id arg=args[@"data"];
  245. if(arg==[NSNull null]){
  246. arg=nil;
  247. }
  248. NSString * cb;
  249. do{
  250. if(args && (cb= args[@"_dscbstub"])){
  251. if([JavascriptInterfaceObject respondsToSelector:selasyn]){
  252. __weak typeof(self) weakSelf = self;
  253. void (^completionHandler)(int, id,BOOL) = ^(int code, id value,BOOL complete){
  254. NSString *del=@"";
  255. result[@"code"]=@(0);
  256. NSMutableDictionary *jsdata = [[NSMutableDictionary alloc] init];
  257. jsdata[@"code"]=@(code);
  258. if(value!=nil){
  259. jsdata[@"data"]=value;
  260. }
  261. result[@"data"] = jsdata;
  262. value=[JSBUtil objToJsonString:result];
  263. value=[value stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  264. if(complete){
  265. del=[@"delete window." stringByAppendingString:cb];
  266. }
  267. NSString*js=[NSString stringWithFormat:@"try {%@(JSON.parse(decodeURIComponent(\"%@\")).data);%@; } catch(e){};",cb,(value == nil) ? @"" : value,del];
  268. __strong typeof(self) strongSelf = weakSelf;
  269. @synchronized(self)
  270. {
  271. UInt64 t=[[NSDate date] timeIntervalSince1970]*1000;
  272. jsCache=[jsCache stringByAppendingString:js];
  273. if(t-lastCallTime<50){
  274. if(!isPending){
  275. [strongSelf evalJavascript:50];
  276. isPending=true;
  277. }
  278. }else{
  279. [strongSelf evalJavascript:0];
  280. }
  281. }
  282. };
  283. void(*action)(id,SEL,id,id) = (void(*)(id,SEL,id,id))objc_msgSend;
  284. action(JavascriptInterfaceObject,selasyn,arg,completionHandler);
  285. break;
  286. }
  287. }else if([JavascriptInterfaceObject respondsToSelector:sel]){
  288. id ret;
  289. id(*action)(id,SEL,id) = (id(*)(id,SEL,id))objc_msgSend;
  290. ret=action(JavascriptInterfaceObject,sel,arg);
  291. [result setValue:@0 forKey:@"code"];
  292. if(ret!=nil){
  293. [result setValue:ret forKey:@"data"];
  294. }
  295. break;
  296. }
  297. NSString*js=[error stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  298. if(isDebug){
  299. js=[NSString stringWithFormat:@"window.alert(decodeURIComponent(\"%@\"));",js];
  300. [self evaluateJavaScript :js completionHandler:nil];
  301. }
  302. NSLog(@"%@",error);
  303. }while (0);
  304. }
  305. return [JSBUtil objToJsonString:result];
  306. }
  307. - (void)setJavascriptCloseWindowListener:(void (^)(void))callback
  308. {
  309. javascriptCloseWindowListener=callback;
  310. }
  311. - (void)setDebugMode:(bool)debug{
  312. isDebug=debug;
  313. }
  314. - (void)loadUrl: (NSString *)url
  315. {
  316. NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
  317. [self loadRequest:request];
  318. }
  319. - (void)callHandler:(NSString *)methodName arguments:(NSArray *)args{
  320. [self callHandler:methodName arguments:args completionHandler:nil];
  321. }
  322. - (void)callHandler:(NSString *)methodName completionHandler:(void (^)(id _Nullable))completionHandler{
  323. [self callHandler:methodName arguments:nil completionHandler:completionHandler];
  324. }
  325. -(void)callHandler:(NSString *)methodName arguments:(NSArray *)args completionHandler:(void (^)(id _Nullable value))completionHandler
  326. {
  327. DSCallInfo *callInfo=[[DSCallInfo alloc] init];
  328. callInfo.id=[NSNumber numberWithInt: callId++];
  329. callInfo.args=args==nil?@[]:args;
  330. callInfo.method=methodName;
  331. if(completionHandler){
  332. [handerMap setObject:completionHandler forKey:callInfo.id];
  333. }
  334. if(callInfoList!=nil){
  335. [callInfoList addObject:callInfo];
  336. }else{
  337. [self dispatchJavascriptCall:callInfo];
  338. }
  339. }
  340. - (void)dispatchStartupQueue{
  341. if(callInfoList==nil) return;
  342. for (DSCallInfo * callInfo in callInfoList) {
  343. [self dispatchJavascriptCall:callInfo];
  344. }
  345. callInfoList=nil;
  346. }
  347. - (void) dispatchJavascriptCall:(DSCallInfo*) info{
  348. NSString * json=[JSBUtil objToJsonString:@{@"method":info.method,@"callbackId":info.id,
  349. @"data":[JSBUtil objToJsonString: info.args]}];
  350. [self evaluateJavaScript:[NSString stringWithFormat:@"window._handleMessageFromNative(%@)",json]
  351. completionHandler:nil];
  352. }
  353. - (void) addJavascriptObject:(id)object namespace:(NSString *)namespace{
  354. if(namespace==nil){
  355. namespace=@"";
  356. }
  357. if(object!=NULL){
  358. [javaScriptNamespaceInterfaces setObject:object forKey:namespace];
  359. }
  360. }
  361. - (void) removeJavascriptObject:(NSString *)namespace {
  362. if(namespace==nil){
  363. namespace=@"";
  364. }
  365. [javaScriptNamespaceInterfaces removeObjectForKey:namespace];
  366. }
  367. - (void)customJavascriptDialogLabelTitles:(NSDictionary *)dic{
  368. if(dic){
  369. dialogTextDic=dic;
  370. }
  371. }
  372. - (id)onMessage:(NSDictionary *)msg type:(int)type{
  373. id ret=nil;
  374. switch (type) {
  375. case DSB_API_HASNATIVEMETHOD:
  376. ret= [self hasNativeMethod:msg]?@1:@0;
  377. break;
  378. case DSB_API_CLOSEPAGE:
  379. [self closePage:msg];
  380. break;
  381. case DSB_API_RETURNVALUE:
  382. ret=[self returnValue:msg];
  383. break;
  384. case DSB_API_DSINIT:
  385. ret=[self dsinit:msg];
  386. break;
  387. case DSB_API_DISABLESAFETYALERTBOX:
  388. [self disableJavascriptDialogBlock:[msg[@"disable"] boolValue]];
  389. break;
  390. default:
  391. break;
  392. }
  393. return ret;
  394. }
  395. - (bool) hasNativeMethod:(NSDictionary *) args
  396. {
  397. NSArray *nameStr=[JSBUtil parseNamespace:[args[@"name"]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
  398. NSString * type= [args[@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
  399. id JavascriptInterfaceObject= [javaScriptNamespaceInterfaces objectForKey:nameStr[0]];
  400. if(JavascriptInterfaceObject){
  401. bool syn=[JSBUtil methodByNameArg:1 selName:nameStr[1] class:[JavascriptInterfaceObject class]]!=nil;
  402. bool asyn=[JSBUtil methodByNameArg:2 selName:nameStr[1] class:[JavascriptInterfaceObject class]]!=nil;
  403. if(([@"all" isEqualToString:type]&&(syn||asyn))
  404. ||([@"asyn" isEqualToString:type]&&asyn)
  405. ||([@"syn" isEqualToString:type]&&syn)
  406. ){
  407. return true;
  408. }
  409. }
  410. return false;
  411. }
  412. - (id) closePage:(NSDictionary *) args{
  413. if(javascriptCloseWindowListener){
  414. javascriptCloseWindowListener();
  415. }
  416. return nil;
  417. }
  418. - (id) returnValue:(NSDictionary *) args{
  419. void (^ completionHandler)(NSString * _Nullable)= handerMap[args[@"id"]];
  420. if(completionHandler){
  421. if(isDebug){
  422. completionHandler(args[@"data"]);
  423. }else{
  424. @try{
  425. completionHandler(args[@"data"]);
  426. }@catch (NSException *e){
  427. NSLog(@"%@",e);
  428. }
  429. }
  430. if([args[@"complete"] boolValue]){
  431. [handerMap removeObjectForKey:args[@"id"]];
  432. }
  433. }
  434. return nil;
  435. }
  436. - (id) dsinit:(NSDictionary *) args{
  437. [self dispatchStartupQueue];
  438. return nil;
  439. }
  440. - (void) disableJavascriptDialogBlock:(bool) disable{
  441. jsDialogBlock=!disable;
  442. }
  443. - (void)hasJavascriptMethod:(NSString *)handlerName methodExistCallback:(void (^)(bool exist))callback{
  444. [self callHandler:@"_hasJavascriptMethod" arguments:@[handlerName] completionHandler:^(NSNumber* _Nullable value) {
  445. callback([value boolValue]);
  446. }];
  447. }
  448. @end