DWKWebView.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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"]:@"取消"
  98. otherButtonTitles:dialogTextDic[@"promptOkBtn"]?dialogTextDic[@"promptOkBtn"]:@"确定",
  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"]:@"确定"
  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"]:@"取消"
  157. otherButtonTitles:dialogTextDic[@"confirmOkBtn"]?dialogTextDic[@"confirmOkBtn"]:@"确定", 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)(id,BOOL) = ^(id value,BOOL complete){
  254. NSString *del=@"";
  255. result[@"code"]=@0;
  256. if(value!=nil){
  257. result[@"data"]=value;
  258. }
  259. value=[JSBUtil objToJsonString:result];
  260. value=[value stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  261. if(complete){
  262. del=[@"delete window." stringByAppendingString:cb];
  263. }
  264. NSString*js=[NSString stringWithFormat:@"try {%@(JSON.parse(decodeURIComponent(\"%@\")).data);%@; } catch(e){};",cb,(value == nil) ? @"" : value,del];
  265. __strong typeof(self) strongSelf = weakSelf;
  266. @synchronized(self)
  267. {
  268. UInt64 t=[[NSDate date] timeIntervalSince1970]*1000;
  269. jsCache=[jsCache stringByAppendingString:js];
  270. if(t-lastCallTime<50){
  271. if(!isPending){
  272. [strongSelf evalJavascript:50];
  273. isPending=true;
  274. }
  275. }else{
  276. [strongSelf evalJavascript:0];
  277. }
  278. }
  279. };
  280. void(*action)(id,SEL,id,id) = (void(*)(id,SEL,id,id))objc_msgSend;
  281. action(JavascriptInterfaceObject,selasyn,arg,completionHandler);
  282. break;
  283. }
  284. }else if([JavascriptInterfaceObject respondsToSelector:sel]){
  285. id ret;
  286. id(*action)(id,SEL,id) = (id(*)(id,SEL,id))objc_msgSend;
  287. ret=action(JavascriptInterfaceObject,sel,arg);
  288. [result setValue:@0 forKey:@"code"];
  289. if(ret!=nil){
  290. [result setValue:ret forKey:@"data"];
  291. }
  292. break;
  293. }
  294. NSString*js=[error stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  295. if(isDebug){
  296. js=[NSString stringWithFormat:@"window.alert(decodeURIComponent(\"%@\"));",js];
  297. [self evaluateJavaScript :js completionHandler:nil];
  298. }
  299. NSLog(@"%@",error);
  300. }while (0);
  301. }
  302. return [JSBUtil objToJsonString:result];
  303. }
  304. - (void)setJavascriptCloseWindowListener:(void (^)(void))callback
  305. {
  306. javascriptCloseWindowListener=callback;
  307. }
  308. - (void)setDebugMode:(bool)debug{
  309. isDebug=debug;
  310. }
  311. - (void)loadUrl: (NSString *)url
  312. {
  313. NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
  314. [self loadRequest:request];
  315. }
  316. - (void)callHandler:(NSString *)methodName arguments:(NSArray *)args{
  317. [self callHandler:methodName arguments:args completionHandler:nil];
  318. }
  319. - (void)callHandler:(NSString *)methodName completionHandler:(void (^)(id _Nullable))completionHandler{
  320. [self callHandler:methodName arguments:nil completionHandler:completionHandler];
  321. }
  322. -(void)callHandler:(NSString *)methodName arguments:(NSArray *)args completionHandler:(void (^)(id _Nullable value))completionHandler
  323. {
  324. DSCallInfo *callInfo=[[DSCallInfo alloc] init];
  325. callInfo.id=[NSNumber numberWithInt: callId++];
  326. callInfo.args=args==nil?@[]:args;
  327. callInfo.method=methodName;
  328. if(completionHandler){
  329. [handerMap setObject:completionHandler forKey:callInfo.id];
  330. }
  331. if(callInfoList!=nil){
  332. [callInfoList addObject:callInfo];
  333. }else{
  334. [self dispatchJavascriptCall:callInfo];
  335. }
  336. }
  337. - (void)dispatchStartupQueue{
  338. if(callInfoList==nil) return;
  339. for (DSCallInfo * callInfo in callInfoList) {
  340. [self dispatchJavascriptCall:callInfo];
  341. }
  342. callInfoList=nil;
  343. }
  344. - (void) dispatchJavascriptCall:(DSCallInfo*) info{
  345. NSString * json=[JSBUtil objToJsonString:@{@"method":info.method,@"callbackId":info.id,
  346. @"data":[JSBUtil objToJsonString: info.args]}];
  347. [self evaluateJavaScript:[NSString stringWithFormat:@"window._handleMessageFromNative(%@)",json]
  348. completionHandler:nil];
  349. }
  350. - (void) addJavascriptObject:(id)object namespace:(NSString *)namespace{
  351. if(namespace==nil){
  352. namespace=@"";
  353. }
  354. if(object!=NULL){
  355. [javaScriptNamespaceInterfaces setObject:object forKey:namespace];
  356. }
  357. }
  358. - (void) removeJavascriptObject:(NSString *)namespace {
  359. if(namespace==nil){
  360. namespace=@"";
  361. }
  362. [javaScriptNamespaceInterfaces removeObjectForKey:namespace];
  363. }
  364. - (void)customJavascriptDialogLabelTitles:(NSDictionary *)dic{
  365. if(dic){
  366. dialogTextDic=dic;
  367. }
  368. }
  369. - (id)onMessage:(NSDictionary *)msg type:(int)type{
  370. id ret=nil;
  371. switch (type) {
  372. case DSB_API_HASNATIVEMETHOD:
  373. ret= [self hasNativeMethod:msg]?@1:@0;
  374. break;
  375. case DSB_API_CLOSEPAGE:
  376. [self closePage:msg];
  377. break;
  378. case DSB_API_RETURNVALUE:
  379. ret=[self returnValue:msg];
  380. break;
  381. case DSB_API_DSINIT:
  382. ret=[self dsinit:msg];
  383. break;
  384. case DSB_API_DISABLESAFETYALERTBOX:
  385. [self disableJavascriptDialogBlock:[msg[@"disable"] boolValue]];
  386. break;
  387. default:
  388. break;
  389. }
  390. return ret;
  391. }
  392. - (bool) hasNativeMethod:(NSDictionary *) args
  393. {
  394. NSArray *nameStr=[JSBUtil parseNamespace:[args[@"name"]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
  395. NSString * type= [args[@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
  396. id JavascriptInterfaceObject= [javaScriptNamespaceInterfaces objectForKey:nameStr[0]];
  397. if(JavascriptInterfaceObject){
  398. bool syn=[JSBUtil methodByNameArg:1 selName:nameStr[1] class:[JavascriptInterfaceObject class]]!=nil;
  399. bool asyn=[JSBUtil methodByNameArg:2 selName:nameStr[1] class:[JavascriptInterfaceObject class]]!=nil;
  400. if(([@"all" isEqualToString:type]&&(syn||asyn))
  401. ||([@"asyn" isEqualToString:type]&&asyn)
  402. ||([@"syn" isEqualToString:type]&&syn)
  403. ){
  404. return true;
  405. }
  406. }
  407. return false;
  408. }
  409. - (id) closePage:(NSDictionary *) args{
  410. if(javascriptCloseWindowListener){
  411. javascriptCloseWindowListener();
  412. }
  413. return nil;
  414. }
  415. - (id) returnValue:(NSDictionary *) args{
  416. void (^ completionHandler)(NSString * _Nullable)= handerMap[args[@"id"]];
  417. if(completionHandler){
  418. if(isDebug){
  419. completionHandler(args[@"data"]);
  420. }else{
  421. @try{
  422. completionHandler(args[@"data"]);
  423. }@catch (NSException *e){
  424. NSLog(@"%@",e);
  425. }
  426. }
  427. if([args[@"complete"] boolValue]){
  428. [handerMap removeObjectForKey:args[@"id"]];
  429. }
  430. }
  431. return nil;
  432. }
  433. - (id) dsinit:(NSDictionary *) args{
  434. [self dispatchStartupQueue];
  435. return nil;
  436. }
  437. - (void) disableJavascriptDialogBlock:(bool) disable{
  438. jsDialogBlock=!disable;
  439. }
  440. - (void)hasJavascriptMethod:(NSString *)handlerName methodExistCallback:(void (^)(bool exist))callback{
  441. [self callHandler:@"_hasJavascriptMethod" arguments:@[handlerName] completionHandler:^(NSNumber* _Nullable value) {
  442. callback([value boolValue]);
  443. }];
  444. }
  445. @end