cronapp-cordova-plugin-ionic-webview 4.4.0-RC.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,886 @@
1
+ /*
2
+ Licensed to the Apache Software Foundation (ASF) under one
3
+ or more contributor license agreements. See the NOTICE file
4
+ distributed with this work for additional information
5
+ regarding copyright ownership. The ASF licenses this file
6
+ to you under the Apache License, Version 2.0 (the
7
+ "License"); you may not use this file except in compliance
8
+ with the License. You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing,
13
+ software distributed under the License is distributed on an
14
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ KIND, either express or implied. See the License for the
16
+ specific language governing permissions and limitations
17
+ under the License.
18
+
19
+ Modifications copyright (c) 2026 Magellandevs
20
+ */
21
+
22
+ #import <Cordova/NSDictionary+CordovaPreferences.h>
23
+ #import <MobileCoreServices/MobileCoreServices.h>
24
+ #import <AVFoundation/AVFoundation.h>
25
+ #import <objc/message.h>
26
+ #import <objc/runtime.h>
27
+
28
+ #import "CDVWKWebViewEngine.h"
29
+ #import "CDVWKWebViewUIDelegate.h"
30
+ #import "CDVWKProcessPoolFactory.h"
31
+ #import "IONAssetHandler.h"
32
+
33
+ #define CDV_BRIDGE_NAME @"cordova"
34
+ #define CDV_IONIC_STOP_SCROLL @"stopScroll"
35
+ #define CDV_SERVER_PATH @"serverBasePath"
36
+ #define LAST_BINARY_VERSION_CODE @"lastBinaryVersionCode"
37
+ #define LAST_BINARY_VERSION_NAME @"lastBinaryVersionName"
38
+
39
+ @implementation UIScrollView (BugIOS11)
40
+
41
+ + (void)load {
42
+ static dispatch_once_t onceToken;
43
+ dispatch_once(&onceToken, ^{
44
+ Class class = [self class];
45
+ SEL originalSelector = @selector(init);
46
+ SEL swizzledSelector = @selector(xxx_init);
47
+
48
+ Method originalMethod = class_getInstanceMethod(class, originalSelector);
49
+ Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
50
+
51
+ BOOL didAddMethod =
52
+ class_addMethod(class,
53
+ originalSelector,
54
+ method_getImplementation(swizzledMethod),
55
+ method_getTypeEncoding(swizzledMethod));
56
+
57
+ if (didAddMethod) {
58
+ class_replaceMethod(class,
59
+ swizzledSelector,
60
+ method_getImplementation(originalMethod),
61
+ method_getTypeEncoding(originalMethod));
62
+ } else {
63
+ method_exchangeImplementations(originalMethod, swizzledMethod);
64
+ }
65
+ });
66
+ }
67
+
68
+ #pragma mark - Method Swizzling
69
+
70
+ - (id)xxx_init {
71
+ id a = [self xxx_init];
72
+ NSArray *stack = [NSThread callStackSymbols];
73
+ for(NSString *trace in stack) {
74
+ if([trace containsString:@"WebKit"]) {
75
+ [a setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
76
+ break;
77
+ }
78
+ }
79
+ return a;
80
+ }
81
+
82
+ @end
83
+
84
+
85
+ @interface CDVWKWeakScriptMessageHandler : NSObject <WKScriptMessageHandler>
86
+
87
+ @property (nonatomic, weak, readonly) id<WKScriptMessageHandler>scriptMessageHandler;
88
+
89
+ - (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler;
90
+
91
+ @end
92
+
93
+
94
+ @interface CDVWKWebViewEngine ()
95
+
96
+ @property (nonatomic, strong, readwrite) UIView* engineWebView;
97
+ @property (nonatomic, strong, readwrite) id <WKUIDelegate> uiDelegate;
98
+ @property (nonatomic, weak) id <WKScriptMessageHandler> weakScriptMessageHandler;
99
+ @property (nonatomic, readwrite) CGRect frame;
100
+ @property (nonatomic, strong) NSString *userAgentCreds;
101
+ @property (nonatomic, strong) IONAssetHandler * handler;
102
+
103
+ @property (nonatomic, readwrite) NSString *CDV_LOCAL_SERVER;
104
+ @end
105
+
106
+ // expose private configuration value required for background operation
107
+ @interface WKWebViewConfiguration ()
108
+
109
+ @end
110
+
111
+
112
+ // see forwardingTargetForSelector: selector comment for the reason for this pragma
113
+ #pragma clang diagnostic ignored "-Wprotocol"
114
+
115
+ @implementation CDVWKWebViewEngine
116
+
117
+ @synthesize engineWebView = _engineWebView;
118
+
119
+ - (instancetype)initWithFrame:(CGRect)frame
120
+ {
121
+ self = [super init];
122
+ if (self) {
123
+ if (NSClassFromString(@"WKWebView") == nil) {
124
+ return nil;
125
+ }
126
+ // add to keyWindow to ensure it is 'active'
127
+ [UIApplication.sharedApplication.keyWindow addSubview:self.engineWebView];
128
+
129
+ self.frame = frame;
130
+ }
131
+ return self;
132
+ }
133
+
134
+ -(NSString *) getStartPath {
135
+ NSString * wwwPath = [[NSBundle mainBundle] pathForResource:@"www" ofType: nil];
136
+
137
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
138
+ NSString * persistedPath = [userDefaults objectForKey:CDV_SERVER_PATH];
139
+ if (![self isDeployDisabled] && ![self isNewBinary] && persistedPath && ![persistedPath isEqualToString:@""]) {
140
+ NSFileManager *fileManager = [NSFileManager defaultManager];
141
+
142
+ if ([persistedPath hasPrefix:@"/"] && [fileManager fileExistsAtPath:persistedPath]) {
143
+ wwwPath = persistedPath;
144
+ } else {
145
+ NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
146
+ NSString * cordovaDataDirectory = [libPath stringByAppendingPathComponent:@"NoCloud"];
147
+ NSString * contentSyncPath = [cordovaDataDirectory stringByAppendingPathComponent:persistedPath];
148
+ NSString * snapshots = [cordovaDataDirectory stringByAppendingPathComponent:@"ionic_built_snapshots"];
149
+ NSString * snapshotPath = [snapshots stringByAppendingPathComponent:[persistedPath lastPathComponent]];
150
+
151
+ if ([fileManager fileExistsAtPath:contentSyncPath]) {
152
+ wwwPath = contentSyncPath;
153
+ } else {
154
+ wwwPath = snapshotPath;
155
+ }
156
+ }
157
+ }
158
+ self.basePath = wwwPath;
159
+ return wwwPath;
160
+ }
161
+
162
+ -(BOOL) isNewBinary
163
+ {
164
+ NSString * versionCode = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
165
+ NSString * versionName = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
166
+ NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
167
+ NSString * lastVersionCode = [prefs stringForKey:LAST_BINARY_VERSION_CODE];
168
+ NSString * lastVersionName = [prefs stringForKey:LAST_BINARY_VERSION_NAME];
169
+ if (![versionCode isEqualToString:lastVersionCode] || ![versionName isEqualToString:lastVersionName]) {
170
+ [prefs setObject:versionCode forKey:LAST_BINARY_VERSION_CODE];
171
+ [prefs setObject:versionName forKey:LAST_BINARY_VERSION_NAME];
172
+ [prefs setObject:@"" forKey:CDV_SERVER_PATH];
173
+ [prefs synchronize];
174
+ return YES;
175
+ }
176
+ return NO;
177
+ }
178
+
179
+ -(BOOL) isDeployDisabled {
180
+ return [[self.commandDelegate.settings objectForKey:[@"DisableDeploy" lowercaseString]] boolValue];
181
+ }
182
+
183
+ - (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings
184
+ {
185
+ WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
186
+ configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
187
+ configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
188
+
189
+ if (settings == nil) {
190
+ return configuration;
191
+ }
192
+
193
+ if(![settings cordovaBoolSettingForKey:@"WKSuspendInBackground" defaultValue:YES]){
194
+ NSString* _BGStatus;
195
+ if (@available(iOS 12.2, *)) {
196
+ // do stuff for iOS 12.2 and newer
197
+ NSLog(@"iOS 12.2+ detected");
198
+ NSString* str = @"YWx3YXlzUnVuc0F0Rm9yZWdyb3VuZFByaW9yaXR5";
199
+ NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0];
200
+ _BGStatus = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
201
+ } else {
202
+ // do stuff for iOS 12.1 and older
203
+ NSLog(@"iOS Below 12.2 detected");
204
+ NSString* str = @"X2Fsd2F5c1J1bnNBdEZvcmVncm91bmRQcmlvcml0eQ==";
205
+ NSData* data = [[NSData alloc] initWithBase64EncodedString:str options:0];
206
+ _BGStatus = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
207
+ }
208
+ [configuration setValue:[NSNumber numberWithBool:YES]
209
+ forKey:_BGStatus];
210
+ }
211
+ NSString *userAgent = configuration.applicationNameForUserAgent;
212
+ if (
213
+ [settings cordovaSettingForKey:@"OverrideUserAgent"] == nil &&
214
+ [settings cordovaSettingForKey:@"AppendUserAgent"] != nil
215
+ ) {
216
+ userAgent = [NSString stringWithFormat:@"%@ %@", userAgent, [settings cordovaSettingForKey:@"AppendUserAgent"]];
217
+ }
218
+ configuration.applicationNameForUserAgent = userAgent;
219
+ configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:YES];
220
+ configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO];
221
+ configuration.allowsAirPlayForMediaPlayback = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES];
222
+ return configuration;
223
+ }
224
+
225
+ - (void)pluginInitialize
226
+ {
227
+ // viewController would be available now. we attempt to set all possible delegates to it, by default
228
+ NSDictionary* settings = self.commandDelegate.settings;
229
+ NSString *bind = [settings cordovaSettingForKey:@"Hostname"];
230
+ if(bind == nil){
231
+ bind = @"localhost";
232
+ }
233
+ NSString *scheme = [settings cordovaSettingForKey:@"iosScheme"];
234
+ if(scheme == nil || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"file"]){
235
+ scheme = @"ionic";
236
+ }
237
+ self.CDV_LOCAL_SERVER = [NSString stringWithFormat:@"%@://%@", scheme, bind];
238
+
239
+ self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
240
+
241
+ CDVWKWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWKWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
242
+
243
+ WKUserContentController* userContentController = [[WKUserContentController alloc] init];
244
+ [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME];
245
+ [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_IONIC_STOP_SCROLL];
246
+
247
+ // Inject XHR Polyfill
248
+ NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
249
+ WKUserScript *wkScript = [self wkPluginScript];
250
+ if (wkScript) {
251
+ [userContentController addUserScript:wkScript];
252
+ }
253
+
254
+ WKUserScript *configScript = [self configScript];
255
+ if (configScript) {
256
+ [userContentController addUserScript:configScript];
257
+ }
258
+
259
+ BOOL autoCordova = [settings cordovaBoolSettingForKey:@"AutoInjectCordova" defaultValue:NO];
260
+ if (autoCordova){
261
+ NSLog(@"CDVWKWebViewEngine: trying to inject XHR polyfill");
262
+ WKUserScript *cordova = [self autoCordovify];
263
+ if (cordova) {
264
+ [userContentController addUserScript:cordova];
265
+ }
266
+ }
267
+
268
+ BOOL audioCanMix = [settings cordovaBoolSettingForKey:@"AudioCanMix" defaultValue:NO];
269
+ if (audioCanMix) {
270
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
271
+ withOptions:AVAudioSessionCategoryOptionMixWithOthers
272
+ error:nil];
273
+ }
274
+
275
+ WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
276
+ configuration.userContentController = userContentController;
277
+
278
+ self.handler = [[IONAssetHandler alloc] initWithBasePath:[self getStartPath] andScheme:scheme];
279
+ [configuration setURLSchemeHandler:self.handler forURLScheme:scheme];
280
+
281
+ // re-create WKWebView, since we need to update configuration
282
+ // remove from keyWindow before recreating
283
+ [self.engineWebView removeFromSuperview];
284
+ WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.frame configuration:configuration];
285
+
286
+ [wkWebView.scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
287
+
288
+ wkWebView.UIDelegate = self.uiDelegate;
289
+ self.engineWebView = wkWebView;
290
+ // add to keyWindow to ensure it is 'active'
291
+ [UIApplication.sharedApplication.keyWindow addSubview:self.engineWebView];
292
+
293
+ NSString * overrideUserAgent = [settings cordovaSettingForKey:@"OverrideUserAgent"];
294
+ if (overrideUserAgent != nil) {
295
+ wkWebView.customUserAgent = overrideUserAgent;
296
+ }
297
+
298
+ if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) {
299
+ wkWebView.UIDelegate = (id <WKUIDelegate>)self.viewController;
300
+ }
301
+
302
+ if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) {
303
+ wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self.viewController;
304
+ } else {
305
+ wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self;
306
+ }
307
+
308
+ if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
309
+ [wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:CDV_BRIDGE_NAME];
310
+ }
311
+
312
+ [self keyboardDisplayDoesNotRequireUserAction];
313
+
314
+ if ([settings cordovaBoolSettingForKey:@"KeyboardAppearanceDark" defaultValue:NO]) {
315
+ [self setKeyboardAppearanceDark];
316
+ }
317
+
318
+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400
319
+ // With the introduction of iOS 16.4 the webview is no longer inspectable by default.
320
+ // We'll honor that change for release builds, but will still allow inspection on debug builds by default.
321
+ // We also introduce an override option, so consumers can influence this decision in their own build.
322
+ if (@available(iOS 16.4, *)) {
323
+ #ifdef DEBUG
324
+ BOOL allowWebviewInspectionDefault = YES;
325
+ #else
326
+ BOOL allowWebviewInspectionDefault = NO;
327
+ #endif
328
+ wkWebView.inspectable = [settings cordovaBoolSettingForKey:@"InspectableWebview" defaultValue:allowWebviewInspectionDefault];
329
+ }
330
+ #endif
331
+ [self updateSettings:settings];
332
+
333
+ // check if content thread has died on resume
334
+ NSLog(@"%@", @"CDVWKWebViewEngine will reload WKWebView if required on resume");
335
+ [[NSNotificationCenter defaultCenter]
336
+ addObserver:self
337
+ selector:@selector(onAppWillEnterForeground:)
338
+ name:UIApplicationWillEnterForegroundNotification object:nil];
339
+
340
+ // If less than ios 13.4
341
+ if (@available(iOS 13.4, *)) {} else {
342
+ // For keyboard dismissal leaving viewport shifted (can potentially be removed when apple releases the fix for the issue discussed here: https://github.com/apache/cordova-ios/issues/417#issuecomment-423340885)
343
+ // Apple has released a fix in 13.4, but not in 12.x (as of 12.4.6)
344
+ [[NSNotificationCenter defaultCenter]
345
+ addObserver:self
346
+ selector:@selector(keyboardWillHide)
347
+ name:UIKeyboardWillHideNotification object:nil];
348
+ }
349
+
350
+ NSLog(@"Using Ionic WKWebView");
351
+
352
+ }
353
+
354
+ // https://github.com/Telerik-Verified-Plugins/WKWebView/commit/04e8296adeb61f289f9c698045c19b62d080c7e3#L609-L620
355
+ - (void) keyboardDisplayDoesNotRequireUserAction {
356
+ Class class = NSClassFromString(@"WKContentView");
357
+ NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
358
+ NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
359
+ NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
360
+ char * methodSignature = "_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
361
+
362
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
363
+ methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:";
364
+ } else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
365
+ methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
366
+ }
367
+
368
+ if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
369
+ SEL selector = sel_getUid(methodSignature);
370
+ Method method = class_getInstanceMethod(class, selector);
371
+ IMP original = method_getImplementation(method);
372
+ IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
373
+ ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
374
+ });
375
+ method_setImplementation(method, override);
376
+ } else {
377
+ SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
378
+ Method method = class_getInstanceMethod(class, selector);
379
+ IMP original = method_getImplementation(method);
380
+ IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
381
+ ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
382
+ });
383
+ method_setImplementation(method, override);
384
+ }
385
+ }
386
+
387
+ - (void)setKeyboardAppearanceDark
388
+ {
389
+ IMP darkImp = imp_implementationWithBlock(^(id _s) {
390
+ return UIKeyboardAppearanceDark;
391
+ });
392
+ for (NSString* classString in @[@"WKContentView", @"UITextInputTraits"]) {
393
+ Class c = NSClassFromString(classString);
394
+ Method m = class_getInstanceMethod(c, @selector(keyboardAppearance));
395
+ if (m != NULL) {
396
+ method_setImplementation(m, darkImp);
397
+ } else {
398
+ class_addMethod(c, @selector(keyboardAppearance), darkImp, "l@:");
399
+ }
400
+ }
401
+ }
402
+
403
+
404
+
405
+ - (void)onAppWillEnterForeground:(NSNotification *)notification {
406
+ if ([self shouldReloadWebView]) {
407
+ NSLog(@"%@", @"CDVWKWebViewEngine reloading!");
408
+ [(WKWebView*)_engineWebView reload];
409
+ }
410
+ }
411
+
412
+
413
+ -(void)keyboardWillHide
414
+ {
415
+ // For keyboard dismissal leaving viewport shifted (can potentially be removed when apple releases the fix for the issue discussed here: https://github.com/apache/cordova-ios/issues/417#issuecomment-423340885)
416
+ UIScrollView * scrollView = self.webView.scrollView;
417
+ // Calculate some vars for convenience
418
+ CGFloat contentLengthWithInsets = scrollView.contentSize.height + scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom;
419
+ CGFloat contentOffsetY = scrollView.contentOffset.y;
420
+ CGFloat screenHeight = scrollView.frame.size.height;
421
+ CGFloat maxAllowedOffsetY = fmax(contentLengthWithInsets - screenHeight, 0); // 0 is for the case where content is shorter than screen
422
+
423
+ // If the keyboard allowed the user to get to an offset beyond the max
424
+ if (contentOffsetY > maxAllowedOffsetY) {
425
+ // Reset the scroll to the max allowed so that there is no additional empty white space at the bottom where the keyboard occupied!
426
+ CGPoint bottomOfPage = CGPointMake(scrollView.contentOffset.x, maxAllowedOffsetY);
427
+ [scrollView setContentOffset:bottomOfPage];
428
+ }
429
+ }
430
+
431
+ - (BOOL)shouldReloadWebView
432
+ {
433
+ WKWebView* wkWebView = (WKWebView*)_engineWebView;
434
+ return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
435
+ }
436
+
437
+ - (BOOL)shouldReloadWebView:(NSURL *)location title:(NSString*)title
438
+ {
439
+ BOOL title_is_nil = (title == nil);
440
+ BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"];
441
+
442
+ BOOL reload = (title_is_nil || location_is_blank);
443
+
444
+ #ifdef DEBUG
445
+ NSLog(@"%@", @"CDVWKWebViewEngine shouldReloadWebView::");
446
+ NSLog(@"CDVWKWebViewEngine shouldReloadWebView title: %@", title);
447
+ NSLog(@"CDVWKWebViewEngine shouldReloadWebView location: %@", [location absoluteString]);
448
+ NSLog(@"CDVWKWebViewEngine shouldReloadWebView reload: %u", reload);
449
+ #endif
450
+
451
+ return reload;
452
+ }
453
+
454
+
455
+ - (id)loadRequest:(NSURLRequest *)request
456
+ {
457
+ if (request.URL.fileURL) {
458
+ NSURL* startURL = [NSURL URLWithString:((CDVViewController *)self.viewController).startPage];
459
+ NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]];
460
+ NSURL *url = [[NSURL URLWithString:self.CDV_LOCAL_SERVER] URLByAppendingPathComponent:request.URL.path];
461
+ if ([request.URL.path isEqualToString:startFilePath]) {
462
+ url = [NSURL URLWithString:self.CDV_LOCAL_SERVER];
463
+ }
464
+ if(request.URL.query) {
465
+ url = [NSURL URLWithString:[@"?" stringByAppendingString:request.URL.query] relativeToURL:url];
466
+ }
467
+ if(request.URL.fragment) {
468
+ url = [NSURL URLWithString:[@"#" stringByAppendingString:request.URL.fragment] relativeToURL:url];
469
+ }
470
+ request = [NSURLRequest requestWithURL:url];
471
+ }
472
+ return [(WKWebView*)_engineWebView loadRequest:request];
473
+ }
474
+
475
+ - (id)loadHTMLString:(NSString *)string baseURL:(NSURL*)baseURL
476
+ {
477
+ return [(WKWebView*)_engineWebView loadHTMLString:string baseURL:baseURL];
478
+ }
479
+
480
+ - (NSURL*) URL
481
+ {
482
+ return [(WKWebView*)_engineWebView URL];
483
+ }
484
+
485
+ - (BOOL)canLoadRequest:(NSURLRequest *)request
486
+ {
487
+ return TRUE;
488
+ }
489
+
490
+ - (void)updateSettings:(NSDictionary *)settings
491
+ {
492
+ WKWebView* wkWebView = (WKWebView *)_engineWebView;
493
+
494
+ // By default, DisallowOverscroll is false (thus bounce is allowed)
495
+ BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]);
496
+
497
+ // prevent webView from bouncing
498
+ if (!bounceAllowed) {
499
+ if ([wkWebView respondsToSelector:@selector(scrollView)]) {
500
+ ((UIScrollView*)[wkWebView scrollView]).bounces = NO;
501
+ } else {
502
+ for (id subview in wkWebView.subviews) {
503
+ if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
504
+ ((UIScrollView*)subview).bounces = NO;
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0];
511
+ wkWebView.allowsLinkPreview = [settings cordovaBoolSettingForKey:@"AllowLinkPreview" defaultValue:NO];
512
+ wkWebView.scrollView.scrollEnabled = [settings cordovaBoolSettingForKey:@"ScrollEnabled" defaultValue:NO];
513
+ wkWebView.allowsBackForwardNavigationGestures = [settings cordovaBoolSettingForKey:@"AllowBackForwardNavigationGestures" defaultValue:NO];
514
+ }
515
+
516
+ - (void)updateWithInfo:(NSDictionary *)info
517
+ {
518
+ NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers];
519
+ NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences];
520
+ id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate];
521
+ id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate];
522
+
523
+ WKWebView* wkWebView = (WKWebView*)_engineWebView;
524
+
525
+ if (scriptMessageHandlers && [scriptMessageHandlers isKindOfClass:[NSDictionary class]]) {
526
+ NSArray* allKeys = [scriptMessageHandlers allKeys];
527
+
528
+ for (NSString* key in allKeys) {
529
+ id object = [scriptMessageHandlers objectForKey:key];
530
+ if ([object conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
531
+ [wkWebView.configuration.userContentController addScriptMessageHandler:object name:key];
532
+ }
533
+ }
534
+ }
535
+
536
+ if (navigationDelegate && [navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]) {
537
+ wkWebView.navigationDelegate = navigationDelegate;
538
+ }
539
+
540
+ if (uiDelegate && [uiDelegate conformsToProtocol:@protocol(WKUIDelegate)]) {
541
+ wkWebView.UIDelegate = uiDelegate;
542
+ }
543
+
544
+ if (settings && [settings isKindOfClass:[NSDictionary class]]) {
545
+ [self updateSettings:settings];
546
+ }
547
+ }
548
+
549
+ // This forwards the methods that are in the header that are not implemented here.
550
+ // loadHTMLString:baseURL:
551
+ // loadRequest:
552
+ - (id)forwardingTargetForSelector:(SEL)aSelector
553
+ {
554
+ return _engineWebView;
555
+ }
556
+
557
+ - (UIView *)webView
558
+ {
559
+ return self.engineWebView;
560
+ }
561
+
562
+ - (WKUserScript *)wkPluginScript
563
+ {
564
+ NSString *scriptFile = [[NSBundle mainBundle] pathForResource:@"www/wk-plugin" ofType:@"js"];
565
+ if (scriptFile == nil) {
566
+ NSLog(@"CDVWKWebViewEngine: WK plugin was not found");
567
+ return nil;
568
+ }
569
+ NSError *error = nil;
570
+ NSString *source = [NSString stringWithContentsOfFile:scriptFile encoding:NSUTF8StringEncoding error:&error];
571
+ if (source == nil || error != nil) {
572
+ NSLog(@"CDVWKWebViewEngine: WK plugin can not be loaded: %@", error);
573
+ return nil;
574
+ }
575
+ source = [source stringByAppendingString:[NSString stringWithFormat:@"window.WEBVIEW_SERVER_URL = '%@';", self.CDV_LOCAL_SERVER]];
576
+
577
+ return [[WKUserScript alloc] initWithSource:source
578
+ injectionTime:WKUserScriptInjectionTimeAtDocumentStart
579
+ forMainFrameOnly:YES];
580
+ }
581
+
582
+ - (WKUserScript *)configScript
583
+ {
584
+ Class keyboard = NSClassFromString(@"CDVIonicKeyboard");
585
+ BOOL keyboardPlugin = keyboard != nil;
586
+ if(!keyboardPlugin) {
587
+ return nil;
588
+ }
589
+
590
+ BOOL keyboardResizes = [self.commandDelegate.settings cordovaBoolSettingForKey:@"KeyboardResize" defaultValue:YES];
591
+ NSString *source = [NSString stringWithFormat:
592
+ @"window.Ionic = window.Ionic || {};"
593
+ @"window.Ionic.keyboardPlugin=true;"
594
+ @"window.Ionic.keyboardResizes=%@",
595
+ keyboardResizes ? @"true" : @"false"];
596
+
597
+ return [[WKUserScript alloc] initWithSource:source
598
+ injectionTime:WKUserScriptInjectionTimeAtDocumentStart
599
+ forMainFrameOnly:YES];
600
+ }
601
+
602
+ - (WKUserScript *)autoCordovify
603
+ {
604
+ NSURL *cordovaURL = [[NSBundle mainBundle] URLForResource:@"www/cordova" withExtension:@"js"];
605
+ if (cordovaURL == nil) {
606
+ NSLog(@"CDVWKWebViewEngine: cordova.js WAS NOT FOUND");
607
+ return nil;
608
+ }
609
+ NSError *error = nil;
610
+ NSString *source = [NSString stringWithContentsOfURL:cordovaURL encoding:NSUTF8StringEncoding error:&error];
611
+ if (source == nil || error != nil) {
612
+ NSLog(@"CDVWKWebViewEngine: cordova.js can not be loaded: %@", error);
613
+ return nil;
614
+ }
615
+ NSLog(@"CDVWKWebViewEngine: auto injecting cordova");
616
+ NSString *cordovaPath = [self.CDV_LOCAL_SERVER stringByAppendingString:cordovaURL.URLByDeletingLastPathComponent.path];
617
+ NSString *replacement = [NSString stringWithFormat:@"var pathPrefix = '%@/';", cordovaPath];
618
+ source = [source stringByReplacingOccurrencesOfString:@"var pathPrefix = findCordovaPath();" withString:replacement];
619
+
620
+ return [[WKUserScript alloc] initWithSource:source
621
+ injectionTime:WKUserScriptInjectionTimeAtDocumentStart
622
+ forMainFrameOnly:YES];
623
+ }
624
+
625
+ #pragma mark WKScriptMessageHandler implementation
626
+
627
+ - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
628
+ {
629
+ if ([message.name isEqualToString:CDV_BRIDGE_NAME]) {
630
+ [self handleCordovaMessage: message];
631
+ } else if ([message.name isEqualToString:CDV_IONIC_STOP_SCROLL]) {
632
+ [self handleStopScroll];
633
+ }
634
+ }
635
+
636
+ - (void)handleCordovaMessage:(WKScriptMessage*)message
637
+ {
638
+ CDVViewController *vc = (CDVViewController*)self.viewController;
639
+
640
+ NSArray *jsonEntry = message.body; // NSString:callbackId, NSString:service, NSString:action, NSArray:args
641
+ CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
642
+ CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName);
643
+
644
+ if (![vc.commandQueue execute:command]) {
645
+ #ifdef DEBUG
646
+ NSError* error = nil;
647
+ NSString* commandJson = nil;
648
+ NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry
649
+ options:0
650
+ error:&error];
651
+
652
+ if (error == nil) {
653
+ commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
654
+ }
655
+
656
+ static NSUInteger maxLogLength = 1024;
657
+ NSString* commandString = ([commandJson length] > maxLogLength) ?
658
+ [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
659
+ commandJson;
660
+
661
+ NSLog(@"FAILED pluginJSON = %@", commandString);
662
+ #endif
663
+ }
664
+ }
665
+
666
+ - (void)handleStopScroll
667
+ {
668
+ WKWebView* wkWebView = (WKWebView*)_engineWebView;
669
+ NSLog(@"CDVWKWebViewEngine: handleStopScroll");
670
+ [self recursiveStopScroll:[wkWebView scrollView]];
671
+ [wkWebView evaluateJavaScript:@"window.IonicStopScroll.fire()" completionHandler:nil];
672
+ }
673
+
674
+ - (void)recursiveStopScroll:(UIView *)node
675
+ {
676
+ if([node isKindOfClass: [UIScrollView class]]) {
677
+ UIScrollView *nodeAsScroll = (UIScrollView *)node;
678
+
679
+ if([nodeAsScroll isScrollEnabled] && ![nodeAsScroll isHidden]) {
680
+ [nodeAsScroll setScrollEnabled: NO];
681
+ [nodeAsScroll setScrollEnabled: YES];
682
+ }
683
+ }
684
+
685
+ // iterate tree recursivelly
686
+ for (UIView *child in [node subviews]) {
687
+ [self recursiveStopScroll:child];
688
+ }
689
+ }
690
+
691
+
692
+ #pragma mark WKNavigationDelegate implementation
693
+
694
+ - (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation
695
+ {
696
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:webView]];
697
+ }
698
+
699
+ - (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
700
+ {
701
+ #ifndef __CORDOVA_6_0_0
702
+ CDVViewController* vc = (CDVViewController*)self.viewController;
703
+ [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
704
+ #endif
705
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]];
706
+ }
707
+
708
+ - (void)webView:(WKWebView*)theWebView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error
709
+ {
710
+ [self webView:theWebView didFailNavigation:navigation withError:error];
711
+ }
712
+
713
+ - (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error
714
+ {
715
+ CDVViewController* vc = (CDVViewController*)self.viewController;
716
+ #ifndef __CORDOVA_6_0_0
717
+ [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
718
+ #endif
719
+
720
+ NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
721
+ NSLog(@"%@", message);
722
+
723
+ NSURL* errorUrl = vc.errorURL;
724
+ if (errorUrl) {
725
+ NSCharacterSet *charSet = [NSCharacterSet URLFragmentAllowedCharacterSet];
726
+ errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEncodingWithAllowedCharacters:charSet]] relativeToURL:errorUrl];
727
+ NSLog(@"%@", [errorUrl absoluteString]);
728
+ [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
729
+ }
730
+ #ifdef DEBUG
731
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:message preferredStyle:UIAlertControllerStyleAlert];
732
+ [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:nil]];
733
+ [vc presentViewController:alertController animated:YES completion:nil];
734
+ #endif
735
+ }
736
+
737
+ - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
738
+ {
739
+ [webView reload];
740
+ }
741
+
742
+ - (BOOL)defaultResourcePolicyForURL:(NSURL*)url
743
+ {
744
+ // all file:// urls are allowed
745
+ if ([url isFileURL]) {
746
+ return YES;
747
+ }
748
+
749
+ return NO;
750
+ }
751
+
752
+ - (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
753
+ {
754
+ NSURL *url = navigationAction.request.URL;
755
+ CDVViewController *viewController = (CDVViewController *)self.viewController;
756
+
757
+ NSURLRequest *request = navigationAction.request;
758
+ CDVWebViewNavigationType navigationType = (CDVWebViewNavigationType)navigationAction.navigationType;
759
+
760
+ // https://issues.apache.org/jira/browse/CB-12497
761
+ if (navigationAction.navigationType == WKNavigationTypeOther) {
762
+ #ifdef __CORDOVA_6_0_0
763
+ navigationType = -1;
764
+ #else
765
+ navigationType = 5;
766
+ #endif
767
+ }
768
+
769
+ NSMutableDictionary *info = [NSMutableDictionary dictionary];
770
+ info[@"sourceFrame"] = navigationAction.sourceFrame;
771
+ info[@"targetFrame"] = navigationAction.targetFrame;
772
+
773
+ /*
774
+ * Permet aux plugins Cordova d'intercepter et gérer la navigation.
775
+ */
776
+ BOOL pluginHasHandledRequest = NO;
777
+ BOOL shouldAllowRequest = NO;
778
+
779
+ for (NSString *pluginName in viewController.pluginObjects) {
780
+ CDVPlugin *plugin = viewController.pluginObjects[pluginName];
781
+ BOOL respondsToModernSelector = [plugin respondsToSelector: @selector(shouldOverrideLoadWithRequest:navigationType:info:)];
782
+ BOOL respondsToLegacySelector = [plugin respondsToSelector: @selector(shouldOverrideLoadWithRequest:navigationType:)];
783
+
784
+ if (respondsToModernSelector || respondsToLegacySelector) {
785
+ pluginHasHandledRequest = YES;
786
+ id<CDVPluginNavigationHandler> navigationPlugin = (id<CDVPluginNavigationHandler>)plugin;
787
+
788
+ if (respondsToModernSelector) {
789
+ shouldAllowRequest = [navigationPlugin shouldOverrideLoadWithRequest:request navigationType:navigationType info:info];
790
+ } else {
791
+ #pragma clang diagnostic push
792
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
793
+ shouldAllowRequest = [navigationPlugin shouldOverrideLoadWithRequest:request navigationType:navigationType];
794
+ #pragma clang diagnostic pop
795
+ }
796
+
797
+ // Un plugin a refusé la requête
798
+ if (!shouldAllowRequest) {
799
+ break;
800
+ }
801
+ }
802
+ }
803
+
804
+ /*
805
+ * Aucun plugin n’a répondu :
806
+ * application de la politique par défaut Cordova.
807
+ */
808
+ if (!pluginHasHandledRequest) {
809
+ shouldAllowRequest = [self defaultResourcePolicyForURL:url];
810
+ if (!shouldAllowRequest) {
811
+ [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName: CDVPluginHandleOpenURLNotification object:url]];
812
+ }
813
+ }
814
+
815
+ /*
816
+ * Gestion finale de la navigation.
817
+ */
818
+ if (shouldAllowRequest) {
819
+ NSString *scheme = url.scheme.lowercaseString;
820
+
821
+ BOOL isExternalScheme = [scheme isEqualToString:@"tel"] ||
822
+ [scheme isEqualToString:@"mailto"] ||
823
+ [scheme isEqualToString:@"facetime"] ||
824
+ [scheme isEqualToString:@"sms"] ||
825
+ [scheme isEqualToString:@"maps"];
826
+
827
+ if (isExternalScheme) {
828
+ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
829
+ decisionHandler(WKNavigationActionPolicyCancel);
830
+ } else {
831
+ decisionHandler(WKNavigationActionPolicyAllow);
832
+ }
833
+ } else {
834
+ decisionHandler(WKNavigationActionPolicyCancel);
835
+ }
836
+ }
837
+
838
+ -(void)getServerBasePath:(CDVInvokedUrlCommand*)command
839
+ {
840
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:self.basePath] callbackId:command.callbackId];
841
+ }
842
+
843
+ -(void)setServerBasePath:(CDVInvokedUrlCommand*)command
844
+ {
845
+ NSString * path = [command argumentAtIndex:0];
846
+ self.basePath = path;
847
+ [self.handler setAssetPath:path];
848
+ [self saveServerBasePath:path];
849
+
850
+ NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.CDV_LOCAL_SERVER]];
851
+ [(WKWebView*)_engineWebView loadRequest:request];
852
+ }
853
+
854
+ -(void)saveServerBasePath:(NSString*)path
855
+ {
856
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
857
+ [userDefaults setObject:path forKey:CDV_SERVER_PATH];
858
+ [userDefaults synchronize];
859
+ }
860
+
861
+ -(void)persistServerBasePath:(CDVInvokedUrlCommand*)command
862
+ {
863
+ [self saveServerBasePath:self.basePath];
864
+ }
865
+
866
+ @end
867
+
868
+ #pragma mark - CDVWKWeakScriptMessageHandler
869
+
870
+ @implementation CDVWKWeakScriptMessageHandler
871
+
872
+ - (instancetype)initWithScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler
873
+ {
874
+ self = [super init];
875
+ if (self) {
876
+ _scriptMessageHandler = scriptMessageHandler;
877
+ }
878
+ return self;
879
+ }
880
+
881
+ - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
882
+ {
883
+ [self.scriptMessageHandler userContentController:userContentController didReceiveScriptMessage:message];
884
+ }
885
+
886
+ @end