added native iOS code
This commit is contained in:
parent
43692d237c
commit
cc14974811
82
src/ios/CDVInAppBrowser.h
Normal file
82
src/ios/CDVInAppBrowser.h
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
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 "CDVInvokedUrlCommand.h"
|
||||||
|
#import "CDVScreenOrientationDelegate.h"
|
||||||
|
#import "CDVWebViewDelegate.h"
|
||||||
|
|
||||||
|
@class CDVInAppBrowserViewController;
|
||||||
|
|
||||||
|
@interface CDVInAppBrowser : CDVPlugin {
|
||||||
|
BOOL _injectedIframeBridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
|
||||||
|
@property (nonatomic, copy) NSString* callbackId;
|
||||||
|
|
||||||
|
- (void)open:(CDVInvokedUrlCommand*)command;
|
||||||
|
- (void)close:(CDVInvokedUrlCommand*)command;
|
||||||
|
- (void)injectScriptCode:(CDVInvokedUrlCommand*)command;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{
|
||||||
|
@private
|
||||||
|
NSString* _userAgent;
|
||||||
|
NSString* _prevUserAgent;
|
||||||
|
NSInteger _userAgentLockToken;
|
||||||
|
CDVWebViewDelegate* _webViewDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (nonatomic, strong) IBOutlet UIWebView* webView;
|
||||||
|
@property (nonatomic, strong) IBOutlet UIBarButtonItem* closeButton;
|
||||||
|
@property (nonatomic, strong) IBOutlet UILabel* addressLabel;
|
||||||
|
@property (nonatomic, strong) IBOutlet UIBarButtonItem* backButton;
|
||||||
|
@property (nonatomic, strong) IBOutlet UIBarButtonItem* forwardButton;
|
||||||
|
@property (nonatomic, strong) IBOutlet UIActivityIndicatorView* spinner;
|
||||||
|
@property (nonatomic, strong) IBOutlet UIToolbar* toolbar;
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate;
|
||||||
|
@property (nonatomic, weak) CDVInAppBrowser* navigationDelegate;
|
||||||
|
@property (nonatomic) NSURL* currentURL;
|
||||||
|
|
||||||
|
- (void)close;
|
||||||
|
- (void)navigateTo:(NSURL*)url;
|
||||||
|
- (void)showLocationBar:(BOOL)show;
|
||||||
|
|
||||||
|
- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface CDVInAppBrowserOptions : NSObject {}
|
||||||
|
|
||||||
|
@property (nonatomic, assign) BOOL location;
|
||||||
|
@property (nonatomic, copy) NSString* presentationstyle;
|
||||||
|
@property (nonatomic, copy) NSString* transitionstyle;
|
||||||
|
|
||||||
|
@property (nonatomic, assign) BOOL enableviewportscale;
|
||||||
|
@property (nonatomic, assign) BOOL mediaplaybackrequiresuseraction;
|
||||||
|
@property (nonatomic, assign) BOOL allowinlinemediaplayback;
|
||||||
|
@property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction;
|
||||||
|
@property (nonatomic, assign) BOOL suppressesincrementalrendering;
|
||||||
|
|
||||||
|
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;
|
||||||
|
|
||||||
|
@end
|
705
src/ios/CDVInAppBrowser.m
Normal file
705
src/ios/CDVInAppBrowser.m
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
/*
|
||||||
|
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 "CDVInAppBrowser.h"
|
||||||
|
#import "CDVPluginResult.h"
|
||||||
|
#import "CDVUserAgentUtil.h"
|
||||||
|
#import "CDVJSON.h"
|
||||||
|
|
||||||
|
#define kInAppBrowserTargetSelf @"_self"
|
||||||
|
#define kInAppBrowserTargetSystem @"_system"
|
||||||
|
#define kInAppBrowserTargetBlank @"_blank"
|
||||||
|
|
||||||
|
#define TOOLBAR_HEIGHT 44.0
|
||||||
|
#define LOCATIONBAR_HEIGHT 21.0
|
||||||
|
#define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT))
|
||||||
|
|
||||||
|
#pragma mark CDVInAppBrowser
|
||||||
|
|
||||||
|
@implementation CDVInAppBrowser
|
||||||
|
|
||||||
|
- (CDVInAppBrowser*)initWithWebView:(UIWebView*)theWebView
|
||||||
|
{
|
||||||
|
self = [super initWithWebView:theWebView];
|
||||||
|
if (self != nil) {
|
||||||
|
// your initialization here
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onReset
|
||||||
|
{
|
||||||
|
[self close:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)close:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
if (self.inAppBrowserViewController != nil) {
|
||||||
|
[self.inAppBrowserViewController close];
|
||||||
|
self.inAppBrowserViewController = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.callbackId = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)open:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
CDVPluginResult* pluginResult;
|
||||||
|
|
||||||
|
NSString* url = [command argumentAtIndex:0];
|
||||||
|
NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf];
|
||||||
|
NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]];
|
||||||
|
|
||||||
|
self.callbackId = command.callbackId;
|
||||||
|
|
||||||
|
if (url != nil) {
|
||||||
|
NSURL* baseUrl = [self.webView.request URL];
|
||||||
|
NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL];
|
||||||
|
if ([target isEqualToString:kInAppBrowserTargetSelf]) {
|
||||||
|
[self openInCordovaWebView:absoluteUrl withOptions:options];
|
||||||
|
} else if ([target isEqualToString:kInAppBrowserTargetSystem]) {
|
||||||
|
[self openInSystem:absoluteUrl];
|
||||||
|
} else { // _blank or anything else
|
||||||
|
[self openInInAppBrowser:absoluteUrl withOptions:options];
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
||||||
|
} else {
|
||||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
|
||||||
|
{
|
||||||
|
if (self.inAppBrowserViewController == nil) {
|
||||||
|
NSString* originalUA = [CDVUserAgentUtil originalUserAgent];
|
||||||
|
self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA prevUserAgent:[self.commandDelegate userAgent]];
|
||||||
|
self.inAppBrowserViewController.navigationDelegate = self;
|
||||||
|
|
||||||
|
if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) {
|
||||||
|
self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
|
||||||
|
[self.inAppBrowserViewController showLocationBar:browserOptions.location];
|
||||||
|
|
||||||
|
// Set Presentation Style
|
||||||
|
UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
|
||||||
|
if (browserOptions.presentationstyle != nil) {
|
||||||
|
if ([browserOptions.presentationstyle isEqualToString:@"pagesheet"]) {
|
||||||
|
presentationStyle = UIModalPresentationPageSheet;
|
||||||
|
} else if ([browserOptions.presentationstyle isEqualToString:@"formsheet"]) {
|
||||||
|
presentationStyle = UIModalPresentationFormSheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.inAppBrowserViewController.modalPresentationStyle = presentationStyle;
|
||||||
|
|
||||||
|
// Set Transition Style
|
||||||
|
UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
|
||||||
|
if (browserOptions.transitionstyle != nil) {
|
||||||
|
if ([browserOptions.transitionstyle isEqualToString:@"fliphorizontal"]) {
|
||||||
|
transitionStyle = UIModalTransitionStyleFlipHorizontal;
|
||||||
|
} else if ([browserOptions.transitionstyle isEqualToString:@"crossdissolve"]) {
|
||||||
|
transitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
|
||||||
|
|
||||||
|
// UIWebView options
|
||||||
|
self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
|
||||||
|
self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
|
||||||
|
self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback;
|
||||||
|
if (IsAtLeastiOSVersion(@"6.0")) {
|
||||||
|
self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
|
||||||
|
self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.viewController.modalViewController != self.inAppBrowserViewController) {
|
||||||
|
[self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
|
||||||
|
}
|
||||||
|
[self.inAppBrowserViewController navigateTo:url];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
|
||||||
|
{
|
||||||
|
if ([self.commandDelegate URLIsWhitelisted:url]) {
|
||||||
|
NSURLRequest* request = [NSURLRequest requestWithURL:url];
|
||||||
|
[self.webView loadRequest:request];
|
||||||
|
} else { // this assumes the InAppBrowser can be excepted from the white-list
|
||||||
|
[self openInInAppBrowser:url withOptions:options];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openInSystem:(NSURL*)url
|
||||||
|
{
|
||||||
|
if ([[UIApplication sharedApplication] canOpenURL:url]) {
|
||||||
|
[[UIApplication sharedApplication] openURL:url];
|
||||||
|
} else { // handle any custom schemes to plugins
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a helper method for the inject{Script|Style}{Code|File} API calls, which
|
||||||
|
// provides a consistent method for injecting JavaScript code into the document.
|
||||||
|
//
|
||||||
|
// If a wrapper string is supplied, then the source string will be JSON-encoded (adding
|
||||||
|
// quotes) and wrapped using string formatting. (The wrapper string should have a single
|
||||||
|
// '%@' marker).
|
||||||
|
//
|
||||||
|
// If no wrapper is supplied, then the source string is executed directly.
|
||||||
|
|
||||||
|
- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper
|
||||||
|
{
|
||||||
|
if (!_injectedIframeBridge) {
|
||||||
|
_injectedIframeBridge = YES;
|
||||||
|
// Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController
|
||||||
|
[self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e = _cdvIframeBridge = d.createElement('iframe');e.style.display='none';d.body.appendChild(e);})(document)"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsWrapper != nil) {
|
||||||
|
NSString* sourceArrayString = [@[source] JSONString];
|
||||||
|
if (sourceArrayString) {
|
||||||
|
NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)];
|
||||||
|
NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString];
|
||||||
|
[self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)injectScriptCode:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
NSString* jsWrapper = nil;
|
||||||
|
|
||||||
|
if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
|
||||||
|
jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+window.escape(JSON.stringify([eval(%%@)]));", command.callbackId];
|
||||||
|
}
|
||||||
|
[self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)injectScriptFile:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
NSString* jsWrapper;
|
||||||
|
|
||||||
|
if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
|
||||||
|
jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
|
||||||
|
} else {
|
||||||
|
jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)";
|
||||||
|
}
|
||||||
|
[self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)injectStyleCode:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
NSString* jsWrapper;
|
||||||
|
|
||||||
|
if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
|
||||||
|
jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
|
||||||
|
} else {
|
||||||
|
jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)";
|
||||||
|
}
|
||||||
|
[self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)injectStyleFile:(CDVInvokedUrlCommand*)command
|
||||||
|
{
|
||||||
|
NSString* jsWrapper;
|
||||||
|
|
||||||
|
if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
|
||||||
|
jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
|
||||||
|
} else {
|
||||||
|
jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)";
|
||||||
|
}
|
||||||
|
[self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging
|
||||||
|
* to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no
|
||||||
|
* other code execution is possible.
|
||||||
|
*
|
||||||
|
* To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form:
|
||||||
|
*
|
||||||
|
* gap-iab://<callbackId>/<arguments>
|
||||||
|
*
|
||||||
|
* where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789")
|
||||||
|
*
|
||||||
|
* If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded
|
||||||
|
* value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION
|
||||||
|
* is returned if the JSON is invalid.
|
||||||
|
*/
|
||||||
|
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
|
||||||
|
{
|
||||||
|
NSURL* url = request.URL;
|
||||||
|
BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
|
||||||
|
|
||||||
|
// See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
|
||||||
|
// and the path, if present, should be a JSON-encoded value to pass to the callback.
|
||||||
|
if ([[url scheme] isEqualToString:@"gap-iab"]) {
|
||||||
|
NSString* scriptCallbackId = [url host];
|
||||||
|
CDVPluginResult* pluginResult = nil;
|
||||||
|
|
||||||
|
if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) {
|
||||||
|
NSString* scriptResult = [url path];
|
||||||
|
NSError* __autoreleasing error = nil;
|
||||||
|
|
||||||
|
// The message should be a JSON-encoded array of the result of the script which executed.
|
||||||
|
if ((scriptResult != nil) && ([scriptResult length] > 1)) {
|
||||||
|
scriptResult = [scriptResult substringFromIndex:1];
|
||||||
|
NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
|
||||||
|
if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
|
||||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
|
||||||
|
} else {
|
||||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
|
||||||
|
}
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
} else if ((self.callbackId != nil) && isTopLevelNavigation) {
|
||||||
|
// Send a loadstart event for each top-level navigation (includes redirects).
|
||||||
|
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
||||||
|
messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}];
|
||||||
|
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
|
||||||
|
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)webViewDidStartLoad:(UIWebView*)theWebView
|
||||||
|
{
|
||||||
|
_injectedIframeBridge = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
|
||||||
|
{
|
||||||
|
if (self.callbackId != nil) {
|
||||||
|
// TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected).
|
||||||
|
NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
|
||||||
|
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
||||||
|
messageAsDictionary:@{@"type":@"loadstop", @"url":url}];
|
||||||
|
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
|
||||||
|
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
|
||||||
|
{
|
||||||
|
if (self.callbackId != nil) {
|
||||||
|
NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
|
||||||
|
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
||||||
|
messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInt:error.code], @"message": error.localizedDescription}];
|
||||||
|
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
|
||||||
|
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)browserExit
|
||||||
|
{
|
||||||
|
if (self.callbackId != nil) {
|
||||||
|
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
||||||
|
messageAsDictionary:@{@"type":@"exit"}];
|
||||||
|
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
|
||||||
|
|
||||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
|
||||||
|
}
|
||||||
|
// Don't recycle the ViewController since it may be consuming a lot of memory.
|
||||||
|
// Also - this is required for the PDF/User-Agent bug work-around.
|
||||||
|
self.inAppBrowserViewController = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark CDVInAppBrowserViewController
|
||||||
|
|
||||||
|
@implementation CDVInAppBrowserViewController
|
||||||
|
|
||||||
|
@synthesize currentURL;
|
||||||
|
|
||||||
|
- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (self != nil) {
|
||||||
|
_userAgent = userAgent;
|
||||||
|
_prevUserAgent = prevUserAgent;
|
||||||
|
_webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self];
|
||||||
|
[self createViews];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)createViews
|
||||||
|
{
|
||||||
|
// We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included
|
||||||
|
|
||||||
|
CGRect webViewBounds = self.view.bounds;
|
||||||
|
|
||||||
|
webViewBounds.size.height -= FOOTER_HEIGHT;
|
||||||
|
|
||||||
|
self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
|
||||||
|
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
|
||||||
|
|
||||||
|
[self.view addSubview:self.webView];
|
||||||
|
[self.view sendSubviewToBack:self.webView];
|
||||||
|
|
||||||
|
self.webView.delegate = _webViewDelegate;
|
||||||
|
self.webView.backgroundColor = [UIColor whiteColor];
|
||||||
|
|
||||||
|
self.webView.clearsContextBeforeDrawing = YES;
|
||||||
|
self.webView.clipsToBounds = YES;
|
||||||
|
self.webView.contentMode = UIViewContentModeScaleToFill;
|
||||||
|
self.webView.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
|
||||||
|
self.webView.multipleTouchEnabled = YES;
|
||||||
|
self.webView.opaque = YES;
|
||||||
|
self.webView.scalesPageToFit = NO;
|
||||||
|
self.webView.userInteractionEnabled = YES;
|
||||||
|
|
||||||
|
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||||
|
self.spinner.alpha = 1.000;
|
||||||
|
self.spinner.autoresizesSubviews = YES;
|
||||||
|
self.spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
|
||||||
|
self.spinner.clearsContextBeforeDrawing = NO;
|
||||||
|
self.spinner.clipsToBounds = NO;
|
||||||
|
self.spinner.contentMode = UIViewContentModeScaleToFill;
|
||||||
|
self.spinner.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
|
||||||
|
self.spinner.frame = CGRectMake(454.0, 231.0, 20.0, 20.0);
|
||||||
|
self.spinner.hidden = YES;
|
||||||
|
self.spinner.hidesWhenStopped = YES;
|
||||||
|
self.spinner.multipleTouchEnabled = NO;
|
||||||
|
self.spinner.opaque = NO;
|
||||||
|
self.spinner.userInteractionEnabled = NO;
|
||||||
|
[self.spinner stopAnimating];
|
||||||
|
|
||||||
|
self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
|
||||||
|
self.closeButton.enabled = YES;
|
||||||
|
self.closeButton.imageInsets = UIEdgeInsetsZero;
|
||||||
|
self.closeButton.style = UIBarButtonItemStylePlain;
|
||||||
|
self.closeButton.width = 32.000;
|
||||||
|
|
||||||
|
UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
|
||||||
|
|
||||||
|
UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
|
||||||
|
fixedSpaceButton.width = 20;
|
||||||
|
|
||||||
|
self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, (self.view.bounds.size.height - TOOLBAR_HEIGHT), self.view.bounds.size.width, TOOLBAR_HEIGHT)];
|
||||||
|
self.toolbar.alpha = 1.000;
|
||||||
|
self.toolbar.autoresizesSubviews = YES;
|
||||||
|
self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||||
|
self.toolbar.barStyle = UIBarStyleBlackOpaque;
|
||||||
|
self.toolbar.clearsContextBeforeDrawing = NO;
|
||||||
|
self.toolbar.clipsToBounds = NO;
|
||||||
|
self.toolbar.contentMode = UIViewContentModeScaleToFill;
|
||||||
|
self.toolbar.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
|
||||||
|
self.toolbar.hidden = NO;
|
||||||
|
self.toolbar.multipleTouchEnabled = NO;
|
||||||
|
self.toolbar.opaque = NO;
|
||||||
|
self.toolbar.userInteractionEnabled = YES;
|
||||||
|
|
||||||
|
CGFloat labelInset = 5.0;
|
||||||
|
self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, (self.view.bounds.size.height - FOOTER_HEIGHT), self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)];
|
||||||
|
self.addressLabel.adjustsFontSizeToFitWidth = NO;
|
||||||
|
self.addressLabel.alpha = 1.000;
|
||||||
|
self.addressLabel.autoresizesSubviews = YES;
|
||||||
|
self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
|
||||||
|
self.addressLabel.backgroundColor = [UIColor clearColor];
|
||||||
|
self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
|
||||||
|
self.addressLabel.clearsContextBeforeDrawing = YES;
|
||||||
|
self.addressLabel.clipsToBounds = YES;
|
||||||
|
self.addressLabel.contentMode = UIViewContentModeScaleToFill;
|
||||||
|
self.addressLabel.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
|
||||||
|
self.addressLabel.enabled = YES;
|
||||||
|
self.addressLabel.hidden = NO;
|
||||||
|
self.addressLabel.lineBreakMode = UILineBreakModeTailTruncation;
|
||||||
|
self.addressLabel.minimumFontSize = 10.000;
|
||||||
|
self.addressLabel.multipleTouchEnabled = NO;
|
||||||
|
self.addressLabel.numberOfLines = 1;
|
||||||
|
self.addressLabel.opaque = NO;
|
||||||
|
self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0);
|
||||||
|
self.addressLabel.text = @"Loading...";
|
||||||
|
self.addressLabel.textAlignment = UITextAlignmentLeft;
|
||||||
|
self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000];
|
||||||
|
self.addressLabel.userInteractionEnabled = NO;
|
||||||
|
|
||||||
|
NSString* frontArrowString = @"►"; // create arrow from Unicode char
|
||||||
|
self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)];
|
||||||
|
self.forwardButton.enabled = YES;
|
||||||
|
self.forwardButton.imageInsets = UIEdgeInsetsZero;
|
||||||
|
|
||||||
|
NSString* backArrowString = @"◄"; // create arrow from Unicode char
|
||||||
|
self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)];
|
||||||
|
self.backButton.enabled = YES;
|
||||||
|
self.backButton.imageInsets = UIEdgeInsetsZero;
|
||||||
|
|
||||||
|
[self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]];
|
||||||
|
|
||||||
|
self.view.backgroundColor = [UIColor grayColor];
|
||||||
|
[self.view addSubview:self.toolbar];
|
||||||
|
[self.view addSubview:self.addressLabel];
|
||||||
|
[self.view addSubview:self.spinner];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showLocationBar:(BOOL)show
|
||||||
|
{
|
||||||
|
CGRect addressLabelFrame = self.addressLabel.frame;
|
||||||
|
BOOL locationBarVisible = (addressLabelFrame.size.height > 0);
|
||||||
|
|
||||||
|
// prevent double show/hide
|
||||||
|
if (locationBarVisible == show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
CGRect webViewBounds = self.view.bounds;
|
||||||
|
webViewBounds.size.height -= FOOTER_HEIGHT;
|
||||||
|
self.webView.frame = webViewBounds;
|
||||||
|
|
||||||
|
CGRect addressLabelFrame = self.addressLabel.frame;
|
||||||
|
addressLabelFrame.size.height = LOCATIONBAR_HEIGHT;
|
||||||
|
self.addressLabel.frame = addressLabelFrame;
|
||||||
|
} else {
|
||||||
|
CGRect webViewBounds = self.view.bounds;
|
||||||
|
webViewBounds.size.height -= TOOLBAR_HEIGHT;
|
||||||
|
self.webView.frame = webViewBounds;
|
||||||
|
|
||||||
|
CGRect addressLabelFrame = self.addressLabel.frame;
|
||||||
|
addressLabelFrame.size.height = 0;
|
||||||
|
self.addressLabel.frame = addressLabelFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad
|
||||||
|
{
|
||||||
|
[super viewDidLoad];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidUnload
|
||||||
|
{
|
||||||
|
[self.webView loadHTMLString:nil baseURL:nil];
|
||||||
|
[CDVUserAgentUtil releaseLock:&_userAgentLockToken];
|
||||||
|
[super viewDidUnload];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)close
|
||||||
|
{
|
||||||
|
[CDVUserAgentUtil releaseLock:&_userAgentLockToken];
|
||||||
|
|
||||||
|
if ([self respondsToSelector:@selector(presentingViewController)]) {
|
||||||
|
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||||
|
} else {
|
||||||
|
[[self parentViewController] dismissModalViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.currentURL = nil;
|
||||||
|
|
||||||
|
if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
|
||||||
|
[self.navigationDelegate browserExit];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)navigateTo:(NSURL*)url
|
||||||
|
{
|
||||||
|
NSURLRequest* request = [NSURLRequest requestWithURL:url];
|
||||||
|
|
||||||
|
if (_userAgentLockToken != 0) {
|
||||||
|
[self.webView loadRequest:request];
|
||||||
|
} else {
|
||||||
|
[CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
|
||||||
|
_userAgentLockToken = lockToken;
|
||||||
|
[CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken];
|
||||||
|
[self.webView loadRequest:request];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)goBack:(id)sender
|
||||||
|
{
|
||||||
|
[self.webView goBack];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)goForward:(id)sender
|
||||||
|
{
|
||||||
|
[self.webView goForward];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark UIWebViewDelegate
|
||||||
|
|
||||||
|
- (void)webViewDidStartLoad:(UIWebView*)theWebView
|
||||||
|
{
|
||||||
|
// loading url, start spinner, update back/forward
|
||||||
|
|
||||||
|
self.addressLabel.text = @"Loading...";
|
||||||
|
self.backButton.enabled = theWebView.canGoBack;
|
||||||
|
self.forwardButton.enabled = theWebView.canGoForward;
|
||||||
|
|
||||||
|
[self.spinner startAnimating];
|
||||||
|
|
||||||
|
return [self.navigationDelegate webViewDidStartLoad:theWebView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
|
||||||
|
{
|
||||||
|
BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
|
||||||
|
|
||||||
|
if (isTopLevelNavigation) {
|
||||||
|
self.currentURL = request.URL;
|
||||||
|
}
|
||||||
|
return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
|
||||||
|
{
|
||||||
|
// update url, stop spinner, update back/forward
|
||||||
|
|
||||||
|
self.addressLabel.text = [self.currentURL absoluteString];
|
||||||
|
self.backButton.enabled = theWebView.canGoBack;
|
||||||
|
self.forwardButton.enabled = theWebView.canGoForward;
|
||||||
|
|
||||||
|
[self.spinner stopAnimating];
|
||||||
|
|
||||||
|
// Work around a bug where the first time a PDF is opened, all UIWebViews
|
||||||
|
// reload their User-Agent from NSUserDefaults.
|
||||||
|
// This work-around makes the following assumptions:
|
||||||
|
// 1. The app has only a single Cordova Webview. If not, then the app should
|
||||||
|
// take it upon themselves to load a PDF in the background as a part of
|
||||||
|
// their start-up flow.
|
||||||
|
// 2. That the PDF does not require any additional network requests. We change
|
||||||
|
// the user-agent here back to that of the CDVViewController, so requests
|
||||||
|
// from it must pass through its white-list. This *does* break PDFs that
|
||||||
|
// contain links to other remote PDF/websites.
|
||||||
|
// More info at https://issues.apache.org/jira/browse/CB-2225
|
||||||
|
BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
|
||||||
|
if (isPDF) {
|
||||||
|
[CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.navigationDelegate webViewDidFinishLoad:theWebView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
|
||||||
|
{
|
||||||
|
// log fail message, stop spinner, update back/forward
|
||||||
|
NSLog(@"webView:didFailLoadWithError - %@", [error localizedDescription]);
|
||||||
|
|
||||||
|
self.backButton.enabled = theWebView.canGoBack;
|
||||||
|
self.forwardButton.enabled = theWebView.canGoForward;
|
||||||
|
[self.spinner stopAnimating];
|
||||||
|
|
||||||
|
self.addressLabel.text = @"Load Error";
|
||||||
|
|
||||||
|
[self.navigationDelegate webView:theWebView didFailLoadWithError:error];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark CDVScreenOrientationDelegate
|
||||||
|
|
||||||
|
- (BOOL)shouldAutorotate
|
||||||
|
{
|
||||||
|
if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
|
||||||
|
return [self.orientationDelegate shouldAutorotate];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)supportedInterfaceOrientations
|
||||||
|
{
|
||||||
|
if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
|
||||||
|
return [self.orientationDelegate supportedInterfaceOrientations];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 << UIInterfaceOrientationPortrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
|
||||||
|
{
|
||||||
|
if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
|
||||||
|
return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
|
||||||
|
}
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation CDVInAppBrowserOptions
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if (self = [super init]) {
|
||||||
|
// default values
|
||||||
|
self.location = YES;
|
||||||
|
|
||||||
|
self.enableviewportscale = NO;
|
||||||
|
self.mediaplaybackrequiresuseraction = NO;
|
||||||
|
self.allowinlinemediaplayback = NO;
|
||||||
|
self.keyboarddisplayrequiresuseraction = YES;
|
||||||
|
self.suppressesincrementalrendering = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options
|
||||||
|
{
|
||||||
|
CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init];
|
||||||
|
|
||||||
|
// NOTE: this parsing does not handle quotes within values
|
||||||
|
NSArray* pairs = [options componentsSeparatedByString:@","];
|
||||||
|
|
||||||
|
// parse keys and values, set the properties
|
||||||
|
for (NSString* pair in pairs) {
|
||||||
|
NSArray* keyvalue = [pair componentsSeparatedByString:@"="];
|
||||||
|
|
||||||
|
if ([keyvalue count] == 2) {
|
||||||
|
NSString* key = [[keyvalue objectAtIndex:0] lowercaseString];
|
||||||
|
NSString* value = [[keyvalue objectAtIndex:1] lowercaseString];
|
||||||
|
|
||||||
|
BOOL isBoolean = [value isEqualToString:@"yes"] || [value isEqualToString:@"no"];
|
||||||
|
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
|
||||||
|
[numberFormatter setAllowsFloats:YES];
|
||||||
|
BOOL isNumber = [numberFormatter numberFromString:value] != nil;
|
||||||
|
|
||||||
|
// set the property according to the key name
|
||||||
|
if ([obj respondsToSelector:NSSelectorFromString(key)]) {
|
||||||
|
if (isNumber) {
|
||||||
|
[obj setValue:[numberFormatter numberFromString:value] forKey:key];
|
||||||
|
} else if (isBoolean) {
|
||||||
|
[obj setValue:[NSNumber numberWithBool:[value isEqualToString:@"yes"]] forKey:key];
|
||||||
|
} else {
|
||||||
|
[obj setValue:value forKey:key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in New Issue
Block a user