diff options
author | Jay Freeman (saurik) <saurik@saurik.com> | 2009-06-16 07:06:48 +0000 |
---|---|---|
committer | Jay Freeman (saurik) <saurik@saurik.com> | 2010-09-30 07:13:15 +0000 |
commit | 43f3d7f667a56659b4ac4b2e8171fbf8b7cc3c94 (patch) | |
tree | 72ccb2249620bfcd9141772276a5a9025b069a96 /UICaboodle/BrowserView.mm | |
parent | 0dceb29b5b709a6f9cfea52648941a6c2c5ef46b (diff) |
We prefer this factoring.
Diffstat (limited to 'UICaboodle/BrowserView.mm')
-rw-r--r-- | UICaboodle/BrowserView.mm | 1403 |
1 files changed, 1403 insertions, 0 deletions
diff --git a/UICaboodle/BrowserView.mm b/UICaboodle/BrowserView.mm new file mode 100644 index 0000000..6f6081e --- /dev/null +++ b/UICaboodle/BrowserView.mm @@ -0,0 +1,1403 @@ +#include <BrowserView.h> +#include <UCLocalize.h> + +#import <QuartzCore/CALayer.h> +// XXX: fix the minimum requirement +extern NSString * const kCAFilterNearest; + +#include <WebCore/WebCoreThread.h> + +#include "substrate.h" + +@interface NSString (UIKit) +- (NSString *) stringByAddingPercentEscapes; +@end + +/* Indirect Delegate {{{ */ +@interface IndirectDelegate : NSObject { + _transient volatile id delegate_; +} + +- (void) setDelegate:(id)delegate; +- (id) initWithDelegate:(id)delegate; +@end + +@implementation IndirectDelegate + +- (void) setDelegate:(id)delegate { + delegate_ = delegate; +} + +- (id) initWithDelegate:(id)delegate { + delegate_ = delegate; + return self; +} + +- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didClearWindowObject:window forFrame:frame]; +} + +- (void) webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didCommitLoadForFrame:frame]; +} + +- (void) webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didFailLoadWithError:error forFrame:frame]; +} + +- (void) webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didFailProvisionalLoadWithError:error forFrame:frame]; +} + +- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didFinishLoadForFrame:frame]; +} + +- (void) webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didReceiveTitle:title forFrame:frame]; +} + +- (void) webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame { + if (delegate_ != nil) + return [delegate_ webView:sender didStartProvisionalLoadForFrame:frame]; +} + +- (void) webView:(WebView *)sender resource:(id)identifier didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge fromDataSource:(WebDataSource *)source { + if (delegate_ != nil) + return [delegate_ webView:sender resource:identifier didReceiveAuthenticationChallenge:challenge fromDataSource:source]; +} + +- (NSURLRequest *) webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)source { + if (delegate_ != nil) + return [delegate_ webView:sender resource:identifier willSendRequest:request redirectResponse:redirectResponse fromDataSource:source]; + return nil; +} + +- (IMP) methodForSelector:(SEL)sel { + if (IMP method = [super methodForSelector:sel]) + return method; + fprintf(stderr, "methodForSelector:[%s] == NULL\n", sel_getName(sel)); + return NULL; +} + +- (BOOL) respondsToSelector:(SEL)sel { + if ([super respondsToSelector:sel]) + return YES; + // XXX: WebThreadCreateNSInvocation returns nil + //fprintf(stderr, "[%s]R?%s\n", class_getName(self->isa), sel_getName(sel)); + return delegate_ == nil ? NO : [delegate_ respondsToSelector:sel]; +} + +- (NSMethodSignature *) methodSignatureForSelector:(SEL)sel { + if (NSMethodSignature *method = [super methodSignatureForSelector:sel]) + return method; + //fprintf(stderr, "[%s]S?%s\n", class_getName(self->isa), sel_getName(sel)); + if (delegate_ != nil) + if (NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel]) + return sig; + // XXX: I fucking hate Apple so very very bad + return [NSMethodSignature signatureWithObjCTypes:"v@:"]; +} + +- (void) forwardInvocation:(NSInvocation *)inv { + SEL sel = [inv selector]; + if (delegate_ != nil && [delegate_ respondsToSelector:sel]) + [inv invokeWithTarget:delegate_]; +} + +@end +/* }}} */ + +@interface WebView (UICaboodle) +- (void) setScriptDebugDelegate:(id)delegate; +- (void) _setFormDelegate:(id)delegate; +- (void) _setUIKitDelegate:(id)delegate; +- (void) setWebMailDelegate:(id)delegate; +- (void) _setLayoutInterval:(float)interval; +@end + +@implementation WebScriptObject (UICaboodle) + +- (unsigned) count { + id length([self valueForKey:@"length"]); + if ([length respondsToSelector:@selector(intValue)]) + return [length intValue]; + else + return 0; +} + +- (id) objectAtIndex:(unsigned)index { + return [self webScriptValueAtIndex:index]; +} + +@end + +#if 0 +/* Mail Composition {{{ */ +@interface MailToView : PopUpView { + MailComposeController *controller_; +} + +- (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url; + +@end + +@implementation MailToView + +- (void) dealloc { + [controller_ release]; + [super dealloc]; +} + +- (void) mailComposeControllerWillAttemptToSend:(MailComposeController *)controller { + NSLog(@"will"); +} + +- (void) mailComposeControllerDidAttemptToSend:(MailComposeController *)controller mailDelivery:(id)delivery { + NSLog(@"did:%@", delivery); +// [UIApp setStatusBarShowsProgress:NO]; +if ([controller error]){ +NSArray *buttons = [NSArray arrayWithObjects:UCLocalize("OK"), nil]; +UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:UCLocalize("ERROR") buttons:buttons defaultButtonIndex:0 delegate:self context:self]; +[mailAlertSheet setBodyText:[controller error]]; +[mailAlertSheet popupAlertAnimated:YES]; +} +} + +- (void) showError { + NSLog(@"%@", [controller_ error]); + NSArray *buttons = [NSArray arrayWithObjects:UCLocalize("OK"), nil]; + UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:UCLocalize("ERROR") buttons:buttons defaultButtonIndex:0 delegate:self context:self]; + [mailAlertSheet setBodyText:[controller_ error]]; + [mailAlertSheet popupAlertAnimated:YES]; +} + +- (void) deliverMessage { _pooled + setuid(501); + setgid(501); + + if (![controller_ deliverMessage]) + [self performSelectorOnMainThread:@selector(showError) withObject:nil waitUntilDone:NO]; +} + +- (void) mailComposeControllerCompositionFinished:(MailComposeController *)controller { + if ([controller_ needsDelivery]) + [NSThread detachNewThreadSelector:@selector(deliverMessage) toTarget:self withObject:nil]; + else + [self cancel]; +} + +- (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url { + if ((self = [super initWithView:view delegate:delegate]) != nil) { + controller_ = [[MailComposeController alloc] initForContentSize:[overlay_ bounds].size]; + [controller_ setDelegate:self]; + [controller_ initializeUI]; + [controller_ setupForURL:url]; + + UIView *view([controller_ view]); + [overlay_ addSubview:view]; + } return self; +} + +@end +/* }}} */ +#endif + +@implementation BrowserView + +#if ShowInternals +#include "Internals.h" +#endif + +- (void) dealloc { +#if LogBrowser + NSLog(@"[BrowserView dealloc]"); +#endif + + if (challenge_ != nil) + [challenge_ release]; + + WebThreadLock(); + + WebView *webview = [webview_ webView]; + [webview setFrameLoadDelegate:nil]; + [webview setResourceLoadDelegate:nil]; + [webview setUIDelegate:nil]; + [webview setScriptDebugDelegate:nil]; + [webview setPolicyDelegate:nil]; + + [webview setDownloadDelegate:nil]; + + /* XXX: these are set by UIWebDocumentView + [webview _setFormDelegate:nil]; + [webview _setUIKitDelegate:nil]; + [webview setEditingDelegate:nil];*/ + + /* XXX: no one sets this, ever + [webview setWebMailDelegate:nil];*/ + + [webview_ setDelegate:nil]; + [webview_ setGestureDelegate:nil]; + [webview_ setFormEditingDelegate:nil]; + [webview_ setInteractionDelegate:nil]; + + [indirect_ setDelegate:nil]; + + //NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + [webview close]; + +#if RecycleWebViews + [webview_ removeFromSuperview]; + [Documents_ addObject:[webview_ autorelease]]; +#else + [webview_ release]; +#endif + + [indirect_ release]; + + WebThreadUnlock(); + + [scroller_ setDelegate:nil]; + + if (button_ != nil) + [button_ release]; + if (style_ != nil) + [style_ release]; + if (function_ != nil) + [function_ release]; + if (finish_ != nil) + [finish_ release]; + if (closer_ != nil) + [closer_ release]; + if (special_ != nil) + [special_ release]; + + [scroller_ release]; + [indicator_ release]; + if (confirm_ != nil) + [confirm_ release]; + if (sensitive_ != nil) + [sensitive_ release]; + if (title_ != nil) + [title_ release]; + [super dealloc]; +} + +- (void) loadURL:(NSURL *)url cachePolicy:(NSURLRequestCachePolicy)policy { + [self loadRequest:[NSURLRequest + requestWithURL:url + cachePolicy:policy + timeoutInterval:30.0 + ]]; +} + +- (void) loadURL:(NSURL *)url { + [self loadURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy]; +} + +- (void) loadRequest:(NSURLRequest *)request { + pushed_ = true; + error_ = false; + + WebThreadLock(); + [webview_ loadRequest:request]; + WebThreadUnlock(); +} + +- (void) reloadURL { + if (request_ == nil) + return; + + if ([request_ HTTPBody] == nil && [request_ HTTPBodyStream] == nil) + [self loadRequest:request_]; + else { + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:UCLocalize("RESUBMIT_FORM") + buttons:[NSArray arrayWithObjects:UCLocalize("CANCEL"), UCLocalize("SUBMIT"), nil] + defaultButtonIndex:0 + delegate:self + context:@"submit" + ] autorelease]; + + [sheet setNumberOfRows:1]; + [sheet popupAlertAnimated:YES]; + } +} + +- (WebView *) webView { + return [webview_ webView]; +} + +- (UIWebDocumentView *) documentView { + return webview_; +} + +/* XXX: WebThreadLock? */ +- (void) _fixScroller:(CGRect)bounds { + float extra; + if (!editing_) + extra = 0; + else { + UIFormAssistant *assistant([UIFormAssistant sharedFormAssistant]); + CGRect peripheral([assistant peripheralFrame]); +#if LogBrowser + NSLog(@"per:%f", peripheral.size.height); +#endif + extra = peripheral.size.height; + } + + CGRect subrect([scroller_ frame]); + subrect.size.height -= extra; + [scroller_ setScrollerIndicatorSubrect:subrect]; + + NSSize visible(NSMakeSize(subrect.size.width, subrect.size.height)); + [webview_ setValue:[NSValue valueWithSize:visible] forGestureAttribute:UIGestureAttributeVisibleSize]; + + CGSize size(size_); + size.height += extra; + [scroller_ setContentSize:size]; + + [scroller_ releaseRubberBandIfNecessary]; +} + +- (void) fixScroller { + CGRect bounds([webview_ documentBounds]); +#if TrackResize + NSLog(@"_fs:(%f,%f+%f,%f)", bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height); +#endif + [self _fixScroller:bounds]; +} + +- (void) view:(UIView *)sender didSetFrame:(CGRect)frame { + size_ = frame.size; +#if TrackResize + NSLog(@"dsf:(%f,%f+%f,%f)", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); +#endif + [self _fixScroller:frame]; +} + +- (void) view:(UIView *)sender didSetFrame:(CGRect)frame oldFrame:(CGRect)old { + [self view:sender didSetFrame:frame]; +} + +- (void) pushPage:(RVPage *)page { + [page setDelegate:delegate_]; + [self setBackButtonTitle:title_]; + [book_ pushPage:page]; +} + +- (void) _pushPage { + if (pushed_) + return; + // WTR: [self autorelease]; + pushed_ = true; + [book_ pushPage:self]; +} + +- (void) swapPage:(RVPage *)page { + [page setDelegate:delegate_]; + if (pushed_) + [book_ swapPage:page]; + else + [book_ pushPage:page]; +} + +- (BOOL) getSpecial:(NSURL *)url swap:(BOOL)swap { +#if LogBrowser + NSLog(@"getSpecial:%@", url); +#endif + + if (RVPage *page = [delegate_ pageForURL:url hasTag:NULL]) { + if (swap) + [self swapPage:page]; + else + [self pushPage:page]; + + return true; + } else + return false; +} + +- (void) webViewShow:(WebView *)sender { + /* XXX: this is where I cry myself to sleep */ +} + +- (bool) _allowJavaScriptPanel { + return true; +} + +- (bool) allowSensitiveRequests { + return [self _allowJavaScriptPanel]; +} + +- (void) _promptForSensitive:(NSMutableArray *)array { + NSString *name([array objectAtIndex:0]); + + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:nil + buttons:[NSArray arrayWithObjects:UCLocalize("YES"), UCLocalize("NO"), nil] + defaultButtonIndex:0 + delegate:indirect_ + context:@"sensitive" + ] autorelease]; + + NSString *host(@"XXX"); + + [sheet setNumberOfRows:1]; + [sheet setBodyText:[NSString stringWithFormat:@"The website at %@ is requesting your phone's %@. This is almost certainly for product licensing purposes. Will you allow this?", host, name]]; + [sheet popupAlertAnimated:YES]; + + NSRunLoop *loop([NSRunLoop currentRunLoop]); + NSDate *future([NSDate distantFuture]); + + while (sensitive_ == nil && [loop runMode:NSDefaultRunLoopMode beforeDate:future]); + + NSNumber *sensitive([sensitive_ autorelease]); + sensitive_ = nil; + + [self autorelease]; + [array replaceObjectAtIndex:0 withObject:sensitive]; +} + +- (bool) promptForSensitive:(NSString *)name { + if (![self allowSensitiveRequests]) + return false; + + NSMutableArray *array([NSMutableArray arrayWithCapacity:1]); + [array addObject:name]; + + [self performSelectorOnMainThread:@selector(_promptForSensitive:) withObject:array waitUntilDone:YES]; + return [[array lastObject] boolValue]; +} + +- (void) webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame { + if (![self _allowJavaScriptPanel]) + return; + [self retain]; + + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:nil + buttons:[NSArray arrayWithObjects:UCLocalize("OK"), nil] + defaultButtonIndex:0 + delegate:self + context:@"alert" + ] autorelease]; + + [sheet setBodyText:message]; + [sheet popupAlertAnimated:YES]; +} + +- (BOOL) webView:(WebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame { + if (![self _allowJavaScriptPanel]) + return NO; + [self retain]; + + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:nil + buttons:[NSArray arrayWithObjects:UCLocalize("OK"), UCLocalize("CANCEL"), nil] + defaultButtonIndex:0 + delegate:indirect_ + context:@"confirm" + ] autorelease]; + + [sheet setNumberOfRows:1]; + [sheet setBodyText:message]; + [sheet popupAlertAnimated:YES]; + + NSRunLoop *loop([NSRunLoop currentRunLoop]); + NSDate *future([NSDate distantFuture]); + + while (confirm_ == nil && [loop runMode:NSDefaultRunLoopMode beforeDate:future]); + + NSNumber *confirm([confirm_ autorelease]); + confirm_ = nil; + + [self autorelease]; + return [confirm boolValue]; +} + +- (void) setAutoPopup:(BOOL)popup { + popup_ = popup; +} + +- (void) setSpecial:(id)function { + if (special_ != nil) + [special_ autorelease]; + special_ = function == nil ? nil : [function retain]; +} + +- (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { + if (button_ != nil) + [button_ autorelease]; + button_ = button == nil ? nil : [[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:button]]] retain]; + + if (style_ != nil) + [style_ autorelease]; + style_ = style == nil ? nil : [style retain]; + + if (function_ != nil) + [function_ autorelease]; + function_ = function == nil ? nil : [function retain]; + + [self reloadButtons]; +} + +- (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { + if (button_ != nil) + [button_ autorelease]; + button_ = button == nil ? nil : [button retain]; + + if (style_ != nil) + [style_ autorelease]; + style_ = style == nil ? nil : [style retain]; + + if (function_ != nil) + [function_ autorelease]; + function_ = function == nil ? nil : [function retain]; + + [self reloadButtons]; +} + +- (void) setFinishHook:(id)function { + if (finish_ != nil) + [finish_ autorelease]; + finish_ = function == nil ? nil : [function retain]; +} + +- (void) setPopupHook:(id)function { + if (closer_ != nil) + [closer_ autorelease]; + closer_ = function == nil ? nil : [function retain]; +} + +- (void) _openMailToURL:(NSURL *)url { +// XXX: this makes me sad +#if 0 + [[[MailToView alloc] initWithView:underlay_ delegate:self url:url] autorelease]; +#else + [UIApp openURL:url];// asPanel:YES]; +#endif +} + +- (void) webView:(WebView *)sender willBeginEditingFormElement:(id)element { + editing_ = true; +} + +- (void) webView:(WebView *)sender didBeginEditingFormElement:(id)element { + [self fixScroller]; +} + +- (void) webViewDidEndEditingFormElements:(WebView *)sender { + editing_ = false; + [self fixScroller]; +} + +- (void) webViewClose:(WebView *)sender { + [book_ close]; +} + +- (void) close { + [book_ close]; +} + +- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { +} + +- (void) webView:(WebView *)sender unableToImplementPolicyWithError:(NSError *)error frame:(WebFrame *)frame { + NSLog(@"err:%@", error); +} + +- (void) webView:(WebView *)sender decidePolicyForNewWindowAction:(NSDictionary *)action request:(NSURLRequest *)request newFrameName:(NSString *)name decisionListener:(id<WebPolicyDecisionListener>)listener { +#if LogBrowser + NSLog(@"nwa:%@", name); +#endif + + if (NSURL *url = [request URL]) { + if (name == nil) unknown: { + if (![self getSpecial:url swap:NO]) { + NSString *scheme([[url scheme] lowercaseString]); + if ([scheme isEqualToString:@"mailto"]) + [self _openMailToURL:url]; + else goto use; + } + } else if ([name isEqualToString:@"_open"]) + [delegate_ openURL:url]; + else if ([name isEqualToString:@"_popup"]) { + NSString *scheme([[url scheme] lowercaseString]); + if ([scheme isEqualToString:@"mailto"]) + [self _openMailToURL:url]; + else { + RVBook *book([[[RVPopUpBook alloc] initWithFrame:[delegate_ popUpBounds]] autorelease]); + [book setHook:indirect_]; + + RVPage *page([delegate_ pageForURL:url hasTag:NULL]); + if (page == nil) { + /* XXX: call createWebViewWithRequest instead? */ + + [self setBackButtonTitle:title_]; + + BrowserView *browser([[[BrowserView alloc] initWithBook:book] autorelease]); + [browser loadURL:url]; + page = browser; + } + + [book setDelegate:delegate_]; + [page setDelegate:delegate_]; + + [book setPage:page]; + [book_ pushBook:book]; + } + } else goto unknown; + + [listener ignore]; + } else use: + [listener use]; +} + +- (void) webView:(WebView *)sender decidePolicyForMIMEType:(NSString *)type request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener { + if ([WebView canShowMIMEType:type]) + [listener use]; + else { + // XXX: handle more mime types! + [listener ignore]; + + WebView *webview([webview_ webView]); + if (frame == [webview mainFrame]) + [UIApp openURL:[request URL]]; + } +} + +- (void) webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)action request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener { + if (request == nil) ignore: { + [listener ignore]; + return; + } + + NSURL *url([request URL]); + + if (url == nil) use: { + if (!error_ && [frame parentFrame] == nil) { + if (request_ != nil) + [request_ autorelease]; + request_ = [request retain]; +#if LogBrowser + NSLog(@"dpn:%@", request_); +#endif + } + + [listener use]; + + WebView *webview([webview_ webView]); + if (frame == [webview mainFrame]) + [self _pushPage]; + return; + } +#if LogBrowser + else NSLog(@"nav:%@:%@", url, [action description]); +#endif + + const NSArray *capability; + +#if 0 // XXX:3:GSSystemCopyCapability + capability = reinterpret_cast<const NSArray *>(GSSystemGetCapability(kGSDisplayIdentifiersCapability)); +#else + capability = nil; +#endif + + if (capability != nil && ( + [capability containsObject:@"com.apple.Maps"] && [url mapsURL] || + [capability containsObject:@"com.apple.youtube"] && [url youTubeURL] + )) { + open: + [UIApp openURL:url]; + goto ignore; + } + + int store(_not(int)); + if (NSURL *itms = [url itmsURL:&store]) { +#if LogBrowser + NSLog(@"itms#%@#%u#%@", url, store, itms); +#endif + + if (capability != nil && ( + store == 1 && [capability containsObject:@"com.apple.MobileStore"] || + store == 2 && [capability containsObject:@"com.apple.AppStore"] + )) { + url = itms; + goto open; + } + } + + NSString *scheme([[url scheme] lowercaseString]); + + if ([scheme isEqualToString:@"tel"]) { + // XXX: intelligence + goto open; + } + + if ([scheme isEqualToString:@"mailto"]) { + [self _openMailToURL:url]; + goto ignore; + } + + if ([self getSpecial:url swap:YES]) + goto ignore; + else if ([WebView _canHandleRequest:request]) + goto use; + else if ([url isSpringboardHandledURL]) + goto open; + else + goto use; +} + +- (void) webView:(WebView *)sender setStatusText:(NSString *)text { + //lprintf("Status:%s\n", [text UTF8String]); +} + +- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button { + NSString *context([sheet context]); + + if ([context isEqualToString:@"alert"]) { + [self autorelease]; + [sheet dismiss]; + } else if ([context isEqualToString:@"confirm"]) { + switch (button) { + case 1: + confirm_ = [NSNumber numberWithBool:YES]; + break; + + case 2: + confirm_ = [NSNumber numberWithBool:NO]; + break; + } + + [sheet dismiss]; + } else if ([context isEqualToString:@"sensitive"]) { + switch (button) { + case 1: + sensitive_ = [NSNumber numberWithBool:YES]; + break; + + case 2: + sensitive_ = [NSNumber numberWithBool:NO]; + break; + } + + [sheet dismiss]; + } else if ([context isEqualToString:@"challenge"]) { + id<NSURLAuthenticationChallengeSender> sender([challenge_ sender]); + + switch (button) { + case 1: { + NSString *username([[sheet textFieldAtIndex:0] text]); + NSString *password([[sheet textFieldAtIndex:1] text]); + + NSURLCredential *credential([NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceForSession]); + + [sender useCredential:credential forAuthenticationChallenge:challenge_]; + } break; + + case 2: + [sender cancelAuthenticationChallenge:challenge_]; + break; + + default: + _assert(false); + } + + [challenge_ release]; + challenge_ = nil; + + [sheet dismiss]; + } else if ([context isEqualToString:@"submit"]) { + switch (button) { + case 1: + break; + + case 2: + if (request_ != nil) { + WebThreadLock(); + [webview_ loadRequest:request_]; + WebThreadUnlock(); + } + break; + + default: + _assert(false); + } + + [sheet dismiss]; + } +} + +- (void) webView:(WebView *)sender resource:(id)identifier didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge fromDataSource:(WebDataSource *)source { + challenge_ = [challenge retain]; + + NSURLProtectionSpace *space([challenge protectionSpace]); + NSString *realm([space realm]); + if (realm == nil) + realm = @""; + + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:realm + buttons:[NSArray arrayWithObjects:UCLocalize("LOGIN"), UCLocalize("CANCEL"), nil] + defaultButtonIndex:0 + delegate:self + context:@"challenge" + ] autorelease]; + + [sheet setNumberOfRows:1]; + + [sheet addTextFieldWithValue:@"" label:UCLocalize("USERNAME")]; + [sheet addTextFieldWithValue:@"" label:UCLocalize("PASSWORD")]; + + UITextField *username([sheet textFieldAtIndex:0]); { + UITextInputTraits *traits([username textInputTraits]); + [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; + [traits setKeyboardType:UIKeyboardTypeASCIICapable]; + [traits setReturnKeyType:UIReturnKeyNext]; + } + + UITextField *password([sheet textFieldAtIndex:1]); { + UITextInputTraits *traits([password textInputTraits]); + [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; + [traits setKeyboardType:UIKeyboardTypeASCIICapable]; + // XXX: UIReturnKeyDone + [traits setReturnKeyType:UIReturnKeyNext]; + [traits setSecureTextEntry:YES]; + } + + [sheet popupAlertAnimated:YES]; +} + +- (NSURLRequest *) webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)source { + return request; +} + +- (WebView *) webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request windowFeatures:(NSDictionary *)features { +//- (WebView *) webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request userGesture:(BOOL)gesture { +#if LogBrowser + NSLog(@"cwv:%@ (%@): %@", request, title_, features == nil ? @"{}" : [features description]); + //NSLog(@"cwv:%@ (%@): %@", request, title_, gesture ? @"Yes" : @"No"); +#endif + + NSNumber *value([features objectForKey:@"width"]); + float width(value == nil ? 0 : [value floatValue]); + + RVBook *book(!popup_ ? book_ : [[[RVPopUpBook alloc] initWithFrame:[delegate_ popUpBounds]] autorelease]); + + /* XXX: deal with cydia:// pages */ + BrowserView *browser([[[BrowserView alloc] initWithBook:book forWidth:width] autorelease]); + + if (features != nil && popup_) { + [book setDelegate:delegate_]; + [book setHook:indirect_]; + [browser setDelegate:delegate_]; + + [browser loadRequest:request]; + + [book setPage:browser]; + [book_ pushBook:book]; + } else if (request == nil) { + [self setBackButtonTitle:title_]; + [browser setDelegate:delegate_]; + [browser retain]; + } else { + [self pushPage:browser]; + [browser loadRequest:request]; + } + + return [browser webView]; +} + +- (WebView *) webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request { + return [self webView:sender createWebViewWithRequest:request windowFeatures:nil]; + //return [self webView:sender createWebViewWithRequest:request userGesture:YES]; +} + +- (void) webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame { + if ([frame parentFrame] != nil) + return; + + title_ = [title retain]; + [book_ reloadTitleForPage:self]; +} + +- (void) webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame { + if ([loading_ count] == 0) + [self retain]; + [loading_ addObject:[NSValue valueWithNonretainedObject:frame]]; + + if ([frame parentFrame] == nil) { + [webview_ resignFirstResponder]; + + reloading_ = false; + + if (title_ != nil) { + [title_ release]; + title_ = nil; + } + + if (button_ != nil) { + [button_ release]; + button_ = nil; + } + + if (style_ != nil) { + [style_ release]; + style_ = nil; + } + + if (function_ != nil) { + [function_ release]; + function_ = nil; + } + + if (finish_ != nil) { + [finish_ release]; + finish_ = nil; + } + + if (closer_ != nil) { + [closer_ release]; + closer_ = nil; + } + + if (special_ != nil) { + [special_ release]; + special_ = nil; + } + + [book_ reloadTitleForPage:self]; + + [scroller_ scrollPointVisibleAtTopLeft:CGPointZero]; + + if ([scroller_ respondsToSelector:@selector(setZoomScale:duration:)]) + [scroller_ setZoomScale:1 duration:0]; + else if ([scroller_ respondsToSelector:@selector(_setZoomScale:duration:)]) + [scroller_ _setZoomScale:1 duration:0]; + /*else if ([scroller_ respondsToSelector:@selector(setZoomScale:animated:)]) + [scroller_ setZoomScale:1 animated:NO];*/ + + CGRect webrect = [scroller_ bounds]; + webrect.size.height = 0; + [webview_ setFrame:webrect]; + } + + [self reloadButtons]; +} + +- (void) _finishLoading { + size_t count([loading_ count]); + if (count == 0) + [self autorelease]; + if (reloading_ || count != 0) + return; + if (finish_ != nil) + [self callFunction:finish_]; + [self reloadButtons]; +} + +- (bool) isLoading { + return [loading_ count] != 0; +} + +- (void) reloadButtons { + if ([self isLoading]) + [indicator_ startAnimation]; + else + [indicator_ stopAnimation]; + [super reloadButtons]; +} + +- (BOOL) webView:(WebView *)sender shouldScrollToPoint:(struct CGPoint)point forFrame:(WebFrame *)frame { + return [webview_ webView:sender shouldScrollToPoint:point forFrame:frame]; +} + +- (void) webView:(WebView *)sender didReceiveViewportArguments:(id)arguments forFrame:(WebFrame *)frame { + return [webview_ webView:sender didReceiveViewportArguments:arguments forFrame:frame]; +} + +- (void) webView:(WebView *)sender needsScrollNotifications:(id)notifications forFrame:(WebFrame *)frame { + return [webview_ webView:sender needsScrollNotifications:notifications forFrame:frame]; +} + +- (void) webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)frame { + [self _pushPage]; + return [webview_ webView:sender didCommitLoadForFrame:frame]; +} + +- (void) webView:(WebView *)sender didReceiveDocTypeForFrame:(WebFrame *)frame { + return [webview_ webView:sender didReceiveDocTypeForFrame:frame]; +} + +- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame { + [loading_ removeObject:[NSValue valueWithNonretainedObject:frame]]; + [self _finishLoading]; + + if ([frame parentFrame] == nil) { + if (DOMDocument *document = [frame DOMDocument]) + if (DOMNodeList<NSFastEnumeration> *bodies = [document getElementsByTagName:@"body"]) + for (DOMHTMLBodyElement *body in bodies) { + DOMCSSStyleDeclaration *style([document getComputedStyle:body pseudoElement:nil]); + + bool colored(false); + + if (DOMCSSPrimitiveValue *color = static_cast<DOMCSSPrimitiveValue *>([style getPropertyCSSValue:@"background-color"])) { + if ([color primitiveType] == DOM_CSS_RGBCOLOR) { + DOMRGBColor *rgb([color getRGBColorValue]); + + float red([[rgb red] getFloatValue:DOM_CSS_NUMBER]); + float green([[rgb green] getFloatValue:DOM_CSS_NUMBER]); + float blue([[rgb blue] getFloatValue:DOM_CSS_NUMBER]); + float alpha([[rgb alpha] getFloatValue:DOM_CSS_NUMBER]); + + UIColor *uic(nil); + + if (red == 0xc7 && green == 0xce && blue == 0xd5) + uic = [UIColor pinStripeColor]; + else if (alpha != 0) + uic = [UIColor + colorWithRed:(red / 255) + green:(green / 255) + blue:(blue / 255) + alpha:alpha + ]; + + if (uic != nil) { + colored = true; + [scroller_ setBackgroundColor:uic]; + } + } + } + + if (!colored) + [scroller_ setBackgroundColor:[UIColor pinStripeColor]]; + break; + } + } + + return [webview_ webView:sender didFinishLoadForFrame:frame]; +} + +- (void) _didFailWithError:(NSError *)error forFrame:(WebFrame *)frame { + if ([frame parentFrame] == nil) + [self autorelease]; + + [loading_ removeObject:[NSValue valueWithNonretainedObject:frame]]; + [self _finishLoading]; + + if (reloading_) + return; + + if ([frame parentFrame] == nil) { + [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", + [[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"error" ofType:@"html"]] absoluteString], + [[error localizedDescription] stringByAddingPercentEscapes] + ]]]; + + error_ = true; + } +} + +- (void) webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { + [self _didFailWithError:error forFrame:frame]; +} + +- (void) webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame { + [self _didFailWithError:error forFrame:frame]; +} + +- (void) webView:(WebView *)sender addMessageToConsole:(NSDictionary *)dictionary { +#if LogBrowser || ForSaurik + lprintf("Console:%s\n", [[dictionary description] UTF8String]); +#endif +} + +/* XXX: fix this stupid include file +- (void) webView:(WebView *)sender frame:(WebFrame *)frame exceededDatabaseQuotaForSecurityOrigin:(WebSecurityOrigin *)origin database:(NSString *)database { + [origin setQuota:0x500000]; +}*/ + +- (void) _setTileDrawingEnabled:(BOOL)enabled { + //[webview_ setTileDrawingEnabled:enabled]; +} + +- (void) setViewportWidth:(float)width { + width_ = width ? width != 0 : [[self class] defaultWidth]; + [webview_ setViewportSize:CGSizeMake(width_, UIWebViewGrowsAndShrinksToFitHeight) forDocumentTypes:0x10]; +} + +- (void) willStartGesturesInView:(UIView *)view forEvent:(GSEventRef)event { + [self _setTileDrawingEnabled:NO]; +} + +- (void) didFinishGesturesInView:(UIView *)view forEvent:(GSEventRef)event { + [self _setTileDrawingEnabled:YES]; + [webview_ redrawScaledDocument]; +} + +- (void) scrollerWillStartDragging:(UIScroller *)scroller { + [self _setTileDrawingEnabled:NO]; +} + +- (void) scrollerDidEndDragging:(UIScroller *)scroller willSmoothScroll:(BOOL)smooth { + [self _setTileDrawingEnabled:YES]; +} + +- (void) scrollerDidEndDragging:(UIScroller *)scroller { + [self _setTileDrawingEnabled:YES]; +} + +- (id) initWithBook:(RVBook *)book forWidth:(float)width { + if ((self = [super initWithBook:book]) != nil) { + loading_ = [[NSMutableSet alloc] initWithCapacity:3]; + popup_ = false; + + struct CGRect bounds = [self bounds]; + + scroller_ = [[UIScroller alloc] initWithFrame:bounds]; + [self addSubview:scroller_]; + + [scroller_ setFixedBackgroundPattern:YES]; + [scroller_ setBackgroundColor:[UIColor pinStripeColor]]; + + [scroller_ setScrollingEnabled:YES]; + [scroller_ setClipsSubviews:YES]; + [scroller_ setAllowsRubberBanding:YES]; + + [scroller_ setDelegate:self]; + [scroller_ setBounces:YES]; + [scroller_ setScrollHysteresis:8]; + [scroller_ setThumbDetectionEnabled:NO]; + [scroller_ setDirectionalScrolling:YES]; + [scroller_ setScrollDecelerationFactor:0.99]; /* 0.989324 */ + [scroller_ setEventMode:YES]; + [scroller_ setShowBackgroundShadow:NO]; /* YES */ + [scroller_ setAllowsRubberBanding:YES]; /* Vertical */ + [scroller_ setAdjustForContentSizeChange:YES]; /* NO */ + + CGRect webrect = [scroller_ bounds]; + webrect.size.height = 0; + + WebView *webview; + + WebThreadLock(); + +#if RecycleWebViews + webview_ = [Documents_ lastObject]; + if (webview_ != nil) { + webview_ = [webview_ retain]; + webview = [webview_ webView]; + [Documents_ removeLastObject]; + [webview_ setFrame:webrect]; + } else { +#else + if (true) { +#endif + webview_ = [[UIWebDocumentView alloc] initWithFrame:webrect]; + webview = [webview_ webView]; + + // XXX: this is terribly (too?) expensive + //[webview_ setDrawsBackground:NO]; + [webview setPreferencesIdentifier:@"Cydia"]; + + [webview_ setTileSize:CGSizeMake(webrect.size.width, 500)]; + + [webview_ setAllowsMessaging:YES]; + + [webview_ setTilingEnabled:YES]; + [webview_ setDrawsGrid:NO]; + [webview_ setLogsTilingChanges:NO]; + [webview_ setTileMinificationFilter:kCAFilterNearest]; + if ([webview_ respondsToSelector:@selector(setDataDetectorTypes:)]) + /* XXX: abstractify */ + [webview_ setDataDetectorTypes:0x80000000]; + else + [webview_ setDetectsPhoneNumbers:NO]; + [webview_ setAutoresizes:YES]; + + [webview_ setMinimumScale:0.25f forDocumentTypes:0x10]; + [webview_ setMaximumScale:5.00f forDocumentTypes:0x10]; + [webview_ setInitialScale:UIWebViewScalesToFitScale forDocumentTypes:0x10]; + //[webview_ setViewportSize:CGSizeMake(980, UIWebViewGrowsAndShrinksToFitHeight) forDocumentTypes:0x10]; + + [webview_ setViewportSize:CGSizeMake(320, UIWebViewGrowsAndShrinksToFitHeight) forDocumentTypes:0x2]; + + [webview_ setMinimumScale:1.00f forDocumentTypes:0x8]; + [webview_ setInitialScale:UIWebViewScalesToFitScale forDocumentTypes:0x8]; + [webview_ setViewportSize:CGSizeMake(320, UIWebViewGrowsAndShrinksToFitHeight) forDocumentTypes:0x8]; + + [webview_ _setDocumentType:0x4]; + + if ([webview_ respondsToSelector:@selector(UIWebDocumentView:)]) + [webview_ setZoomsFocusedFormControl:YES]; + [webview_ setContentsPosition:7]; + [webview_ setEnabledGestures:0xa]; + [webview_ setValue:[NSNumber numberWithBool:YES] forGestureAttribute:UIGestureAttributeIsZoomRubberBandEnabled]; + [webview_ setValue:[NSNumber numberWithBool:YES] forGestureAttribute:UIGestureAttributeUpdatesScroller]; + + [webview_ setSmoothsFonts:YES]; + [webview_ setAllowsImageSheet:YES]; + [webview _setUsesLoaderCache:YES]; + + [webview setGroupName:@"CydiaGroup"]; + if ([webview respondsToSelector:@selector(_setLayoutInterval:)]) + [webview _setLayoutInterval:0]; + } + + [self setViewportWidth:width]; + + [webview_ setDelegate:self]; + [webview_ setGestureDelegate:self]; + [webview_ setFormEditingDelegate:self]; + [webview_ setInteractionDelegate:self]; + + [scroller_ addSubview:webview_]; + + //NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + indirect_ = [[IndirectDelegate alloc] initWithDelegate:self]; + + [webview setFrameLoadDelegate:indirect_]; + [webview setResourceLoadDelegate:indirect_]; + [webview setUIDelegate:indirect_]; + [webview setScriptDebugDelegate:indirect_]; + [webview setPolicyDelegate:indirect_]; + + WebThreadUnlock(); + + CGSize indsize = [UIProgressIndicator defaultSizeForStyle:UIProgressIndicatorStyleMediumWhite]; + indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(281, 12, indsize.width, indsize.height)]; + [indicator_ setStyle:UIProgressIndicatorStyleMediumWhite]; + + [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight]; + [scroller_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight]; + + /*UIWebView *test([[[UIWebView alloc] initWithFrame:[self bounds]] autorelease]); + [test loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.saurik.com/"]]]; + [self addSubview:test];*/ + } return self; +} + +- (id) initWithBook:(RVBook *)book { + return [self initWithBook:book forWidth:0]; +} + +- (NSString *) stringByEvaluatingJavaScriptFromString:(NSString *)script { + WebThreadLock(); + WebView *webview([webview_ webView]); + NSString *string([webview stringByEvaluatingJavaScriptFromString:script]); + WebThreadUnlock(); + return string; +} + +- (void) callFunction:(WebScriptObject *)function { + WebThreadLock(); + + WebView *webview([webview_ webView]); + WebFrame *frame([webview mainFrame]); + + id _private(MSHookIvar<id>(webview, "_private")); + WebCore::Page *page(_private == nil ? NULL : MSHookIvar<WebCore::Page *>(_private, "page")); + WebCore::Settings *settings(page == NULL ? NULL : page->settings()); + + bool no; + if (settings == NULL) + no = 0; + else { + no = settings->JavaScriptCanOpenWindowsAutomatically(); + settings->setJavaScriptCanOpenWindowsAutomatically(true); + } + + if (UIWindow *window = [self window]) + if (UIResponder *responder = [window firstResponder]) + [responder resignFirstResponder]; + + JSObjectRef object([function JSObject]); + JSGlobalContextRef context([frame globalContext]); + JSObjectCallAsFunction(context, object, NULL, 0, NULL, NULL); + + if (settings != NULL) + settings->setJavaScriptCanOpenWindowsAutomatically(no); + + WebThreadUnlock(); +} + +- (void) didCloseBook:(RVBook *)book { + if (closer_ != nil) + [self callFunction:closer_]; +} + +- (void) __rightButtonClicked { + reloading_ = true; + [self reloadURL]; +} + +- (void) _rightButtonClicked { +#if !AlwaysReload + if (function_ != nil) + [self callFunction:function_]; + else +#endif + [self __rightButtonClicked]; +} + +- (id) _rightButtonTitle { + return UCLocalize("RELOAD"); +} + +- (id) rightButtonTitle { + return [self isLoading] ? @"" : button_ != nil ? button_ : [self _rightButtonTitle]; +} + +- (UINavigationButtonStyle) rightButtonStyle { + if (style_ == nil) normal: + return UINavigationButtonStyleNormal; + else if ([style_ isEqualToString:@"Normal"]) + return UINavigationButtonStyleNormal; + else if ([style_ isEqualToString:@"Back"]) + return UINavigationButtonStyleBack; + else if ([style_ isEqualToString:@"Highlighted"]) + return UINavigationButtonStyleHighlighted; + else if ([style_ isEqualToString:@"Destructive"]) + return UINavigationButtonStyleDestructive; + else goto normal; +} + +- (NSString *) title { + return title_ == nil ? UCLocalize("LOADING") : title_; +} + +- (NSString *) backButtonTitle { + return UCLocalize("BROWSER"); +} + +- (void) setPageActive:(BOOL)active { + if (!active) + [indicator_ removeFromSuperview]; + else + [[book_ navigationBar] addSubview:indicator_]; +} + +- (void) resetViewAnimated:(BOOL)animated { +} + +- (void) setPushed:(bool)pushed { + pushed_ = pushed; +} + ++ (float) defaultWidth { + return 980; +} + +@end |