add .gitignore
This commit is contained in:
25
msext.xcodeproj/CordovaLib/Classes/Private/CDVDebug.h
Executable file
25
msext.xcodeproj/CordovaLib/Classes/Private/CDVDebug.h
Executable file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DLog(...)
|
||||
#endif
|
||||
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
31
msext.xcodeproj/CordovaLib/Classes/Private/CDVJSON_private.h
Executable file
31
msext.xcodeproj/CordovaLib/Classes/Private/CDVJSON_private.h
Executable file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
@interface NSArray (CDVJSONSerializingPrivate)
|
||||
- (NSString*)cdv_JSONString;
|
||||
@end
|
||||
|
||||
@interface NSDictionary (CDVJSONSerializingPrivate)
|
||||
- (NSString*)cdv_JSONString;
|
||||
@end
|
||||
|
||||
@interface NSString (CDVJSONSerializingPrivate)
|
||||
- (id)cdv_JSONObject;
|
||||
- (id)cdv_JSONFragment;
|
||||
@end
|
||||
91
msext.xcodeproj/CordovaLib/Classes/Private/CDVJSON_private.m
Executable file
91
msext.xcodeproj/CordovaLib/Classes/Private/CDVJSON_private.m
Executable file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVJSON_private.h"
|
||||
#import <Foundation/NSJSONSerialization.h>
|
||||
|
||||
@implementation NSArray (CDVJSONSerializingPrivate)
|
||||
|
||||
- (NSString*)cdv_JSONString
|
||||
{
|
||||
NSError* error = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self
|
||||
options:0
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"NSArray JSONString error: %@", [error localizedDescription]);
|
||||
return nil;
|
||||
} else {
|
||||
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSDictionary (CDVJSONSerializingPrivate)
|
||||
|
||||
- (NSString*)cdv_JSONString
|
||||
{
|
||||
NSError* error = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"NSDictionary JSONString error: %@", [error localizedDescription]);
|
||||
return nil;
|
||||
} else {
|
||||
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (CDVJSONSerializingPrivate)
|
||||
|
||||
- (id)cdv_JSONObject
|
||||
{
|
||||
NSError* error = nil;
|
||||
id object = [NSJSONSerialization JSONObjectWithData:[self dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"NSString JSONObject error: %@", [error localizedDescription]);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
- (id)cdv_JSONFragment
|
||||
{
|
||||
NSError* error = nil;
|
||||
id object = [NSJSONSerialization JSONObjectWithData:[self dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:NSJSONReadingAllowFragments
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"NSString JSONObject error: %@", [error localizedDescription]);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
@end
|
||||
24
msext.xcodeproj/CordovaLib/Classes/Private/CDVPlugin+Private.h
Executable file
24
msext.xcodeproj/CordovaLib/Classes/Private/CDVPlugin+Private.h
Executable file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
@interface CDVPlugin (Private)
|
||||
|
||||
- (instancetype)initWithWebViewEngine:(id <CDVWebViewEngineProtocol>)theWebViewEngine;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVPlugin.h"
|
||||
|
||||
@interface CDVGestureHandler : CDVPlugin
|
||||
|
||||
@property (nonatomic, strong) UILongPressGestureRecognizer* lpgr;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVGestureHandler.h"
|
||||
|
||||
@implementation CDVGestureHandler
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
[self applyLongPressFix];
|
||||
}
|
||||
|
||||
- (void)applyLongPressFix
|
||||
{
|
||||
// You can't suppress 3D Touch and still have regular longpress,
|
||||
// so if this is false, let's not consider the 3D Touch setting at all.
|
||||
if (![self.commandDelegate.settings objectForKey:@"suppresseslongpressgesture"] ||
|
||||
![[self.commandDelegate.settings objectForKey:@"suppresseslongpressgesture"] boolValue]) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGestures:)];
|
||||
self.lpgr.minimumPressDuration = 0.45f;
|
||||
self.lpgr.allowableMovement = 100.0f;
|
||||
|
||||
// 0.45 is ok for 'regular longpress', 0.05-0.08 is required for '3D Touch longpress',
|
||||
// but since this will also kill onclick handlers (not ontouchend) it's optional.
|
||||
if ([self.commandDelegate.settings objectForKey:@"suppresses3dtouchgesture"] &&
|
||||
[[self.commandDelegate.settings objectForKey:@"suppresses3dtouchgesture"] boolValue]) {
|
||||
self.lpgr.minimumPressDuration = 0.05f;
|
||||
}
|
||||
|
||||
NSArray *views = self.webView.subviews;
|
||||
if (views.count == 0) {
|
||||
NSLog(@"No webview subviews found, not applying the longpress fix.");
|
||||
return;
|
||||
}
|
||||
for (int i=0; i<views.count; i++) {
|
||||
UIView *webViewScrollView = views[i];
|
||||
if ([webViewScrollView isKindOfClass:[UIScrollView class]]) {
|
||||
NSArray *webViewScrollViewSubViews = webViewScrollView.subviews;
|
||||
UIView *browser = webViewScrollViewSubViews[0];
|
||||
[browser addGestureRecognizer:self.lpgr];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleLongPressGestures:(UILongPressGestureRecognizer*)sender
|
||||
{
|
||||
if ([sender isEqual:self.lpgr]) {
|
||||
if (sender.state == UIGestureRecognizerStateBegan) {
|
||||
NSLog(@"Ignoring a longpress in order to suppress the magnifying glass.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVPlugin.h"
|
||||
|
||||
@interface CDVHandleOpenURL : CDVPlugin
|
||||
|
||||
@property (nonatomic, strong) NSURL* url;
|
||||
@property (nonatomic, assign) BOOL pageLoaded;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVHandleOpenURL.h"
|
||||
#import "CDV.h"
|
||||
|
||||
@implementation CDVHandleOpenURL
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationLaunchedWithUrl:) name:CDVPluginHandleOpenURLNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationPageDidLoad:) name:CDVPageDidLoadNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)applicationLaunchedWithUrl:(NSNotification*)notification
|
||||
{
|
||||
NSURL* url = [notification object];
|
||||
|
||||
self.url = url;
|
||||
|
||||
// warm-start handler
|
||||
if (self.pageLoaded) {
|
||||
[self processOpenUrl:self.url pageLoaded:YES];
|
||||
self.url = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationPageDidLoad:(NSNotification*)notification
|
||||
{
|
||||
// cold-start handler
|
||||
|
||||
self.pageLoaded = YES;
|
||||
|
||||
if (self.url) {
|
||||
[self processOpenUrl:self.url pageLoaded:YES];
|
||||
self.url = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)processOpenUrl:(NSURL*)url pageLoaded:(BOOL)pageLoaded
|
||||
{
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
dispatch_block_t handleOpenUrl = ^(void) {
|
||||
// calls into javascript global function 'handleOpenURL'
|
||||
NSString* jsString = [NSString stringWithFormat:@"document.addEventListener('deviceready',function(){if (typeof handleOpenURL === 'function') { handleOpenURL(\"%@\");}});", url.absoluteString];
|
||||
|
||||
[weakSelf.webViewEngine evaluateJavaScript:jsString completionHandler:nil];
|
||||
};
|
||||
|
||||
if (!pageLoaded) {
|
||||
NSString* jsString = @"document.readystate";
|
||||
[self.webViewEngine evaluateJavaScript:jsString
|
||||
completionHandler:^(id object, NSError* error) {
|
||||
if ((error == nil) && [object isKindOfClass:[NSString class]]) {
|
||||
NSString* readyState = (NSString*)object;
|
||||
BOOL ready = [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"];
|
||||
if (ready) {
|
||||
handleOpenUrl();
|
||||
} else {
|
||||
self.url = url;
|
||||
}
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
handleOpenUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVPlugin.h"
|
||||
|
||||
@interface CDVIntentAndNavigationFilter : CDVPlugin <NSXMLParserDelegate>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVIntentAndNavigationFilter.h"
|
||||
#import <Cordova/CDV.h>
|
||||
|
||||
@interface CDVIntentAndNavigationFilter ()
|
||||
|
||||
@property (nonatomic, readwrite) NSMutableArray* allowIntents;
|
||||
@property (nonatomic, readwrite) NSMutableArray* allowNavigations;
|
||||
@property (nonatomic, readwrite) CDVWhitelist* allowIntentsWhitelist;
|
||||
@property (nonatomic, readwrite) CDVWhitelist* allowNavigationsWhitelist;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CDVIntentAndNavigationFilter
|
||||
|
||||
#pragma mark NSXMLParserDelegate
|
||||
|
||||
- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict
|
||||
{
|
||||
if ([elementName isEqualToString:@"allow-navigation"]) {
|
||||
[self.allowNavigations addObject:attributeDict[@"href"]];
|
||||
}
|
||||
if ([elementName isEqualToString:@"allow-intent"]) {
|
||||
[self.allowIntents addObject:attributeDict[@"href"]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parserDidStartDocument:(NSXMLParser*)parser
|
||||
{
|
||||
// file: url <allow-navigations> are added by default
|
||||
self.allowNavigations = [[NSMutableArray alloc] initWithArray:@[ @"file://" ]];
|
||||
// no intents are added by default
|
||||
self.allowIntents = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
- (void)parserDidEndDocument:(NSXMLParser*)parser
|
||||
{
|
||||
self.allowIntentsWhitelist = [[CDVWhitelist alloc] initWithArray:self.allowIntents];
|
||||
self.allowNavigationsWhitelist = [[CDVWhitelist alloc] initWithArray:self.allowNavigations];
|
||||
}
|
||||
|
||||
- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError
|
||||
{
|
||||
NSAssert(NO, @"config.xml parse error line %ld col %ld", (long)[parser lineNumber], (long)[parser columnNumber]);
|
||||
}
|
||||
|
||||
#pragma mark CDVPlugin
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
if ([self.viewController isKindOfClass:[CDVViewController class]]) {
|
||||
[(CDVViewController*)self.viewController parseSettingsWithParser:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
|
||||
{
|
||||
NSString* allowIntents_whitelistRejectionFormatString = @"ERROR External navigation rejected - <allow-intent> not set for url='%@'";
|
||||
NSString* allowNavigations_whitelistRejectionFormatString = @"ERROR Internal navigation rejected - <allow-navigation> not set for url='%@'";
|
||||
|
||||
NSURL* url = [request URL];
|
||||
BOOL allowNavigationsPass = NO;
|
||||
NSMutableArray* errorLogs = [NSMutableArray array];
|
||||
|
||||
switch (navigationType) {
|
||||
case UIWebViewNavigationTypeLinkClicked:
|
||||
// Note that the rejection strings will *only* print if
|
||||
// it's a link click (and url is not whitelisted by <allow-*>)
|
||||
if ([self.allowIntentsWhitelist URLIsAllowed:url logFailure:NO]) {
|
||||
// the url *is* in a <allow-intent> tag, push to the system
|
||||
[[UIApplication sharedApplication] openURL:url];
|
||||
return NO;
|
||||
} else {
|
||||
[errorLogs addObject:[NSString stringWithFormat:allowIntents_whitelistRejectionFormatString, [url absoluteString]]];
|
||||
}
|
||||
// fall through, to check whether you can load this in the webview
|
||||
default:
|
||||
// check whether we can internally navigate to this url
|
||||
allowNavigationsPass = [self.allowNavigationsWhitelist URLIsAllowed:url logFailure:NO];
|
||||
// log all failures only when this last filter fails
|
||||
if (!allowNavigationsPass){
|
||||
[errorLogs addObject:[NSString stringWithFormat:allowNavigations_whitelistRejectionFormatString, [url absoluteString]]];
|
||||
|
||||
// this is the last filter and it failed, now print out all previous error logs
|
||||
for (NSString* errorLog in errorLogs) {
|
||||
NSLog(@"%@", errorLog);
|
||||
}
|
||||
}
|
||||
|
||||
return allowNavigationsPass;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVPlugin.h"
|
||||
|
||||
#define kCDVLocalStorageErrorDomain @"kCDVLocalStorageErrorDomain"
|
||||
#define kCDVLocalStorageFileOperationError 1
|
||||
|
||||
@interface CDVLocalStorage : CDVPlugin
|
||||
|
||||
@property (nonatomic, readonly, strong) NSMutableArray* backupInfo;
|
||||
|
||||
- (BOOL)shouldBackup;
|
||||
- (BOOL)shouldRestore;
|
||||
- (void)backup:(CDVInvokedUrlCommand*)command;
|
||||
- (void)restore:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
+ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType;
|
||||
// Visible for testing.
|
||||
+ (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict
|
||||
bundlePath:(NSString*)bundlePath
|
||||
fileManager:(NSFileManager*)fileManager;
|
||||
@end
|
||||
|
||||
@interface CDVBackupInfo : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString* original;
|
||||
@property (nonatomic, copy) NSString* backup;
|
||||
@property (nonatomic, copy) NSString* label;
|
||||
|
||||
- (BOOL)shouldBackup;
|
||||
- (BOOL)shouldRestore;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVLocalStorage.h"
|
||||
#import "CDV.h"
|
||||
|
||||
@interface CDVLocalStorage ()
|
||||
|
||||
@property (nonatomic, readwrite, strong) NSMutableArray* backupInfo; // array of CDVBackupInfo objects
|
||||
@property (nonatomic, readwrite, weak) id <UIWebViewDelegate> webviewDelegate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CDVLocalStorage
|
||||
|
||||
@synthesize backupInfo, webviewDelegate;
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive)
|
||||
name:UIApplicationWillResignActiveNotification object:nil];
|
||||
BOOL cloudBackup = [@"cloud" isEqualToString : self.commandDelegate.settings[[@"BackupWebStorage" lowercaseString]]];
|
||||
|
||||
self.backupInfo = [[self class] createBackupInfoWithCloudBackup:cloudBackup];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Plugin interface methods
|
||||
|
||||
+ (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir:(NSString*)backupDir targetDirNests:(BOOL)targetDirNests backupDirNests:(BOOL)backupDirNests rename:(BOOL)rename
|
||||
{
|
||||
/*
|
||||
This "helper" does so much work and has so many options it would probably be clearer to refactor the whole thing.
|
||||
Basically, there are three database locations:
|
||||
|
||||
1. "Normal" dir -- LIB/<nested dires WebKit/LocalStorage etc>/<normal filenames>
|
||||
2. "Caches" dir -- LIB/Caches/<normal filenames>
|
||||
3. "Backup" dir -- DOC/Backups/<renamed filenames>
|
||||
|
||||
And between these three, there are various migration paths, most of which only consider 2 of the 3, which is why this helper is based on 2 locations and has a notion of "direction".
|
||||
*/
|
||||
NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:3];
|
||||
|
||||
NSString* original;
|
||||
NSString* backup;
|
||||
CDVBackupInfo* backupItem;
|
||||
|
||||
// ////////// LOCALSTORAGE
|
||||
|
||||
original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0.localstorage":@"file__0.localstorage"];
|
||||
backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
|
||||
backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db" : @"file__0.localstorage")];
|
||||
|
||||
backupItem = [[CDVBackupInfo alloc] init];
|
||||
backupItem.backup = backup;
|
||||
backupItem.original = original;
|
||||
backupItem.label = @"localStorage database";
|
||||
|
||||
[backupInfo addObject:backupItem];
|
||||
|
||||
// ////////// WEBSQL MAIN DB
|
||||
|
||||
original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/Databases.db":@"Databases.db"];
|
||||
backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
|
||||
backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db" : @"Databases.db")];
|
||||
|
||||
backupItem = [[CDVBackupInfo alloc] init];
|
||||
backupItem.backup = backup;
|
||||
backupItem.original = original;
|
||||
backupItem.label = @"websql main database";
|
||||
|
||||
[backupInfo addObject:backupItem];
|
||||
|
||||
// ////////// WEBSQL DATABASES
|
||||
|
||||
original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0":@"file__0"];
|
||||
backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
|
||||
backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db" : @"file__0")];
|
||||
|
||||
backupItem = [[CDVBackupInfo alloc] init];
|
||||
backupItem.backup = backup;
|
||||
backupItem.original = original;
|
||||
backupItem.label = @"websql databases";
|
||||
|
||||
[backupInfo addObject:backupItem];
|
||||
|
||||
return backupInfo;
|
||||
}
|
||||
|
||||
+ (NSMutableArray*)createBackupInfoWithCloudBackup:(BOOL)cloudBackup
|
||||
{
|
||||
// create backup info from backup folder to caches folder
|
||||
NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
NSString* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"];
|
||||
NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"];
|
||||
|
||||
// create the backups folder, if needed
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:backupsFolder withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
|
||||
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:backupsFolder] skip:!cloudBackup];
|
||||
|
||||
return [self createBackupInfoWithTargetDir:cacheFolder backupDir:backupsFolder targetDirNests:NO backupDirNests:NO rename:YES];
|
||||
}
|
||||
|
||||
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL skip:(BOOL)skip
|
||||
{
|
||||
NSError* error = nil;
|
||||
BOOL success = [URL setResourceValue:[NSNumber numberWithBool:skip] forKey:NSURLIsExcludedFromBackupKey error:&error];
|
||||
|
||||
if (!success) {
|
||||
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
+ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError* __autoreleasing*)error
|
||||
{
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (![fileManager fileExistsAtPath:src]) {
|
||||
NSString* errorString = [NSString stringWithFormat:@"%@ file does not exist.", src];
|
||||
if (error != NULL) {
|
||||
(*error) = [NSError errorWithDomain:kCDVLocalStorageErrorDomain
|
||||
code:kCDVLocalStorageFileOperationError
|
||||
userInfo:[NSDictionary dictionaryWithObject:errorString
|
||||
forKey:NSLocalizedDescriptionKey]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// generate unique filepath in temp directory
|
||||
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
|
||||
CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
|
||||
NSString* tempBackup = [[NSTemporaryDirectory() stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"];
|
||||
CFRelease(uuidString);
|
||||
CFRelease(uuidRef);
|
||||
|
||||
BOOL destExists = [fileManager fileExistsAtPath:dest];
|
||||
|
||||
// backup the dest
|
||||
if (destExists && ![fileManager copyItemAtPath:dest toPath:tempBackup error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// remove the dest
|
||||
if (destExists && ![fileManager removeItemAtPath:dest error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// create path to dest
|
||||
if (!destExists && ![fileManager createDirectoryAtPath:[dest stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// copy src to dest
|
||||
if ([fileManager copyItemAtPath:src toPath:dest error:error]) {
|
||||
// success - cleanup - delete the backup to the dest
|
||||
if ([fileManager fileExistsAtPath:tempBackup]) {
|
||||
[fileManager removeItemAtPath:tempBackup error:error];
|
||||
}
|
||||
return YES;
|
||||
} else {
|
||||
// failure - we restore the temp backup file to dest
|
||||
[fileManager copyItemAtPath:tempBackup toPath:dest error:error];
|
||||
// cleanup - delete the backup to the dest
|
||||
if ([fileManager fileExistsAtPath:tempBackup]) {
|
||||
[fileManager removeItemAtPath:tempBackup error:error];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldBackup
|
||||
{
|
||||
for (CDVBackupInfo* info in self.backupInfo) {
|
||||
if ([info shouldBackup]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldRestore
|
||||
{
|
||||
for (CDVBackupInfo* info in self.backupInfo) {
|
||||
if ([info shouldRestore]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* copy from webkitDbLocation to persistentDbLocation */
|
||||
- (void)backup:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSString* callbackId = command.callbackId;
|
||||
|
||||
NSError* __autoreleasing error = nil;
|
||||
CDVPluginResult* result = nil;
|
||||
NSString* message = nil;
|
||||
|
||||
for (CDVBackupInfo* info in self.backupInfo) {
|
||||
if ([info shouldBackup]) {
|
||||
[[self class] copyFrom:info.original to:info.backup error:&error];
|
||||
|
||||
if (callbackId) {
|
||||
if (error == nil) {
|
||||
message = [NSString stringWithFormat:@"Backed up: %@", info.label];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
} else {
|
||||
message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) backup: %@", info.label, [error localizedDescription]];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy from persistentDbLocation to webkitDbLocation */
|
||||
- (void)restore:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
NSError* __autoreleasing error = nil;
|
||||
CDVPluginResult* result = nil;
|
||||
NSString* message = nil;
|
||||
|
||||
for (CDVBackupInfo* info in self.backupInfo) {
|
||||
if ([info shouldRestore]) {
|
||||
[[self class] copyFrom:info.backup to:info.original error:&error];
|
||||
|
||||
if (error == nil) {
|
||||
message = [NSString stringWithFormat:@"Restored: %@", info.label];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
} else {
|
||||
message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) restore: %@", info.label, [error localizedDescription]];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
|
||||
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType
|
||||
{
|
||||
[self __verifyAndFixDatabaseLocations];
|
||||
[self __restoreLegacyDatabaseLocationsWithBackupType:backupType];
|
||||
}
|
||||
|
||||
+ (void)__verifyAndFixDatabaseLocations
|
||||
{
|
||||
NSBundle* mainBundle = [NSBundle mainBundle];
|
||||
NSString* bundlePath = [[mainBundle bundlePath] stringByDeletingLastPathComponent];
|
||||
NSString* bundleIdentifier = [[mainBundle infoDictionary] objectForKey:@"CFBundleIdentifier"];
|
||||
NSString* appPlistPath = [bundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"Library/Preferences/%@.plist", bundleIdentifier]];
|
||||
|
||||
NSMutableDictionary* appPlistDict = [NSMutableDictionary dictionaryWithContentsOfFile:appPlistPath];
|
||||
BOOL modified = [[self class] __verifyAndFixDatabaseLocationsWithAppPlistDict:appPlistDict
|
||||
bundlePath:bundlePath
|
||||
fileManager:[NSFileManager defaultManager]];
|
||||
|
||||
if (modified) {
|
||||
BOOL ok = [appPlistDict writeToFile:appPlistPath atomically:YES];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
NSLog(@"Fix applied for database locations?: %@", ok ? @"YES" : @"NO");
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict
|
||||
bundlePath:(NSString*)bundlePath
|
||||
fileManager:(NSFileManager*)fileManager
|
||||
{
|
||||
NSString* libraryCaches = @"Library/Caches";
|
||||
NSString* libraryWebKit = @"Library/WebKit";
|
||||
|
||||
NSArray* keysToCheck = [NSArray arrayWithObjects:
|
||||
@"WebKitLocalStorageDatabasePathPreferenceKey",
|
||||
@"WebDatabaseDirectory",
|
||||
nil];
|
||||
|
||||
BOOL dirty = NO;
|
||||
|
||||
for (NSString* key in keysToCheck) {
|
||||
NSString* value = [appPlistDict objectForKey:key];
|
||||
// verify key exists, and path is in app bundle, if not - fix
|
||||
if ((value != nil) && ![value hasPrefix:bundlePath]) {
|
||||
// the pathSuffix to use may be wrong - OTA upgrades from < 5.1 to 5.1 do keep the old path Library/WebKit,
|
||||
// while Xcode synced ones do change the storage location to Library/Caches
|
||||
NSString* newBundlePath = [bundlePath stringByAppendingPathComponent:libraryCaches];
|
||||
if (![fileManager fileExistsAtPath:newBundlePath]) {
|
||||
newBundlePath = [bundlePath stringByAppendingPathComponent:libraryWebKit];
|
||||
}
|
||||
[appPlistDict setValue:newBundlePath forKey:key];
|
||||
dirty = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
+ (void)__restoreLegacyDatabaseLocationsWithBackupType:(NSString*)backupType
|
||||
{
|
||||
// on iOS 6, if you toggle between cloud/local backup, you must move database locations. Default upgrade from iOS5.1 to iOS6 is like a toggle from local to cloud.
|
||||
NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
|
||||
|
||||
NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:0];
|
||||
|
||||
if ([backupType isEqualToString:@"cloud"]) {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"\n\nStarted backup to iCloud! Please be careful."
|
||||
"\nYour application might be rejected by Apple if you store too much data."
|
||||
"\nFor more information please read \"iOS Data Storage Guidelines\" at:"
|
||||
"\nhttps://developer.apple.com/icloud/documentation/data-storage/"
|
||||
"\nTo disable web storage backup to iCloud, set the BackupWebStorage preference to \"local\" in the Cordova config.xml file\n\n");
|
||||
#endif
|
||||
// We would like to restore old backups/caches databases to the new destination (nested in lib folder)
|
||||
[backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appDocumentsFolder stringByAppendingPathComponent:@"Backups"] targetDirNests:YES backupDirNests:NO rename:YES]];
|
||||
[backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] targetDirNests:YES backupDirNests:NO rename:NO]];
|
||||
} else {
|
||||
// For ios6 local backups we also want to restore from Backups dir -- but we don't need to do that here, since the plugin will do that itself.
|
||||
[backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] backupDir:appLibraryFolder targetDirNests:NO backupDirNests:YES rename:NO]];
|
||||
}
|
||||
|
||||
NSFileManager* manager = [NSFileManager defaultManager];
|
||||
|
||||
for (CDVBackupInfo* info in backupInfo) {
|
||||
if ([manager fileExistsAtPath:info.backup]) {
|
||||
if ([info shouldRestore]) {
|
||||
NSLog(@"Restoring old webstorage backup. From: '%@' To: '%@'.", info.backup, info.original);
|
||||
[self copyFrom:info.backup to:info.original error:nil];
|
||||
}
|
||||
NSLog(@"Removing old webstorage backup: '%@'.", info.backup);
|
||||
[manager removeItemAtPath:info.backup error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:[backupType isEqualToString:@"cloud"] forKey:@"WebKitStoreWebDataForBackup"];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Notification handlers
|
||||
|
||||
- (void)onResignActive
|
||||
{
|
||||
UIDevice* device = [UIDevice currentDevice];
|
||||
NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"];
|
||||
|
||||
BOOL isMultitaskingSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported];
|
||||
|
||||
if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default)
|
||||
exitsOnSuspend = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
|
||||
if (exitsOnSuspend) {
|
||||
[self backup:nil];
|
||||
} else if (isMultitaskingSupported) {
|
||||
__block UIBackgroundTaskIdentifier backgroundTaskID = UIBackgroundTaskInvalid;
|
||||
|
||||
backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
|
||||
backgroundTaskID = UIBackgroundTaskInvalid;
|
||||
NSLog(@"Background task to backup WebSQL/LocalStorage expired.");
|
||||
}];
|
||||
CDVLocalStorage __weak* weakSelf = self;
|
||||
[self.commandDelegate runInBackground:^{
|
||||
[weakSelf backup:nil];
|
||||
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
|
||||
backgroundTaskID = UIBackgroundTaskInvalid;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onAppTerminate
|
||||
{
|
||||
[self onResignActive];
|
||||
}
|
||||
|
||||
- (void)onReset
|
||||
{
|
||||
[self restore:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CDVBackupInfo implementation
|
||||
|
||||
@implementation CDVBackupInfo
|
||||
|
||||
@synthesize original, backup, label;
|
||||
|
||||
- (BOOL)file:(NSString*)aPath isNewerThanFile:(NSString*)bPath
|
||||
{
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
NSError* __autoreleasing error = nil;
|
||||
|
||||
NSDictionary* aPathAttribs = [fileManager attributesOfItemAtPath:aPath error:&error];
|
||||
NSDictionary* bPathAttribs = [fileManager attributesOfItemAtPath:bPath error:&error];
|
||||
|
||||
NSDate* aPathModDate = [aPathAttribs objectForKey:NSFileModificationDate];
|
||||
NSDate* bPathModDate = [bPathAttribs objectForKey:NSFileModificationDate];
|
||||
|
||||
if ((nil == aPathModDate) && (nil == bPathModDate)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [aPathModDate compare:bPathModDate] == NSOrderedDescending || bPathModDate == nil;
|
||||
}
|
||||
|
||||
- (BOOL)item:(NSString*)aPath isNewerThanItem:(NSString*)bPath
|
||||
{
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
|
||||
BOOL aPathIsDir = NO, bPathIsDir = NO;
|
||||
BOOL aPathExists = [fileManager fileExistsAtPath:aPath isDirectory:&aPathIsDir];
|
||||
|
||||
[fileManager fileExistsAtPath:bPath isDirectory:&bPathIsDir];
|
||||
|
||||
if (!aPathExists) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!(aPathIsDir && bPathIsDir)) { // just a file
|
||||
return [self file:aPath isNewerThanFile:bPath];
|
||||
}
|
||||
|
||||
// essentially we want rsync here, but have to settle for our poor man's implementation
|
||||
// we get the files in aPath, and see if it is newer than the file in bPath
|
||||
// (it is newer if it doesn't exist in bPath) if we encounter the FIRST file that is newer,
|
||||
// we return YES
|
||||
NSDirectoryEnumerator* directoryEnumerator = [fileManager enumeratorAtPath:aPath];
|
||||
NSString* path;
|
||||
|
||||
while ((path = [directoryEnumerator nextObject])) {
|
||||
NSString* aPathFile = [aPath stringByAppendingPathComponent:path];
|
||||
NSString* bPathFile = [bPath stringByAppendingPathComponent:path];
|
||||
|
||||
BOOL isNewer = [self file:aPathFile isNewerThanFile:bPathFile];
|
||||
if (isNewer) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldBackup
|
||||
{
|
||||
return [self item:self.original isNewerThanItem:self.backup];
|
||||
}
|
||||
|
||||
- (BOOL)shouldRestore
|
||||
{
|
||||
return [self item:self.backup isNewerThanItem:self.original];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "CDVAvailability.h"
|
||||
|
||||
/**
|
||||
* Distinguishes top-level navigations from sub-frame navigations.
|
||||
* shouldStartLoadWithRequest is called for every request, but didStartLoad
|
||||
* and didFinishLoad is called only for top-level navigations.
|
||||
* Relevant bug: CB-2389
|
||||
*/
|
||||
@interface CDVUIWebViewDelegate : NSObject <UIWebViewDelegate>{
|
||||
__weak NSObject <UIWebViewDelegate>* _delegate;
|
||||
NSInteger _loadCount;
|
||||
NSInteger _state;
|
||||
NSInteger _curLoadToken;
|
||||
NSInteger _loadStartPollCount;
|
||||
}
|
||||
|
||||
- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate;
|
||||
|
||||
- (BOOL)request:(NSURLRequest*)newRequest isEqualToRequestAfterStrippingFragments:(NSURLRequest*)originalRequest;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Testing shows:
|
||||
//
|
||||
// In all cases, webView.request.URL is the previous page's URL (or empty) during the didStartLoad callback.
|
||||
// When loading a page with a redirect:
|
||||
// 1. shouldStartLoading (requestURL is target page)
|
||||
// 2. didStartLoading
|
||||
// 3. shouldStartLoading (requestURL is redirect target)
|
||||
// 4. didFinishLoad (request.URL is redirect target)
|
||||
//
|
||||
// Note the lack of a second didStartLoading **
|
||||
//
|
||||
// When loading a page with iframes:
|
||||
// 1. shouldStartLoading (requestURL is main page)
|
||||
// 2. didStartLoading
|
||||
// 3. shouldStartLoading (requestURL is one of the iframes)
|
||||
// 4. didStartLoading
|
||||
// 5. didFinishLoad
|
||||
// 6. didFinishLoad
|
||||
//
|
||||
// Note there is no way to distinguish which didFinishLoad maps to which didStartLoad **
|
||||
//
|
||||
// Loading a page by calling window.history.go(-1):
|
||||
// 1. didStartLoading
|
||||
// 2. didFinishLoad
|
||||
//
|
||||
// Note the lack of a shouldStartLoading call **
|
||||
// Actually - this is fixed on iOS6. iOS6 has a shouldStart. **
|
||||
//
|
||||
// Loading a page by calling location.reload()
|
||||
// 1. shouldStartLoading
|
||||
// 2. didStartLoading
|
||||
// 3. didFinishLoad
|
||||
//
|
||||
// Loading a page with an iframe that fails to load:
|
||||
// 1. shouldStart (main page)
|
||||
// 2. didStart
|
||||
// 3. shouldStart (iframe)
|
||||
// 4. didStart
|
||||
// 5. didFailWithError
|
||||
// 6. didFinish
|
||||
//
|
||||
// Loading a page with an iframe that fails to load due to an invalid URL:
|
||||
// 1. shouldStart (main page)
|
||||
// 2. didStart
|
||||
// 3. shouldStart (iframe)
|
||||
// 5. didFailWithError
|
||||
// 6. didFinish
|
||||
//
|
||||
// This case breaks our logic since there is a missing didStart. To prevent this,
|
||||
// we check URLs in shouldStart and return NO if they are invalid.
|
||||
//
|
||||
// Loading a page with an invalid URL
|
||||
// 1. shouldStart (main page)
|
||||
// 2. didFailWithError
|
||||
//
|
||||
// TODO: Record order when page is re-navigated before the first navigation finishes.
|
||||
//
|
||||
|
||||
#import "CDVUIWebViewDelegate.h"
|
||||
|
||||
// #define VerboseLog NSLog
|
||||
#define VerboseLog(...) do { \
|
||||
} while (0)
|
||||
|
||||
typedef enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_WAITING_FOR_LOAD_START = 1,
|
||||
STATE_WAITING_FOR_LOAD_FINISH = 2,
|
||||
STATE_IOS5_POLLING_FOR_LOAD_START = 3,
|
||||
STATE_IOS5_POLLING_FOR_LOAD_FINISH = 4,
|
||||
STATE_CANCELLED = 5
|
||||
} State;
|
||||
|
||||
static NSString *stripFragment(NSString* url)
|
||||
{
|
||||
NSRange r = [url rangeOfString:@"#"];
|
||||
|
||||
if (r.location == NSNotFound) {
|
||||
return url;
|
||||
}
|
||||
return [url substringToIndex:r.location];
|
||||
}
|
||||
|
||||
@implementation CDVUIWebViewDelegate
|
||||
|
||||
- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_delegate = delegate;
|
||||
_loadCount = -1;
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)request:(NSURLRequest*)newRequest isEqualToRequestAfterStrippingFragments:(NSURLRequest*)originalRequest
|
||||
{
|
||||
if (originalRequest.URL && newRequest.URL) {
|
||||
NSString* originalRequestUrl = [originalRequest.URL absoluteString];
|
||||
NSString* newRequestUrl = [newRequest.URL absoluteString];
|
||||
|
||||
NSString* baseOriginalRequestUrl = stripFragment(originalRequestUrl);
|
||||
NSString* baseNewRequestUrl = stripFragment(newRequestUrl);
|
||||
return [baseOriginalRequestUrl isEqualToString:baseNewRequestUrl];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isPageLoaded:(UIWebView*)webView
|
||||
{
|
||||
NSString* readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];
|
||||
|
||||
return [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"];
|
||||
}
|
||||
|
||||
- (BOOL)isJsLoadTokenSet:(UIWebView*)webView
|
||||
{
|
||||
NSString* loadToken = [webView stringByEvaluatingJavaScriptFromString:@"window.__cordovaLoadToken"];
|
||||
|
||||
return [[NSString stringWithFormat:@"%ld", (long)_curLoadToken] isEqualToString:loadToken];
|
||||
}
|
||||
|
||||
- (void)setLoadToken:(UIWebView*)webView
|
||||
{
|
||||
_curLoadToken += 1;
|
||||
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.__cordovaLoadToken=%ld", (long)_curLoadToken]];
|
||||
}
|
||||
|
||||
- (NSString*)evalForCurrentURL:(UIWebView*)webView
|
||||
{
|
||||
return [webView stringByEvaluatingJavaScriptFromString:@"location.href"];
|
||||
}
|
||||
|
||||
- (void)pollForPageLoadStart:(UIWebView*)webView
|
||||
{
|
||||
if (_state != STATE_IOS5_POLLING_FOR_LOAD_START) {
|
||||
return;
|
||||
}
|
||||
if (![self isJsLoadTokenSet:webView]) {
|
||||
VerboseLog(@"Polled for page load start. result = YES!");
|
||||
_state = STATE_IOS5_POLLING_FOR_LOAD_FINISH;
|
||||
[self setLoadToken:webView];
|
||||
if ([_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
|
||||
[_delegate webViewDidStartLoad:webView];
|
||||
}
|
||||
[self pollForPageLoadFinish:webView];
|
||||
} else {
|
||||
VerboseLog(@"Polled for page load start. result = NO");
|
||||
// Poll only for 1 second, and then fall back on checking only when delegate methods are called.
|
||||
++_loadStartPollCount;
|
||||
if (_loadStartPollCount < (1000 * .05)) {
|
||||
[self performSelector:@selector(pollForPageLoadStart:) withObject:webView afterDelay:.05];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pollForPageLoadFinish:(UIWebView*)webView
|
||||
{
|
||||
if (_state != STATE_IOS5_POLLING_FOR_LOAD_FINISH) {
|
||||
return;
|
||||
}
|
||||
if ([self isPageLoaded:webView]) {
|
||||
VerboseLog(@"Polled for page load finish. result = YES!");
|
||||
_state = STATE_IDLE;
|
||||
if ([_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
|
||||
[_delegate webViewDidFinishLoad:webView];
|
||||
}
|
||||
} else {
|
||||
VerboseLog(@"Polled for page load finish. result = NO");
|
||||
[self performSelector:@selector(pollForPageLoadFinish:) withObject:webView afterDelay:.05];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldLoadRequest:(NSURLRequest*)request
|
||||
{
|
||||
NSString* scheme = [[request URL] scheme];
|
||||
NSArray* allowedSchemes = [NSArray arrayWithObjects:@"mailto",@"tel",@"blob",@"sms",@"data", nil];
|
||||
if([allowedSchemes containsObject:scheme]) {
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
return [NSURLConnection canHandleRequest:request];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
|
||||
{
|
||||
BOOL shouldLoad = YES;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
|
||||
shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
|
||||
}
|
||||
|
||||
VerboseLog(@"webView shouldLoad=%d (before) state=%d loadCount=%d URL=%@", shouldLoad, _state, _loadCount, request.URL);
|
||||
|
||||
if (shouldLoad) {
|
||||
// When devtools refresh occurs, it blindly uses the same request object. If a history.replaceState() has occured, then
|
||||
// mainDocumentURL != URL even though it's a top-level navigation.
|
||||
BOOL isDevToolsRefresh = (request == webView.request);
|
||||
BOOL isTopLevelNavigation = isDevToolsRefresh || [request.URL isEqual:[request mainDocumentURL]];
|
||||
if (isTopLevelNavigation) {
|
||||
// Ignore hash changes that don't navigate to a different page.
|
||||
// webView.request does actually update when history.replaceState() gets called.
|
||||
if ([self request:request isEqualToRequestAfterStrippingFragments:webView.request]) {
|
||||
NSString* prevURL = [self evalForCurrentURL:webView];
|
||||
if ([prevURL isEqualToString:[request.URL absoluteString]]) {
|
||||
VerboseLog(@"Page reload detected.");
|
||||
} else {
|
||||
VerboseLog(@"Detected hash change shouldLoad");
|
||||
return shouldLoad;
|
||||
}
|
||||
}
|
||||
|
||||
switch (_state) {
|
||||
case STATE_WAITING_FOR_LOAD_FINISH:
|
||||
// Redirect case.
|
||||
// We expect loadCount == 1.
|
||||
if (_loadCount != 1) {
|
||||
NSLog(@"CDVWebViewDelegate: Detected redirect when loadCount=%ld", (long)_loadCount);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_IDLE:
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_START:
|
||||
case STATE_CANCELLED:
|
||||
// Page navigation start.
|
||||
_loadCount = 0;
|
||||
_state = STATE_WAITING_FOR_LOAD_START;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
NSString* description = [NSString stringWithFormat:@"CDVWebViewDelegate: Navigation started when state=%ld", (long)_state];
|
||||
NSLog(@"%@", description);
|
||||
_loadCount = 0;
|
||||
_state = STATE_WAITING_FOR_LOAD_START;
|
||||
if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
|
||||
NSDictionary* errorDictionary = @{NSLocalizedDescriptionKey : description};
|
||||
NSError* error = [[NSError alloc] initWithDomain:@"CDVUIWebViewDelegate" code:1 userInfo:errorDictionary];
|
||||
[_delegate webView:webView didFailLoadWithError:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Deny invalid URLs so that we don't get the case where we go straight from
|
||||
// webViewShouldLoad -> webViewDidFailLoad (messes up _loadCount).
|
||||
shouldLoad = shouldLoad && [self shouldLoadRequest:request];
|
||||
}
|
||||
VerboseLog(@"webView shouldLoad=%d (after) isTopLevelNavigation=%d state=%d loadCount=%d", shouldLoad, isTopLevelNavigation, _state, _loadCount);
|
||||
}
|
||||
return shouldLoad;
|
||||
}
|
||||
|
||||
- (void)webViewDidStartLoad:(UIWebView*)webView
|
||||
{
|
||||
VerboseLog(@"webView didStartLoad (before). state=%d loadCount=%d", _state, _loadCount);
|
||||
BOOL fireCallback = NO;
|
||||
switch (_state) {
|
||||
case STATE_IDLE:
|
||||
break;
|
||||
|
||||
case STATE_CANCELLED:
|
||||
fireCallback = YES;
|
||||
_state = STATE_WAITING_FOR_LOAD_FINISH;
|
||||
_loadCount += 1;
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_START:
|
||||
if (_loadCount != 0) {
|
||||
NSLog(@"CDVWebViewDelegate: Unexpected loadCount in didStart. count=%ld", (long)_loadCount);
|
||||
}
|
||||
fireCallback = YES;
|
||||
_state = STATE_WAITING_FOR_LOAD_FINISH;
|
||||
_loadCount = 1;
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_FINISH:
|
||||
_loadCount += 1;
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_START:
|
||||
[self pollForPageLoadStart:webView];
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
|
||||
[self pollForPageLoadFinish:webView];
|
||||
break;
|
||||
|
||||
default:
|
||||
NSLog(@"CDVWebViewDelegate: Unexpected didStart with state=%ld loadCount=%ld", (long)_state, (long)_loadCount);
|
||||
}
|
||||
VerboseLog(@"webView didStartLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback);
|
||||
if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
|
||||
[_delegate webViewDidStartLoad:webView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView*)webView
|
||||
{
|
||||
VerboseLog(@"webView didFinishLoad (before). state=%d loadCount=%d", _state, _loadCount);
|
||||
BOOL fireCallback = NO;
|
||||
switch (_state) {
|
||||
case STATE_IDLE:
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_START:
|
||||
NSLog(@"CDVWebViewDelegate: Unexpected didFinish while waiting for load start.");
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_FINISH:
|
||||
if (_loadCount == 1) {
|
||||
fireCallback = YES;
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
_loadCount -= 1;
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_START:
|
||||
[self pollForPageLoadStart:webView];
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
|
||||
[self pollForPageLoadFinish:webView];
|
||||
break;
|
||||
}
|
||||
VerboseLog(@"webView didFinishLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback);
|
||||
if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
|
||||
[_delegate webViewDidFinishLoad:webView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error
|
||||
{
|
||||
VerboseLog(@"webView didFailLoad (before). state=%d loadCount=%d", _state, _loadCount);
|
||||
BOOL fireCallback = NO;
|
||||
|
||||
switch (_state) {
|
||||
case STATE_IDLE:
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_START:
|
||||
if ([error code] == NSURLErrorCancelled) {
|
||||
_state = STATE_CANCELLED;
|
||||
} else {
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
fireCallback = YES;
|
||||
break;
|
||||
|
||||
case STATE_WAITING_FOR_LOAD_FINISH:
|
||||
if ([error code] != NSURLErrorCancelled) {
|
||||
if (_loadCount == 1) {
|
||||
_state = STATE_IDLE;
|
||||
fireCallback = YES;
|
||||
}
|
||||
_loadCount = -1;
|
||||
} else {
|
||||
fireCallback = YES;
|
||||
_state = STATE_CANCELLED;
|
||||
_loadCount -= 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_START:
|
||||
[self pollForPageLoadStart:webView];
|
||||
break;
|
||||
|
||||
case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
|
||||
[self pollForPageLoadFinish:webView];
|
||||
break;
|
||||
}
|
||||
VerboseLog(@"webView didFailLoad (after). state=%d loadCount=%d, fireCallback=%d", _state, _loadCount, fireCallback);
|
||||
if (fireCallback && [_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
|
||||
[_delegate webView:webView didFailLoadWithError:error];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVPlugin.h"
|
||||
#import "CDVWebViewEngineProtocol.h"
|
||||
|
||||
@interface CDVUIWebViewEngine : CDVPlugin <CDVWebViewEngineProtocol>
|
||||
|
||||
@property (nonatomic, strong, readonly) id <UIWebViewDelegate> uiWebViewDelegate;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVUIWebViewEngine.h"
|
||||
#import "CDVUIWebViewDelegate.h"
|
||||
#import "CDVUIWebViewNavigationDelegate.h"
|
||||
#import "NSDictionary+CordovaPreferences.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
@interface CDVUIWebViewEngine ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) UIView* engineWebView;
|
||||
@property (nonatomic, strong, readwrite) id <UIWebViewDelegate> uiWebViewDelegate;
|
||||
@property (nonatomic, strong, readwrite) CDVUIWebViewNavigationDelegate* navWebViewDelegate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CDVUIWebViewEngine
|
||||
|
||||
@synthesize engineWebView = _engineWebView;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.engineWebView = [[UIWebView alloc] initWithFrame:frame];
|
||||
NSLog(@"Using UIWebView");
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)pluginInitialize
|
||||
{
|
||||
// viewController would be available now. we attempt to set all possible delegates to it, by default
|
||||
|
||||
UIWebView* uiWebView = (UIWebView*)_engineWebView;
|
||||
|
||||
if ([self.viewController conformsToProtocol:@protocol(UIWebViewDelegate)]) {
|
||||
self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id <UIWebViewDelegate>)self.viewController];
|
||||
uiWebView.delegate = self.uiWebViewDelegate;
|
||||
} else {
|
||||
self.navWebViewDelegate = [[CDVUIWebViewNavigationDelegate alloc] initWithEnginePlugin:self];
|
||||
self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self.navWebViewDelegate];
|
||||
uiWebView.delegate = self.uiWebViewDelegate;
|
||||
}
|
||||
|
||||
[self updateSettings:self.commandDelegate.settings];
|
||||
}
|
||||
|
||||
- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler
|
||||
{
|
||||
NSString* ret = [(UIWebView*)_engineWebView stringByEvaluatingJavaScriptFromString:javaScriptString];
|
||||
|
||||
if (completionHandler) {
|
||||
completionHandler(ret, nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (id)loadRequest:(NSURLRequest*)request
|
||||
{
|
||||
[(UIWebView*)_engineWebView loadRequest:request];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL
|
||||
{
|
||||
[(UIWebView*)_engineWebView loadHTMLString:string baseURL:baseURL];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSURL*)URL
|
||||
{
|
||||
return [[(UIWebView*)_engineWebView request] URL];
|
||||
}
|
||||
|
||||
- (BOOL) canLoadRequest:(NSURLRequest*)request
|
||||
{
|
||||
return (request != nil);
|
||||
}
|
||||
|
||||
- (void)updateSettings:(NSDictionary*)settings
|
||||
{
|
||||
UIWebView* uiWebView = (UIWebView*)_engineWebView;
|
||||
|
||||
uiWebView.scalesPageToFit = [settings cordovaBoolSettingForKey:@"EnableViewportScale" defaultValue:NO];
|
||||
uiWebView.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:NO];
|
||||
uiWebView.mediaPlaybackRequiresUserAction = [settings cordovaBoolSettingForKey:@"MediaPlaybackRequiresUserAction" defaultValue:YES];
|
||||
uiWebView.mediaPlaybackAllowsAirPlay = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES];
|
||||
uiWebView.keyboardDisplayRequiresUserAction = [settings cordovaBoolSettingForKey:@"KeyboardDisplayRequiresUserAction" defaultValue:YES];
|
||||
uiWebView.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO];
|
||||
uiWebView.gapBetweenPages = [settings cordovaFloatSettingForKey:@"GapBetweenPages" defaultValue:0.0];
|
||||
uiWebView.pageLength = [settings cordovaFloatSettingForKey:@"PageLength" defaultValue:0.0];
|
||||
|
||||
id prefObj = nil;
|
||||
|
||||
// By default, DisallowOverscroll is false (thus bounce is allowed)
|
||||
BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]);
|
||||
|
||||
// prevent webView from bouncing
|
||||
if (!bounceAllowed) {
|
||||
if ([uiWebView respondsToSelector:@selector(scrollView)]) {
|
||||
((UIScrollView*)[uiWebView scrollView]).bounces = NO;
|
||||
} else {
|
||||
for (id subview in self.webView.subviews) {
|
||||
if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
|
||||
((UIScrollView*)subview).bounces = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSString* decelerationSetting = [settings cordovaSettingForKey:@"UIWebViewDecelerationSpeed"];
|
||||
if (![@"fast" isEqualToString:decelerationSetting]) {
|
||||
[uiWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];
|
||||
}
|
||||
|
||||
NSInteger paginationBreakingMode = 0; // default - UIWebPaginationBreakingModePage
|
||||
prefObj = [settings cordovaSettingForKey:@"PaginationBreakingMode"];
|
||||
if (prefObj != nil) {
|
||||
NSArray* validValues = @[@"page", @"column"];
|
||||
NSString* prefValue = [validValues objectAtIndex:0];
|
||||
|
||||
if ([prefObj isKindOfClass:[NSString class]]) {
|
||||
prefValue = prefObj;
|
||||
}
|
||||
|
||||
paginationBreakingMode = [validValues indexOfObject:[prefValue lowercaseString]];
|
||||
if (paginationBreakingMode == NSNotFound) {
|
||||
paginationBreakingMode = 0;
|
||||
}
|
||||
}
|
||||
uiWebView.paginationBreakingMode = paginationBreakingMode;
|
||||
|
||||
NSInteger paginationMode = 0; // default - UIWebPaginationModeUnpaginated
|
||||
prefObj = [settings cordovaSettingForKey:@"PaginationMode"];
|
||||
if (prefObj != nil) {
|
||||
NSArray* validValues = @[@"unpaginated", @"lefttoright", @"toptobottom", @"bottomtotop", @"righttoleft"];
|
||||
NSString* prefValue = [validValues objectAtIndex:0];
|
||||
|
||||
if ([prefObj isKindOfClass:[NSString class]]) {
|
||||
prefValue = prefObj;
|
||||
}
|
||||
|
||||
paginationMode = [validValues indexOfObject:[prefValue lowercaseString]];
|
||||
if (paginationMode == NSNotFound) {
|
||||
paginationMode = 0;
|
||||
}
|
||||
}
|
||||
uiWebView.paginationMode = paginationMode;
|
||||
}
|
||||
|
||||
- (void)updateWithInfo:(NSDictionary*)info
|
||||
{
|
||||
UIWebView* uiWebView = (UIWebView*)_engineWebView;
|
||||
|
||||
id <UIWebViewDelegate> uiWebViewDelegate = [info objectForKey:kCDVWebViewEngineUIWebViewDelegate];
|
||||
NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences];
|
||||
|
||||
if (uiWebViewDelegate &&
|
||||
[uiWebViewDelegate conformsToProtocol:@protocol(UIWebViewDelegate)]) {
|
||||
self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id <UIWebViewDelegate>)self.viewController];
|
||||
uiWebView.delegate = self.uiWebViewDelegate;
|
||||
}
|
||||
|
||||
if (settings && [settings isKindOfClass:[NSDictionary class]]) {
|
||||
[self updateSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
// This forwards the methods that are in the header that are not implemented here.
|
||||
// Both WKWebView and UIWebView implement the below:
|
||||
// loadHTMLString:baseURL:
|
||||
// loadRequest:
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
return _engineWebView;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "CDVUIWebViewEngine.h"
|
||||
|
||||
@interface CDVUIWebViewNavigationDelegate : NSObject <UIWebViewDelegate>
|
||||
|
||||
@property (nonatomic, weak) CDVPlugin* enginePlugin;
|
||||
|
||||
- (instancetype)initWithEnginePlugin:(CDVPlugin*)enginePlugin;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
#import "CDVUIWebViewNavigationDelegate.h"
|
||||
#import <Cordova/CDVViewController.h>
|
||||
#import <Cordova/CDVCommandDelegateImpl.h>
|
||||
#import <Cordova/CDVUserAgentUtil.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
@implementation CDVUIWebViewNavigationDelegate
|
||||
|
||||
- (instancetype)initWithEnginePlugin:(CDVPlugin*)theEnginePlugin
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.enginePlugin = theEnginePlugin;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
When web application loads Add stuff to the DOM, mainly the user-defined settings from the Settings.plist file, and
|
||||
the device's data such as device ID, platform version, etc.
|
||||
*/
|
||||
- (void)webViewDidStartLoad:(UIWebView*)theWebView
|
||||
{
|
||||
NSLog(@"Resetting plugins due to page load.");
|
||||
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
|
||||
|
||||
[vc.commandQueue resetRequestId];
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:self.enginePlugin.webView]];
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the webview finishes loading. This stops the activity view.
|
||||
*/
|
||||
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
|
||||
{
|
||||
NSLog(@"Finished load of: %@", theWebView.request.URL);
|
||||
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
|
||||
|
||||
// It's safe to release the lock even if this is just a sub-frame that's finished loading.
|
||||
[CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
|
||||
|
||||
/*
|
||||
* Hide the Top Activity THROBBER in the Battery Bar
|
||||
*/
|
||||
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:self.enginePlugin.webView]];
|
||||
}
|
||||
|
||||
- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
|
||||
{
|
||||
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
|
||||
|
||||
[CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
|
||||
|
||||
NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
|
||||
NSLog(@"%@", message);
|
||||
|
||||
NSURL* errorUrl = vc.errorURL;
|
||||
if (errorUrl) {
|
||||
errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
|
||||
NSLog(@"%@", [errorUrl absoluteString]);
|
||||
[theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)defaultResourcePolicyForURL:(NSURL*)url
|
||||
{
|
||||
/*
|
||||
* If a URL is being loaded that's a file url, just load it internally
|
||||
*/
|
||||
if ([url isFileURL]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
|
||||
{
|
||||
NSURL* url = [request URL];
|
||||
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
|
||||
|
||||
/*
|
||||
* Execute any commands queued with cordova.exec() on the JS side.
|
||||
* The part of the URL after gap:// is irrelevant.
|
||||
*/
|
||||
if ([[url scheme] isEqualToString:@"gap"]) {
|
||||
[vc.commandQueue fetchCommandsFromJs];
|
||||
// The delegate is called asynchronously in this case, so we don't have to use
|
||||
// flushCommandQueueWithDelayedJs (setTimeout(0)) as we do with hash changes.
|
||||
[vc.commandQueue executePending];
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Give plugins the chance to handle the url
|
||||
*/
|
||||
BOOL anyPluginsResponded = NO;
|
||||
BOOL shouldAllowRequest = NO;
|
||||
|
||||
for (NSString* pluginName in vc.pluginObjects) {
|
||||
CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName];
|
||||
SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
|
||||
if ([plugin respondsToSelector:selector]) {
|
||||
anyPluginsResponded = YES;
|
||||
shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, request, navigationType));
|
||||
if (!shouldAllowRequest) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anyPluginsResponded) {
|
||||
return shouldAllowRequest;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
|
||||
*/
|
||||
BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url];
|
||||
if (shouldAllowNavigation) {
|
||||
return YES;
|
||||
} else {
|
||||
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user