Return-Path: X-Original-To: apmail-cordova-commits-archive@www.apache.org Delivered-To: apmail-cordova-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id DD901EA6D for ; Wed, 20 Feb 2013 18:20:11 +0000 (UTC) Received: (qmail 17189 invoked by uid 500); 20 Feb 2013 18:20:11 -0000 Delivered-To: apmail-cordova-commits-archive@cordova.apache.org Received: (qmail 17170 invoked by uid 500); 20 Feb 2013 18:20:11 -0000 Mailing-List: contact commits-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: callback-dev@cordova.apache.org Delivered-To: mailing list commits@cordova.apache.org Received: (qmail 17163 invoked by uid 99); 20 Feb 2013 18:20:11 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 20 Feb 2013 18:20:11 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 87EE382D403; Wed, 20 Feb 2013 18:20:11 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: agrieve@apache.org To: commits@cordova.apache.org X-Mailer: ASF-Git Admin Mailer Subject: ios commit: [CB-2389] Distinguish top-level from sub-frame navigations. Message-Id: <20130220182011.87EE382D403@tyr.zones.apache.org> Date: Wed, 20 Feb 2013 18:20:11 +0000 (UTC) Updated Branches: refs/heads/master 7d64f654b -> 1bfff2d16 [CB-2389] Distinguish top-level from sub-frame navigations. Project: http://git-wip-us.apache.org/repos/asf/cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-ios/commit/1bfff2d1 Tree: http://git-wip-us.apache.org/repos/asf/cordova-ios/tree/1bfff2d1 Diff: http://git-wip-us.apache.org/repos/asf/cordova-ios/diff/1bfff2d1 Branch: refs/heads/master Commit: 1bfff2d16b18703930afcbb686d698a1f0861f4f Parents: 7d64f65 Author: Andrew Grieve Authored: Wed Feb 20 13:16:39 2013 -0500 Committer: Andrew Grieve Committed: Wed Feb 20 13:16:39 2013 -0500 ---------------------------------------------------------------------- CordovaLib/Classes/CDVViewController.m | 47 ++---- CordovaLib/Classes/CDVWebViewDelegate.h | 37 ++++ CordovaLib/Classes/CDVWebViewDelegate.m | 157 ++++++++++++++++++ CordovaLib/CordovaLib.xcodeproj/project.pbxproj | 8 + 4 files changed, 215 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/1bfff2d1/CordovaLib/Classes/CDVViewController.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVViewController.m b/CordovaLib/Classes/CDVViewController.m index f6da41b..d625424 100644 --- a/CordovaLib/Classes/CDVViewController.m +++ b/CordovaLib/Classes/CDVViewController.m @@ -23,14 +23,13 @@ #import "CDVCommandDelegateImpl.h" #import "CDVConfigParser.h" #import "CDVUserAgentUtil.h" +#import "CDVWebViewDelegate.h" #define degreesToRadian(x) (M_PI * (x) / 180.0) @interface CDVViewController () { NSInteger _userAgentLockToken; - // Used to distinguish an iframe navigation from a top-level one. - NSURL* _topLevelNavigationURL; - BOOL _topLevelNavigationHasStartedLoad; + CDVWebViewDelegate* _webViewDelegate; } @property (nonatomic, readwrite, strong) NSXMLParser* configParser; @@ -463,7 +462,8 @@ [self.view addSubview:self.webView]; [self.view sendSubviewToBack:self.webView]; - self.webView.delegate = self; + _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self]; + self.webView.delegate = _webViewDelegate; // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set [CDVURLProtocol registerViewController:self]; @@ -513,13 +513,9 @@ */ - (void)webViewDidStartLoad:(UIWebView*)theWebView { - // The request of theWebView is not yet set to the new one at this point. - if (!_topLevelNavigationHasStartedLoad) { - _topLevelNavigationHasStartedLoad = YES; - NSLog(@"Started load of %@", _topLevelNavigationURL); - [_commandQueue resetRequestId]; - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:nil]]; - } + NSLog(@"Resetting plugins due to page load."); + [_commandQueue resetRequestId]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:self.webView]]; } /** @@ -527,26 +523,21 @@ */ - (void)webViewDidFinishLoad:(UIWebView*)theWebView { + NSLog(@"Finished load of: %@", theWebView.request.URL); // It's safe to release the lock even if this is just a sub-frame that's finished loading. [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; - if (![_topLevelNavigationURL isEqual:theWebView.request.URL]) { - return; - } - NSLog(@"Finished load of %@", _topLevelNavigationURL); - _topLevelNavigationURL = nil; - - /* - * Hide the Top Activity THROBBER in the Battery Bar - */ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - // The .onNativeReady().fire() will work when cordova.js is already loaded. // The _nativeReady = true; is used when this is run before cordova.js is loaded. NSString* nativeReady = @"try{cordova.require('cordova/channel').onNativeReady.fire();}catch(e){window._nativeReady = true;}"; // Don't use [commandDelegate evalJs] here since it relies on cordova.js being loaded already. [self.webView stringByEvaluatingJavaScriptFromString:nativeReady]; + /* + * Hide the Top Activity THROBBER in the Battery Bar + */ + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + [self processOpenUrl]; [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:nil]]; @@ -563,17 +554,6 @@ { NSURL* url = [request URL]; - // Check if this is just a sub-frame navigation. - BOOL isTopLevelNavigation = [url isEqual:[request mainDocumentURL]]; - - if (isTopLevelNavigation) { - if (_topLevelNavigationURL != nil) { - NSLog(@"Warning: _topLevelNavigationURL was not set to nil in shouldStartLoadWithRequest"); - } - _topLevelNavigationHasStartedLoad = NO; - _topLevelNavigationURL = url; - } - /* * Execute any commands queued with cordova.exec() on the JS side. * The part of the URL after gap:// is irrelevant. @@ -850,7 +830,6 @@ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:CDVPluginHandleOpenURLNotification object:nil]; - self.webView.delegate = nil; self.webView = nil; [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/1bfff2d1/CordovaLib/Classes/CDVWebViewDelegate.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVWebViewDelegate.h b/CordovaLib/Classes/CDVWebViewDelegate.h new file mode 100644 index 0000000..8a89a22 --- /dev/null +++ b/CordovaLib/Classes/CDVWebViewDelegate.h @@ -0,0 +1,37 @@ +/* + 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 + +/** + * 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 CDVWebViewDelegate : NSObject { + __weak NSObject * _delegate; + NSInteger _loadCount; + NSInteger _state; + NSInteger _curLoadToken; +} + +- (id)initWithDelegate:(NSObject *)delegate; + +@end http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/1bfff2d1/CordovaLib/Classes/CDVWebViewDelegate.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVWebViewDelegate.m b/CordovaLib/Classes/CDVWebViewDelegate.m new file mode 100644 index 0000000..9ee8186 --- /dev/null +++ b/CordovaLib/Classes/CDVWebViewDelegate.m @@ -0,0 +1,157 @@ +/* + 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 "CDVWebViewDelegate.h" +#import "CDVAvailability.h" + +typedef enum { + STATE_NORMAL, + STATE_SHOULD_LOAD_MISSING, + STATE_WAITING_FOR_START, + STATE_WAITING_FOR_FINISH +} State; + +@implementation CDVWebViewDelegate + +- (id)initWithDelegate:(NSObject *)delegate +{ + self = [super init]; + if (self != nil) { + _delegate = delegate; + _loadCount = -1; + _state = STATE_NORMAL; + } + return self; +} + +- (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:@"%d", _curLoadToken] isEqualToString:loadToken]; +} + +- (void)setLoadToken:(UIWebView*)webView +{ + _curLoadToken += 1; + [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.__cordovaLoadToken=%d", _curLoadToken]]; +} + +- (void)pollForPageLoadStart:(UIWebView*)webView +{ + if ((_state != STATE_WAITING_FOR_START) && (_state != STATE_SHOULD_LOAD_MISSING)) { + return; + } + if (![self isJsLoadTokenSet:webView]) { + _state = STATE_WAITING_FOR_FINISH; + [self setLoadToken:webView]; + [_delegate webViewDidStartLoad:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)pollForPageLoadFinish:(UIWebView*)webView +{ + if (_state != STATE_WAITING_FOR_FINISH) { + return; + } + if ([self isPageLoaded:webView]) { + _state = STATE_SHOULD_LOAD_MISSING; + [_delegate webViewDidFinishLoad:webView]; + } else { + [self performSelector:@selector(pollForPageLoaded) withObject:webView afterDelay:50]; + } +} + +- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + BOOL shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + + if (shouldLoad) { + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + if (isTopLevelNavigation) { + _loadCount = 0; + _state = STATE_NORMAL; + } + } + return shouldLoad; +} + +- (void)webViewDidStartLoad:(UIWebView*)webView +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 0) { + [_delegate webViewDidStartLoad:webView]; + _loadCount += 1; + } else if (_loadCount > 0) { + _loadCount += 1; + } else if (!IsAtLeastiOSVersion(@"6.0")) { + // If history.go(-1) is used pre-iOS6, the shouldStartLoadWithRequest function is not called. + // Without shouldLoad, we can't distinguish an iframe from a top-level navigation. + // We could try to distinguish using [UIWebView canGoForward], but that's too much complexity, + // and would work only on the first time it was used. + + // Our work-around is to set a JS variable and poll until it disappears (from a naviagtion). + _state = STATE_WAITING_FOR_START; + [self setLoadToken:webView]; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)webViewDidFinishLoad:(UIWebView*)webView +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 1) { + [_delegate webViewDidFinishLoad:webView]; + _loadCount -= 1; + } else if (_loadCount > 1) { + _loadCount -= 1; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 1) { + [_delegate webView:webView didFailLoadWithError:error]; + _loadCount -= 1; + } else if (_loadCount > 1) { + _loadCount -= 1; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +@end http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/1bfff2d1/CordovaLib/CordovaLib.xcodeproj/project.pbxproj ---------------------------------------------------------------------- diff --git a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index bf0d8f7..7855a4f 100644 --- a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = EB96673A16A8970900D86CDF /* CDVUserAgentUtil.m */; }; EBA3557315ABD38C00F4DE24 /* NSArray+Comparisons.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */; settings = {ATTRIBUTES = (Public, ); }; }; EBA3557515ABD38C00F4DE24 /* NSArray+Comparisons.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */; }; + EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */; }; + EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */; }; F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = F858FBC4166009A8007DA594 /* CDVConfigParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F858FBC5166009A8007DA594 /* CDVConfigParser.m */; }; /* End PBXBuildFile section */ @@ -180,6 +182,8 @@ EB96673A16A8970900D86CDF /* CDVUserAgentUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVUserAgentUtil.m; path = Classes/CDVUserAgentUtil.m; sourceTree = ""; }; EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSArray+Comparisons.h"; path = "Classes/NSArray+Comparisons.h"; sourceTree = ""; }; EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+Comparisons.m"; path = "Classes/NSArray+Comparisons.m"; sourceTree = ""; }; + EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVWebViewDelegate.m; path = Classes/CDVWebViewDelegate.m; sourceTree = ""; }; + EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVWebViewDelegate.h; path = Classes/CDVWebViewDelegate.h; sourceTree = ""; }; F858FBC4166009A8007DA594 /* CDVConfigParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVConfigParser.h; path = Classes/CDVConfigParser.h; sourceTree = ""; }; F858FBC5166009A8007DA594 /* CDVConfigParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVConfigParser.m; path = Classes/CDVConfigParser.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -257,6 +261,8 @@ 888700D710922F56009987E8 /* Commands */ = { isa = PBXGroup; children = ( + EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */, + EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */, 30C5F1DD15AF9E950052A00D /* CDVDevice.h */, 30C5F1DE15AF9E950052A00D /* CDVDevice.m */, 301F2F2914F3C9CA003FE9FC /* CDV.h */, @@ -400,6 +406,7 @@ F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */, 30F3930B169F839700B22307 /* CDVJSON.h in Headers */, EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */, + EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -494,6 +501,7 @@ F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */, 30F3930C169F839700B22307 /* CDVJSON.m in Sources */, EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */, + EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };