Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,6 @@ - (void)pluginInitialize

[self updateSettings:settings];

// check if content thread has died on resume
NSLog(@"%@", @"CDVWebViewEngine will reload WKWebView if required on resume");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onAppWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification object:nil];

NSLog(@"Using WKWebView");
}

Expand All @@ -285,37 +278,6 @@ - (void)dispose
[super dispose];
}

- (void) onAppWillEnterForeground:(NSNotification*)notification {
if ([self shouldReloadWebView]) {
NSLog(@"%@", @"CDVWebViewEngine reloading!");
[(WKWebView*)_engineWebView reload];
}
}

- (BOOL)shouldReloadWebView
{
WKWebView* wkWebView = (WKWebView*)_engineWebView;
return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title];
}

- (BOOL)shouldReloadWebView:(NSURL*)location title:(NSString*)title
{
BOOL title_is_nil = (title == nil);
BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"];

BOOL reload = (title_is_nil || location_is_blank);

#ifdef DEBUG
NSLog(@"%@", @"CDVWebViewEngine shouldReloadWebView::");
NSLog(@"CDVWebViewEngine shouldReloadWebView title: %@", title);
NSLog(@"CDVWebViewEngine shouldReloadWebView location: %@", [location absoluteString]);
NSLog(@"CDVWebViewEngine shouldReloadWebView reload: %u", reload);
#endif

return reload;
}


- (id)loadRequest:(NSURLRequest*)request
{
if ([self canLoadRequest:request]) { // can load, differentiate between file urls and other schemes
Expand Down Expand Up @@ -566,7 +528,14 @@ - (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigati

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
[webView reload];
CDVSettingsDictionary *settings = self.commandDelegate.settings;
NSString *recoveryBehavior = [settings cordovaSettingForKey:@"CrashRecoveryBehavior"];

if ([recoveryBehavior isEqualToString:@"reload"]) {
[self.viewController loadStartPage];
} else {
[webView reload];
}
}

- (BOOL)defaultResourcePolicyForURL:(NSURL*)url
Expand Down
48 changes: 25 additions & 23 deletions CordovaLib/Classes/Public/CDVViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -382,27 +382,7 @@ - (void)viewDidLoad
[CDVTimer stop:@"TotalPluginStartup"];
}

// /////////////////
NSURL* appURL = [self appUrl];

if (appURL) {
NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[_webViewEngine loadRequest:appReq];
} else {
NSString* loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.webContentFolderName, self.startPage];
NSLog(@"%@", loadErr);

NSURL* errorUrl = [self errorURL];
if (errorUrl) {
errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]] relativeToURL:errorUrl];
NSLog(@"%@", [errorUrl absoluteString]);
[_webViewEngine loadRequest:[NSURLRequest requestWithURL:errorUrl]];
} else {
NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
[_webViewEngine loadHTMLString:html baseURL:nil];
}
}
// /////////////////
[self loadStartPage];

[self.webView setBackgroundColor:self.backgroundColor];
[self.launchView setBackgroundColor:self.splashBackgroundColor];
Expand Down Expand Up @@ -765,6 +745,29 @@ - (void)createStatusBarView
self.statusBar.hidden = YES;
}

- (void)loadStartPage
{
NSURL *appURL = [self appUrl];

if (appURL) {
NSURLRequest *appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[_webViewEngine loadRequest:appReq];
} else {
NSString *loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.webContentFolderName, self.startPage];
NSLog(@"%@", loadErr);

NSURL *errorUrl = [self errorURL];
if (errorUrl) {
errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]] relativeToURL:errorUrl];
NSLog(@"%@", [errorUrl absoluteString]);
[_webViewEngine loadRequest:[NSURLRequest requestWithURL:errorUrl]];
} else {
NSString *html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
[_webViewEngine loadHTMLString:html baseURL:nil];
}
}
}

#pragma mark CordovaCommands

- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className
Expand Down Expand Up @@ -846,8 +849,7 @@ - (bool)checkAndReinitViewUrl
{
NSURL* appURL = [self appUrl];
if ([self isUrlEmpty: [_webViewEngine URL]] && ![self isUrlEmpty: appURL]) {
NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[_webViewEngine loadRequest:appReq];
[self loadStartPage];
return true;
}
return false;
Expand Down
2 changes: 2 additions & 0 deletions CordovaLib/CordovaLib.docc/upgrading-8.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,5 @@ The following headers are deprecated due to adding global category extensions to

* The ``CDVViewController/showLaunchScreen:`` method is deprecated.
This method has been renamed to ``CDVViewController/showSplashScreen:``.

* Added a new ``CDVViewController/loadStartPage`` method to load the initial starting page in the web view, replacing any existing content.
9 changes: 9 additions & 0 deletions CordovaLib/include/Cordova/CDVViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (UIView*)newCordovaViewWithFrame:(CGRect)bounds;

/**
Loads the starting page in the web view, replacing any existing content.

@Metadata {
@Available(Cordova, introduced: "8.0.0")
}
*/
- (void)loadStartPage;

/**
Returns the ``CDVPlugin`` instance of the given plugin name, creating the
instance if one does not exist.
Expand Down
139 changes: 106 additions & 33 deletions tests/CordovaLibTests/CDVWebViewEngineTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,54 @@ Licensed to the Apache Software Foundation (ASF) under one
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "CDVWebViewEngine.h"
#import "CDVWebViewProcessPoolFactory.h"
#import <Cordova/CDVWebViewProcessPoolFactory.h>
#import <Cordova/CDVSettingsDictionary.h>
#import <Cordova/CDVAvailability.h>

#import "CordovaApp-Swift.h"

@interface CDVWebViewEngineTest : XCTestCase

@property AppDelegate* appDelegate;
@property (nonatomic, strong) CDVWebViewEngine* plugin;
@property (nonatomic, strong) CDVViewController* viewController;

@end

@interface CDVWebViewEngine ()
@interface CDVViewController ()

// TODO: expose private interface, if needed
- (BOOL)shouldReloadWebView;
- (BOOL)shouldReloadWebView:(NSURL*)location title:(NSString*)title;
// expose property as readwrite, for test purposes
@property (nonatomic, readwrite, strong) CDVSettingsDictionary* settings;

@end

@interface CDVViewController ()
@interface TestNavigationDelegate : NSObject <WKNavigationDelegate>
@property (nonatomic, copy) void (^didFinishNavigation)(WKWebView *, WKNavigation *);

// expose property as readwrite, for test purposes
@property (nonatomic, readwrite, strong) CDVSettingsDictionary* settings;
- (void)waitForDidFinishNavigation:(XCTestExpectation *)expectation;
@end

@interface WKWebView ()
@property (nonatomic, readonly) pid_t _webProcessIdentifier;
@end

@implementation TestNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
if (_didFinishNavigation)
_didFinishNavigation(webView, navigation);
}

- (void)waitForDidFinishNavigation:(XCTestExpectation *)expectation
{
XCTAssertFalse(self.didFinishNavigation);

__weak TestNavigationDelegate *weakSelf = self;
self.didFinishNavigation = ^(WKWebView *_view, WKNavigation *_nav) {
[expectation fulfill];
weakSelf.didFinishNavigation = nil;
};
}
@end

@implementation CDVWebViewEngineTest
Expand All @@ -52,17 +76,36 @@ - (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.

// NOTE: no app settings are set, so it will rely on default WKWebViewConfiguration settings
self.plugin = [[CDVWebViewEngine alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
self.appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[self.appDelegate createViewController];
self.viewController = self.appDelegate.testViewController;

self.plugin = (CDVWebViewEngine *)self.viewController.webViewEngine;

XCTAssert([self.plugin conformsToProtocol:@protocol(CDVWebViewEngineProtocol)], @"Plugin does not conform to CDVWebViewEngineProtocol");
}

- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[self.appDelegate destroyViewController];
[super tearDown];
}

- (void)waitForDidFinishNavigation:(WKWebView *)webView
{
NSObject<WKNavigationDelegate> *oldNavigationDelegate = webView.navigationDelegate;

__block XCTestExpectation *expectation = [self expectationWithDescription:@"didFinishNavigation"];

TestNavigationDelegate *navigationDelegate = [[TestNavigationDelegate alloc] init];
webView.navigationDelegate = navigationDelegate;
[navigationDelegate waitForDidFinishNavigation:expectation];

[self waitForExpectations:@[expectation] timeout:5];

webView.navigationDelegate = oldNavigationDelegate;
}

- (void) testCanLoadRequest {
NSURLRequest* fileUrlRequest = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:@"path/to/file.html"]];
NSURLRequest* httpUrlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://apache.org"]];
Expand Down Expand Up @@ -165,48 +208,78 @@ - (void) testConfigurationFromSettings {
} else {
for (id subview in wkWebView.subviews) {
if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
XCTAssertFalse(((UIScrollView*)subview).bounces = NO);
XCTAssertFalse(((UIScrollView*)subview).bounces == NO);
}
}
}

XCTAssertTrue(wkWebView.scrollView.decelerationRate == UIScrollViewDecelerationRateFast);
}

- (void) testShouldReloadWebView {
- (void) testCrashRecoveryRefresh {
WKWebView* wkWebView = (WKWebView*)self.plugin.engineWebView;
[self waitForDidFinishNavigation:wkWebView];

NSURL* about_blank = [NSURL URLWithString:@"about:blank"];
NSURL* real_site = [NSURL URLWithString:@"https://cordova.apache.org"];
NSString* empty_title_document = @"<html><head><title></title></head></html>";
NSString *startPage = @"https://cordova.apache.org/";
self.viewController.startPage = startPage;

// about:blank should reload
[wkWebView loadRequest:[NSURLRequest requestWithURL:about_blank]];
XCTAssertTrue([self.plugin shouldReloadWebView]);
[self.viewController loadStartPage];
[self waitForDidFinishNavigation:wkWebView];
XCTAssertTrue([[[self.plugin URL] absoluteString] isEqualToString:startPage]);

// a network location should *not* reload
[wkWebView loadRequest:[NSURLRequest requestWithURL:real_site]];
XCTAssertFalse([self.plugin shouldReloadWebView]);
NSURLRequest *nextPage = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://cordova.apache.org/blog/"]];
[self.plugin loadRequest:nextPage];
[self waitForDidFinishNavigation:wkWebView];
XCTAssertFalse([[[self.plugin URL] absoluteString] isEqualToString:startPage]);

// document with empty title should *not* reload
// baseURL:nil results in about:blank, so we use a dummy here
[wkWebView loadHTMLString:empty_title_document baseURL:[NSURL URLWithString:@"about:"]];
XCTAssertFalse([self.plugin shouldReloadWebView]);
pid_t webViewPID = [wkWebView _webProcessIdentifier];
kill(webViewPID, 9);

// Anecdotal assertion that when the WKWebView process has died,
// the title is nil, should always reload
XCTAssertTrue([self.plugin shouldReloadWebView:about_blank title:nil]);
XCTAssertTrue([self.plugin shouldReloadWebView:real_site title:nil]);
XCTestExpectation *expectation = [self expectationWithDescription:@"Waiting for 10 seconds"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:10.0];

// about:blank should always reload
XCTAssertTrue([self.plugin shouldReloadWebView:about_blank title:@"some title"]);
XCTAssertFalse([[[self.plugin URL] absoluteString] isEqualToString:startPage]);
XCTAssertTrue([[[self.plugin URL] absoluteString] isEqualToString:@"https://cordova.apache.org/blog/"]);
}

// non about:blank with a non-nil title should **not** reload
XCTAssertFalse([self.plugin shouldReloadWebView:real_site title:@""]);
- (void) testCrashRecoveryReload {
WKWebView* wkWebView = (WKWebView*)self.plugin.engineWebView;
[self waitForDidFinishNavigation:wkWebView];

NSString *startPage = @"https://cordova.apache.org/";
self.viewController.startPage = startPage;
[self.viewController.settings setCordovaSetting:@"reload" forKey:@"CrashRecoveryBehavior"];

[self.viewController loadStartPage];
[self waitForDidFinishNavigation:wkWebView];
XCTAssertTrue([[[self.plugin URL] absoluteString] isEqualToString:startPage]);

NSURLRequest *nextPage = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://cordova.apache.org/blog/"]];
[self.plugin loadRequest:nextPage];
[self waitForDidFinishNavigation:wkWebView];
XCTAssertFalse([[[self.plugin URL] absoluteString] isEqualToString:startPage]);

pid_t webViewPID = [wkWebView _webProcessIdentifier];
kill(webViewPID, 9);

XCTestExpectation *expectation = [self expectationWithDescription:@"Waiting for 10 seconds"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:10.0];

XCTAssertTrue([[[self.plugin URL] absoluteString] isEqualToString:startPage]);
}

- (void) testWKProcessPoolFactory {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
WKProcessPool* shared = [[CDVWebViewProcessPoolFactory sharedFactory] sharedProcessPool];
#pragma clang diagnostic pop

XCTAssertTrue(shared != nil);
}

Expand Down
Loading