iOS web browser with a focus on security and privacy
at remove_ckhttpconnection 1185 lines 40 kB view raw
1/* 2 * Endless 3 * Copyright (c) 2014-2016 joshua stein <jcs@jcs.org> 4 * 5 * See LICENSE file for redistribution terms. 6 */ 7 8#import "AppDelegate.h" 9#import "BookmarkController.h" 10#import "SSLCertificateViewController.h" 11#import "URLInterceptor.h" 12#import "WebViewController.h" 13#import "WebViewTab.h" 14#import "WebViewMenuController.h" 15#import "WYPopoverController.h" 16 17#ifdef SHOW_DONATION_CONTROLLER 18#import "DonationViewController.h" 19#endif 20 21@implementation WebViewController { 22 AppDelegate *appDelegate; 23 UIView *wrapper; 24 25 UIScrollView *tabScroller; 26 UIPageControl *tabChooser; 27 int curTabIndex; 28 NSMutableArray <WebViewTab *> *webViewTabs; 29 30 UIView *toolbar; 31 UITextField *urlField; 32 UIButton *lockIcon; 33 UIButton *brokenLockIcon; 34 UIProgressView *progressBar; 35 UIView *tabToolbarHairline; 36 UIToolbar *tabToolbar; 37 UILabel *tabCount; 38 int keyboardHeight; 39 40 UIButton *backButton; 41 UIButton *forwardButton; 42 UIButton *tabsButton; 43 UIButton *settingsButton; 44 45 UIBarButtonItem *tabAddButton; 46 UIBarButtonItem *tabDoneButton; 47 48 float lastWebViewScrollOffset; 49 BOOL showingTabs; 50 BOOL webViewScrollIsDecelerating; 51 BOOL webViewScrollIsDragging; 52 53 WYPopoverController *popover; 54 55 BookmarkController *bookmarks; 56} 57 58- (void)loadView 59{ 60 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 61 62 appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 63 [appDelegate setWebViewController:self]; 64 [appDelegate setDefaultUserAgent:[self buildDefaultUserAgent]]; 65 66 webViewTabs = [[NSMutableArray alloc] initWithCapacity:10]; 67 curTabIndex = 0; 68 69 self.view = [[UIView alloc] initWithFrame:[[appDelegate window] frame]]; 70 71 wrapper = [[UIView alloc] initWithFrame:self.view.frame]; 72 [[self view] addSubview:wrapper]; 73 74 tabScroller = [[UIScrollView alloc] init]; 75 [tabScroller setScrollEnabled:NO]; 76 [wrapper addSubview:tabScroller]; 77 78 toolbar = [[UIView alloc] init]; 79 [toolbar setClipsToBounds:YES]; 80 [[self view] addSubview:toolbar]; 81 82 self.toolbarOnBottom = [userDefaults boolForKey:@"toolbar_on_bottom"]; 83 self.darkInterface = [userDefaults boolForKey:@"dark_interface"]; 84 85 keyboardHeight = 0; 86 87 tabToolbarHairline = [[UIView alloc] init]; 88 [toolbar addSubview:tabToolbarHairline]; 89 90 progressBar = [[UIProgressView alloc] init]; 91 [progressBar setTrackTintColor:[UIColor clearColor]]; 92 [progressBar setTintColor:[appDelegate window].tintColor]; 93 [progressBar setProgress:0.0]; 94 [toolbar addSubview:progressBar]; 95 96 backButton = [UIButton buttonWithType:UIButtonTypeCustom]; 97 UIImage *backImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 98 [backButton setImage:backImage forState:UIControlStateNormal]; 99 [backButton addTarget:self action:@selector(goBack:) forControlEvents:UIControlEventTouchUpInside]; 100 [toolbar addSubview:backButton]; 101 102 forwardButton = [UIButton buttonWithType:UIButtonTypeCustom]; 103 UIImage *forwardImage = [[UIImage imageNamed:@"forward"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 104 [forwardButton setImage:forwardImage forState:UIControlStateNormal]; 105 [forwardButton addTarget:self action:@selector(goForward:) forControlEvents:UIControlEventTouchUpInside]; 106 [toolbar addSubview:forwardButton]; 107 108 urlField = [[UITextField alloc] init]; 109 [urlField setBorderStyle:UITextBorderStyleRoundedRect]; 110 [urlField setKeyboardType:UIKeyboardTypeWebSearch]; 111 [urlField setFont:[UIFont systemFontOfSize:15]]; 112 [urlField setReturnKeyType:UIReturnKeyGo]; 113 [urlField setClearButtonMode:UITextFieldViewModeWhileEditing]; 114 [urlField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; 115 [urlField setLeftViewMode:UITextFieldViewModeAlways]; 116 [urlField setSpellCheckingType:UITextSpellCheckingTypeNo]; 117 [urlField setAutocorrectionType:UITextAutocorrectionTypeNo]; 118 [urlField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; 119 [urlField setDelegate:self]; 120 [toolbar addSubview:urlField]; 121 122 lockIcon = [UIButton buttonWithType:UIButtonTypeCustom]; 123 [lockIcon setFrame:CGRectMake(0, 0, 24, 16)]; 124 [lockIcon setImage:[UIImage imageNamed:@"lock"] forState:UIControlStateNormal]; 125 [[lockIcon imageView] setContentMode:UIViewContentModeScaleAspectFit]; 126 [lockIcon addTarget:self action:@selector(showSSLCertificate) forControlEvents:UIControlEventTouchUpInside]; 127 128 brokenLockIcon = [UIButton buttonWithType:UIButtonTypeCustom]; 129 [brokenLockIcon setFrame:CGRectMake(0, 0, 24, 16)]; 130 [brokenLockIcon setImage:[UIImage imageNamed:@"broken_lock"] forState:UIControlStateNormal]; 131 [[brokenLockIcon imageView] setContentMode:UIViewContentModeScaleAspectFit]; 132 [brokenLockIcon addTarget:self action:@selector(showSSLCertificate) forControlEvents:UIControlEventTouchUpInside]; 133 134 tabsButton = [UIButton buttonWithType:UIButtonTypeCustom]; 135 UIImage *tabsImage = [[UIImage imageNamed:@"tabs"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 136 [tabsButton setImage:tabsImage forState:UIControlStateNormal]; 137 [tabsButton setTintColor:[progressBar tintColor]]; 138 [tabsButton addTarget:self action:@selector(showTabs:) forControlEvents:UIControlEventTouchUpInside]; 139 [toolbar addSubview:tabsButton]; 140 141 tabCount = [[UILabel alloc] init]; 142 [tabCount setText:@""]; 143 [tabCount setTextAlignment:NSTextAlignmentCenter]; 144 [tabCount setFont:[UIFont systemFontOfSize:11]]; 145 [tabCount setTextColor:[progressBar tintColor]]; 146 [toolbar addSubview:tabCount]; 147 148 settingsButton = [UIButton buttonWithType:UIButtonTypeCustom]; 149 UIImage *settingsImage = [[UIImage imageNamed:@"settings"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 150 [settingsButton setImage:settingsImage forState:UIControlStateNormal]; 151 [settingsButton setTintColor:[progressBar tintColor]]; 152 [settingsButton addTarget:self action:@selector(showPopover:) forControlEvents:UIControlEventTouchUpInside]; 153 [toolbar addSubview:settingsButton]; 154 155 [tabScroller setAutoresizingMask:(UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight)]; 156 [tabScroller setAutoresizesSubviews:NO]; 157 [tabScroller setShowsHorizontalScrollIndicator:NO]; 158 [tabScroller setShowsVerticalScrollIndicator:NO]; 159 [tabScroller setScrollsToTop:NO]; 160 [tabScroller setDelaysContentTouches:NO]; 161 [tabScroller setDelegate:self]; 162 163 tabChooser = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, TOOLBAR_HEIGHT)]; 164 [tabChooser setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)]; 165 [tabChooser addTarget:self action:@selector(slideToCurrentTab:) forControlEvents:UIControlEventValueChanged]; 166 [tabChooser setNumberOfPages:0]; 167 [self.view insertSubview:tabChooser aboveSubview:toolbar]; 168 [tabChooser setHidden:true]; 169 170 tabToolbar = [[UIToolbar alloc] init]; 171 [tabToolbar setClipsToBounds:YES]; 172 [tabToolbar setHidden:true]; 173 /* make completely transparent */ 174 [tabToolbar setBackgroundImage:[UIImage new] forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault]; 175 [tabToolbar setShadowImage:[UIImage new] forToolbarPosition:UIBarPositionAny]; 176 [self.view insertSubview:tabToolbar aboveSubview:toolbar]; 177 178 tabAddButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addNewTabFromToolbar:)]; 179 tabDoneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneWithTabsButton:)]; 180 tabDoneButton.title = @"Done"; 181 182 tabToolbar.items = [NSArray arrayWithObjects: 183 [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil], 184 tabAddButton, 185 [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil], 186 tabDoneButton, 187 nil]; 188 189 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 190 [center addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 191 [center addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 192 193 [[appDelegate window] addSubview:self.view]; 194 195 [self updateSearchBarDetails]; 196 197 [self.view.window makeKeyAndVisible]; 198} 199 200- (UIButton *)settingsButton 201{ 202 return settingsButton; 203} 204 205- (BOOL)prefersStatusBarHidden 206{ 207 return NO; 208} 209 210- (UIStatusBarStyle)preferredStatusBarStyle 211{ 212 if ([self darkInterface] || [self toolbarOnBottom]) 213 return UIStatusBarStyleLightContent; 214 else 215 return UIStatusBarStyleDefault; 216} 217 218- (void)didReceiveMemoryWarning 219{ 220 [super didReceiveMemoryWarning]; 221 222 NSLog(@"============================="); 223 NSLog(@"didReceiveMemoryWarning"); 224 NSLog(@"============================="); 225} 226 227- (void)encodeRestorableStateWithCoder:(NSCoder *)coder 228{ 229 [super encodeRestorableStateWithCoder:coder]; 230 231 NSMutableArray *wvtd = [[NSMutableArray alloc] initWithCapacity:webViewTabs.count - 1]; 232 for (WebViewTab *wvt in webViewTabs) { 233 if (wvt.url == nil) 234 continue; 235 236 [wvtd addObject:@{ @"url" : wvt.url, @"title" : wvt.title.text }]; 237 [[wvt webView] setRestorationIdentifier:[wvt.url absoluteString]]; 238 } 239 [coder encodeObject:wvtd forKey:@"webViewTabs"]; 240 [coder encodeObject:[NSNumber numberWithInt:curTabIndex] forKey:@"curTabIndex"]; 241} 242 243- (void)decodeRestorableStateWithCoder:(NSCoder *)coder 244{ 245 [super decodeRestorableStateWithCoder:coder]; 246 247 NSMutableArray *wvt = [coder decodeObjectForKey:@"webViewTabs"]; 248 for (int i = 0; i < wvt.count; i++) { 249 NSDictionary *params = wvt[i]; 250#ifdef TRACE 251 NSLog(@"[WebViewController] restoring tab %d with %@", i, params); 252#endif 253 WebViewTab *wvt = [self addNewTabForURL:[params objectForKey:@"url"] forRestoration:YES withCompletionBlock:nil]; 254 [[wvt title] setText:[params objectForKey:@"title"]]; 255 } 256 257 NSNumber *cp = [coder decodeObjectForKey:@"curTabIndex"]; 258 if (cp != nil) { 259 if ([cp intValue] <= [webViewTabs count] - 1) 260 [self setCurTabIndex:[cp intValue]]; 261 262 [tabScroller setContentOffset:CGPointMake([self frameForTabIndex:tabChooser.currentPage].origin.x, 0) animated:NO]; 263 264 /* wait for the UI to catch up */ 265 [[self curWebViewTab] performSelector:@selector(refresh) withObject:nil afterDelay:0.5]; 266 } 267 268 [self updateSearchBarDetails]; 269} 270 271- (void)viewDidAppear:(BOOL)animated 272{ 273 [super viewDidAppear:animated]; 274 275 /* we made it this far, remove lock on previous startup */ 276 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 277 [userDefaults removeObjectForKey:STATE_RESTORE_TRY_KEY]; 278 [userDefaults synchronize]; 279} 280 281/* called when we've become visible (possibly again, from app delegate applicationDidBecomeActive) */ 282- (void)viewIsVisible 283{ 284 if (webViewTabs.count == 0) { 285 if ([appDelegate areTesting]) { 286 [self addNewTabForURL:nil]; 287 } else { 288 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 289 NSString *homepage = [userDefaults stringForKey:@"homepage"]; 290 291 if (homepage == nil || [homepage isEqualToString:@""]) { 292 NSDictionary *se = [[appDelegate searchEngines] objectForKey:[userDefaults stringForKey:@"search_engine"]]; 293 homepage = [se objectForKey:@"homepage_url"]; 294 } 295 296 [self addNewTabForURL:[NSURL URLWithString:homepage]]; 297 } 298 } 299} 300 301- (void)viewIsNoLongerVisible 302{ 303 if ([urlField isFirstResponder]) { 304 [urlField resignFirstResponder]; 305 } 306} 307 308- (void)keyboardWillShow:(NSNotification *)notification 309{ 310 CGRect keyboardStart = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 311 CGRect keyboardEnd = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 312 313 /* on devices with a bluetooth keyboard attached, both values should be the same for a 0 height */ 314 keyboardHeight = keyboardStart.origin.y - keyboardEnd.origin.y; 315 316 [self viewDidLayoutSubviews]; 317} 318 319- (void)keyboardWillHide:(NSNotification *)notification 320{ 321 keyboardHeight = 0; 322 [self viewDidLayoutSubviews]; 323} 324 325- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator 326{ 327 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 328 329 [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) { 330 if (showingTabs) 331 [self showTabsWithCompletionBlock:nil]; 332 333 [self dismissPopover]; 334 } completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { 335 /* in case this doesn't get called automatically for some reason */ 336 [self viewDidLayoutSubviews]; 337 }]; 338} 339 340- (void)viewDidLayoutSubviews 341{ 342 float statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; 343 344 /* views are transforming and we may calculate things incorrectly here, so just ignore this request */ 345 if (showingTabs) 346 return; 347 348 /* keep the root view the size of the window minus the statusbar */ 349 self.view.frame = CGRectMake(0, statusBarHeight, [appDelegate window].bounds.size.width, [appDelegate window].bounds.size.height - statusBarHeight); 350 wrapper.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); 351 352 /* keep tabScroller the size of the root frame minus the toolbar */ 353 if (self.toolbarOnBottom) { 354 toolbar.frame = tabToolbar.frame = CGRectMake(0, self.view.bounds.size.height - TOOLBAR_HEIGHT - keyboardHeight, self.view.bounds.size.width, TOOLBAR_HEIGHT + keyboardHeight); 355 progressBar.frame = CGRectMake(0, 0, toolbar.bounds.size.width, 2); 356 tabToolbarHairline.frame = CGRectMake(0, 0, toolbar.bounds.size.width, 1); 357 358 tabScroller.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height - TOOLBAR_HEIGHT); 359 360 tabChooser.frame = CGRectMake(0, self.view.bounds.size.height - TOOLBAR_HEIGHT - 20, self.view.frame.size.width, 20); 361 } 362 else 363 { 364 toolbar.frame = tabToolbar.frame = CGRectMake(0, 0, self.view.bounds.size.width, TOOLBAR_HEIGHT); 365 progressBar.frame = CGRectMake(0, TOOLBAR_HEIGHT - 2, toolbar.frame.size.width, 2); 366 tabToolbarHairline.frame = CGRectMake(0, TOOLBAR_HEIGHT - 0.5, toolbar.frame.size.width, 0.5); 367 368 tabScroller.frame = CGRectMake(0, TOOLBAR_HEIGHT, self.view.bounds.size.width, self.view.bounds.size.height - TOOLBAR_HEIGHT); 369 370 tabChooser.frame = CGRectMake(0, self.view.bounds.size.height - 20 - 20, tabScroller.bounds.size.width, 20); 371 } 372 373 if (self.darkInterface) { 374 [[appDelegate window] setBackgroundColor:[UIColor darkGrayColor]]; 375 [wrapper setBackgroundColor:[UIColor darkGrayColor]]; 376 377 [tabScroller setBackgroundColor:[UIColor darkGrayColor]]; 378 379 [toolbar setBackgroundColor:[UIColor darkGrayColor]]; 380 [urlField setBackgroundColor:[UIColor grayColor]]; 381 [tabToolbarHairline setBackgroundColor:[UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1.0]]; 382 383 [tabAddButton setTintColor:[UIColor lightTextColor]]; 384 [tabDoneButton setTintColor:[UIColor lightTextColor]]; 385 [settingsButton setTintColor:[UIColor lightTextColor]]; 386 [tabsButton setTintColor:[UIColor lightTextColor]]; 387 [tabCount setTextColor:[UIColor lightTextColor]]; 388 389 [tabChooser setPageIndicatorTintColor:[UIColor lightGrayColor]]; 390 [tabChooser setCurrentPageIndicatorTintColor:[UIColor whiteColor]]; 391 } 392 else { 393 if ([self toolbarOnBottom]) 394 [[appDelegate window] setBackgroundColor:[UIColor blackColor]]; 395 else 396 [[appDelegate window] setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; 397 398 [wrapper setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; 399 400 [tabScroller setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; 401 [toolbar setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; 402 [urlField setBackgroundColor:[UIColor whiteColor]]; 403 [tabToolbarHairline setBackgroundColor:[UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1.0]]; 404 405 [tabAddButton setTintColor:[progressBar tintColor]]; 406 [tabDoneButton setTintColor:[progressBar tintColor]]; 407 [settingsButton setTintColor:[progressBar tintColor]]; 408 [tabsButton setTintColor:[progressBar tintColor]]; 409 [tabCount setTextColor:[progressBar tintColor]]; 410 411 [tabChooser setPageIndicatorTintColor:[UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1.0]]; 412 [tabChooser setCurrentPageIndicatorTintColor:[UIColor grayColor]]; 413 } 414 415 [self setNeedsStatusBarAppearanceUpdate]; 416 417 /* tabScroller.frame is now our actual webview viewing area */ 418 419 for (int i = 0; i < webViewTabs.count; i++) { 420 WebViewTab *wvt = webViewTabs[i]; 421 [wvt updateFrame:[self frameForTabIndex:i]]; 422 } 423 424 /* things relative to the toolbar */ 425 float y = ((TOOLBAR_HEIGHT - 1 - TOOLBAR_BUTTON_SIZE) / 2); 426 427 tabScroller.contentSize = CGSizeMake(tabScroller.frame.size.width * tabChooser.numberOfPages, tabScroller.frame.size.height); 428 [tabScroller setContentOffset:CGPointMake([self frameForTabIndex:curTabIndex].origin.x, 0) animated:NO]; 429 430 backButton.frame = CGRectMake(TOOLBAR_PADDING, y, TOOLBAR_BUTTON_SIZE, TOOLBAR_BUTTON_SIZE); 431 forwardButton.frame = CGRectMake(backButton.frame.origin.x + backButton.frame.size.width + TOOLBAR_PADDING, y, TOOLBAR_BUTTON_SIZE, TOOLBAR_BUTTON_SIZE); 432 433 settingsButton.frame = CGRectMake(tabScroller.frame.size.width - backButton.frame.size.width - TOOLBAR_PADDING, y, TOOLBAR_BUTTON_SIZE, TOOLBAR_BUTTON_SIZE); 434 tabsButton.frame = CGRectMake(settingsButton.frame.origin.x - backButton.frame.size.width - TOOLBAR_PADDING, y, TOOLBAR_BUTTON_SIZE, TOOLBAR_BUTTON_SIZE); 435 436 tabCount.frame = CGRectMake(tabsButton.frame.origin.x + 6, tabsButton.frame.origin.y + 12, 14, 10); 437 urlField.frame = [self frameForUrlField]; 438 439 if (bookmarks) { 440 if (self.toolbarOnBottom) 441 bookmarks.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, toolbar.frame.origin.y); 442 else 443 bookmarks.view.frame = CGRectMake(0, toolbar.frame.origin.y + toolbar.bounds.size.height, self.view.bounds.size.width, self.view.bounds.size.height); 444 } 445 446 [self updateSearchBarDetails]; 447} 448 449- (CGRect)frameForTabIndex:(NSUInteger)number 450{ 451 return CGRectMake((self.view.frame.size.width * number), 0, self.view.frame.size.width, tabScroller.frame.size.height); 452} 453 454- (CGRect)frameForUrlField 455{ 456 float x = forwardButton.frame.origin.x + forwardButton.frame.size.width + TOOLBAR_PADDING; 457 float y = (TOOLBAR_HEIGHT - 1 - tabsButton.frame.size.height) / 2; 458 float w = tabsButton.frame.origin.x - TOOLBAR_PADDING - forwardButton.frame.origin.x - forwardButton.frame.size.width - TOOLBAR_PADDING; 459 float h = tabsButton.frame.size.height; 460 461 if (backButton.hidden || [urlField isFirstResponder]) { 462 x -= backButton.frame.size.width + TOOLBAR_PADDING; 463 w += backButton.frame.size.width + TOOLBAR_PADDING; 464 } 465 466 if (forwardButton.hidden || [urlField isFirstResponder]) { 467 x -= forwardButton.frame.size.width + TOOLBAR_PADDING; 468 w += forwardButton.frame.size.width + TOOLBAR_PADDING; 469 } 470 471 return CGRectMake(x, y, w, h); 472} 473 474- (void)focusUrlField 475{ 476 [urlField becomeFirstResponder]; 477} 478 479- (NSMutableArray *)webViewTabs 480{ 481 return webViewTabs; 482} 483 484- (__strong WebViewTab *)curWebViewTab 485{ 486 if (webViewTabs.count > 0) 487 return webViewTabs[curTabIndex]; 488 else 489 return nil; 490} 491 492- (void)setCurTabIndex:(int)tab 493{ 494 if (curTabIndex == tab) 495 return; 496 497 curTabIndex = tab; 498 tabChooser.currentPage = tab; 499 500 for (int i = 0; i < webViewTabs.count; i++) { 501 WebViewTab *wvt = [webViewTabs objectAtIndex:i]; 502 [[[wvt webView] scrollView] setScrollsToTop:(i == tab)]; 503 } 504 505 if ([[self curWebViewTab] needsRefresh]) { 506 [[self curWebViewTab] refresh]; 507 } 508} 509 510- (WebViewTab *)addNewTabForURL:(NSURL *)url 511{ 512 return [self addNewTabForURL:url forRestoration:NO withCompletionBlock:nil]; 513} 514 515- (WebViewTab *)addNewTabForURL:(NSURL *)url forRestoration:(BOOL)restoration withCompletionBlock:(void(^)(BOOL))block 516{ 517 WebViewTab *wvt = [[WebViewTab alloc] initWithFrame:[self frameForTabIndex:webViewTabs.count] withRestorationIdentifier:(restoration ? [url absoluteString] : nil)]; 518 [wvt.webView.scrollView setDelegate:self]; 519 520 [webViewTabs addObject:wvt]; 521 [tabChooser setNumberOfPages:webViewTabs.count]; 522 [wvt setTabIndex:[NSNumber numberWithLong:(webViewTabs.count - 1)]]; 523 524 [tabCount setText:[NSString stringWithFormat:@"%lu", (long)tabChooser.numberOfPages]]; 525 526 [tabScroller setContentSize:CGSizeMake(wvt.viewHolder.frame.size.width * tabChooser.numberOfPages, wvt.viewHolder.frame.size.height)]; 527 [tabScroller addSubview:wvt.viewHolder]; 528 [tabScroller bringSubviewToFront:toolbar]; 529 530 void (^swapToTab)(BOOL) = ^(BOOL finished) { 531 [self setCurTabIndex:(int)webViewTabs.count - 1]; 532 533 [self slideToCurrentTabWithCompletionBlock:^(BOOL finished) { 534 if (url != nil) 535 [wvt loadURL:url]; 536 537 [self showTabsWithCompletionBlock:block]; 538 }]; 539 }; 540 541 if (!restoration) { 542 /* animate zooming out (if not already), switching to the new tab, then zoom back in */ 543 if (showingTabs) { 544 swapToTab(YES); 545 } 546 else if (webViewTabs.count > 1) { 547 [self showTabsWithCompletionBlock:swapToTab]; 548 } 549 else if (url != nil) { 550 [wvt loadURL:url]; 551 } 552 else if (block != nil) 553 block(YES); 554 } 555 556 return wvt; 557} 558 559- (void)addNewTabFromToolbar:(id)_id 560{ 561 [self addNewTabForURL:nil forRestoration:NO withCompletionBlock:^(BOOL finished) { 562 [urlField becomeFirstResponder]; 563 }]; 564} 565 566- (void)switchToTab:(NSNumber *)tabNumber 567{ 568 if ([tabNumber intValue] >= [[self webViewTabs] count]) 569 return; 570 571 if ([tabNumber intValue] == curTabIndex) 572 return; 573 574 WebViewTab *t = [[self webViewTabs] objectAtIndex:[tabNumber intValue]]; 575 void (^swapToTab)(BOOL) = ^(BOOL finished) { 576 [self setCurTabIndex:[[t tabIndex] intValue]]; 577 578 [self slideToCurrentTabWithCompletionBlock:^(BOOL finished) { 579 [self showTabsWithCompletionBlock:nil]; 580 }]; 581 }; 582 583 if (showingTabs) { 584 swapToTab(YES); 585 } 586 else if (webViewTabs.count > 1) { 587 [self showTabsWithCompletionBlock:swapToTab]; 588 } 589} 590 591- (void)reindexTabs 592{ 593 int i = 0; 594 BOOL seencur = NO; 595 596 for (WebViewTab *wvt in [self webViewTabs]) { 597 if (!seencur && [[wvt tabIndex] intValue] == curTabIndex) { 598 curTabIndex = i; 599 seencur = YES; 600 } 601 602 [wvt setTabIndex:[NSNumber numberWithInt:i]]; 603 604 i++; 605 } 606} 607 608- (void)removeTab:(NSNumber *)tabNumber 609{ 610 [self removeTab:tabNumber andFocusTab:[NSNumber numberWithInt:-1]]; 611} 612 613- (void)removeTab:(NSNumber *)tabNumber andFocusTab:(NSNumber *)toFocus 614{ 615 if (tabNumber.intValue > [webViewTabs count] - 1) 616 return; 617 618 if ([urlField isFirstResponder]) { 619 [urlField resignFirstResponder]; 620 return; 621 } 622 623 WebViewTab *wvt = (WebViewTab *)webViewTabs[tabNumber.intValue]; 624 625#ifdef TRACE 626 NSLog(@"[WebViewController] removing tab %@ (%@) and focusing %@", tabNumber, wvt.title.text, toFocus); 627#endif 628 int futureFocusNumber = toFocus.intValue; 629 if (futureFocusNumber > -1) { 630 if (futureFocusNumber == tabNumber.intValue) { 631 futureFocusNumber = -1; 632 } 633 else if (futureFocusNumber > tabNumber.intValue) { 634 futureFocusNumber--; 635 } 636 } 637 638 long wvtHash = [wvt hash]; 639 [[wvt viewHolder] removeFromSuperview]; 640 [webViewTabs removeObjectAtIndex:tabNumber.intValue]; 641 wvt = nil; 642 643 [[appDelegate cookieJar] clearNonWhitelistedDataForTab:wvtHash]; 644 645 [tabChooser setNumberOfPages:webViewTabs.count]; 646 [tabCount setText:[NSString stringWithFormat:@"%lu", (long)tabChooser.numberOfPages]]; 647 648 if (futureFocusNumber == -1) { 649 if (curTabIndex == tabNumber.intValue) { 650 if (webViewTabs.count > tabNumber.intValue && webViewTabs[tabNumber.intValue]) { 651 /* keep currentPage pointing at the page that shifted down to here */ 652 } 653 else if (tabNumber.intValue > 0 && webViewTabs[tabNumber.intValue - 1]) { 654 /* removed last tab, keep the previous one */ 655 [self setCurTabIndex:tabNumber.intValue - 1]; 656 } 657 else { 658 /* no tabs left, add one and zoom out */ 659 [self reindexTabs]; 660 [self addNewTabForURL:nil forRestoration:false withCompletionBlock:^(BOOL finished) { 661 [urlField becomeFirstResponder]; 662 }]; 663 return; 664 } 665 } 666 } 667 else { 668 [self setCurTabIndex:futureFocusNumber]; 669 } 670 671 [self reindexTabs]; 672 673 [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 674 tabScroller.contentSize = CGSizeMake([self frameForTabIndex:0].size.width * tabChooser.numberOfPages, [self frameForTabIndex:0].size.height); 675 676 for (int i = 0; i < webViewTabs.count; i++) { 677 WebViewTab *wvt = webViewTabs[i]; 678 679 [[wvt viewHolder] setTransform:CGAffineTransformIdentity]; 680 [[wvt viewHolder] setFrame:[self frameForTabIndex:i]]; 681 682 BOOL rotated = (wvt.viewHolder.frame.size.width > wvt.viewHolder.frame.size.height); 683 [wvt.viewHolder setTransform:CGAffineTransformMakeScale(rotated ? ZOOM_OUT_SCALE_ROTATED : ZOOM_OUT_SCALE, rotated ? ZOOM_OUT_SCALE_ROTATED : ZOOM_OUT_SCALE)]; 684 } 685 } completion:^(BOOL finished) { 686 [self setCurTabIndex:curTabIndex]; 687 688 [self slideToCurrentTabWithCompletionBlock:^(BOOL finished) { 689 showingTabs = true; 690 [self showTabs:nil]; 691 }]; 692 }]; 693} 694 695- (void)removeAllTabs 696{ 697 curTabIndex = 0; 698 699 [webViewTabs removeAllObjects]; 700 [tabChooser setNumberOfPages:0]; 701 702 [self updateSearchBarDetails]; 703} 704 705- (void)updateSearchBarDetails 706{ 707 /* TODO: cache curURL and only do anything here if it changed, these changes might be expensive */ 708 709 if (self.darkInterface) 710 [urlField setTextColor:[UIColor lightTextColor]]; 711 else 712 [urlField setTextColor:[UIColor darkTextColor]]; 713 714 if (urlField.isFirstResponder) { 715 /* focused, don't muck with the URL while it's being edited */ 716 [urlField setTextAlignment:NSTextAlignmentNatural]; 717 [urlField setLeftView:nil]; 718 } 719 else { 720 [urlField setTextAlignment:NSTextAlignmentCenter]; 721 BOOL isEV = NO; 722 if (self.curWebViewTab && self.curWebViewTab.secureMode >= WebViewTabSecureModeSecure) { 723 [urlField setLeftView:lockIcon]; 724 725 if (self.curWebViewTab.secureMode == WebViewTabSecureModeSecureEV) { 726 /* wait until the page is done loading */ 727 if ([progressBar progress] >= 1.0) { 728 [urlField setTextColor:[UIColor colorWithRed:0 green:(183.0/255.0) blue:(82.0/255.0) alpha:1.0]]; 729 730 if ([self.curWebViewTab.SSLCertificate evOrgName] == nil) 731 [urlField setText:@"Unknown Organization"]; 732 else 733 [urlField setText:self.curWebViewTab.SSLCertificate.evOrgName]; 734 735 isEV = YES; 736 } 737 } 738 } 739 else if (self.curWebViewTab && self.curWebViewTab.secureMode == WebViewTabSecureModeMixed) { 740 [urlField setLeftView:brokenLockIcon]; 741 } 742 else { 743 [urlField setLeftView:nil]; 744 } 745 746 if (!isEV) { 747 NSString *host; 748 if (self.curWebViewTab.url == nil) 749 host = @""; 750 else { 751 host = [self.curWebViewTab.url host]; 752 if (host == nil) 753 host = [self.curWebViewTab.url absoluteString]; 754 } 755 756 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^www\\d*\\." options:NSRegularExpressionCaseInsensitive error:nil]; 757 NSString *hostNoWWW = [regex stringByReplacingMatchesInString:host options:0 range:NSMakeRange(0, [host length]) withTemplate:@""]; 758 759 [urlField setText:hostNoWWW]; 760 761 if ([urlField.text isEqualToString:@""]) { 762 [urlField setTextAlignment:NSTextAlignmentLeft]; 763 } 764 } 765 } 766 767 backButton.enabled = (self.curWebViewTab && self.curWebViewTab.canGoBack); 768 if (backButton.enabled) { 769 [backButton setTintColor:(self.darkInterface ? [UIColor lightTextColor] : [progressBar tintColor])]; 770 } 771 else { 772 [backButton setTintColor:[UIColor grayColor]]; 773 } 774 775 forwardButton.hidden = !(self.curWebViewTab && self.curWebViewTab.canGoForward); 776 if (forwardButton.enabled) { 777 [forwardButton setTintColor:(self.darkInterface ? [UIColor lightTextColor] : [progressBar tintColor])]; 778 } 779 else { 780 [forwardButton setTintColor:[UIColor grayColor]]; 781 } 782 783 [urlField setFrame:[self frameForUrlField]]; 784} 785 786- (void)updateProgress 787{ 788 BOOL animated = YES; 789 float fadeAnimationDuration = 0.15; 790 float fadeOutDelay = 0.3; 791 792 float progress = [[[self curWebViewTab] progress] floatValue]; 793 if (progressBar.progress == progress) { 794 return; 795 } 796 else if (progress == 0.0) { 797 /* reset without animation, an actual update is probably coming right after this */ 798 progressBar.progress = 0.0; 799 return; 800 } 801 802#ifdef TRACE 803 NSLog(@"[Tab %@] loading progress of %@ at %f", self.curWebViewTab.tabIndex, [self.curWebViewTab.url absoluteString], progress); 804#endif 805 806 [self updateSearchBarDetails]; 807 808 if (progress >= 1.0) { 809 [progressBar setProgress:progress animated:NO]; 810 811 [UIView animateWithDuration:fadeAnimationDuration delay:fadeOutDelay options:UIViewAnimationOptionCurveLinear animations:^{ 812 progressBar.alpha = 0.0; 813 } completion:^(BOOL finished) { 814 [self updateSearchBarDetails]; 815 }]; 816 } 817 else { 818 [UIView animateWithDuration:(animated ? fadeAnimationDuration : 0.0) delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ 819 [progressBar setProgress:progress animated:YES]; 820 821 if (showingTabs) 822 progressBar.alpha = 0.0; 823 else 824 progressBar.alpha = 1.0; 825 } completion:nil]; 826 } 827} 828 829- (void)webViewTouched 830{ 831 if ([urlField isFirstResponder]) { 832 [urlField resignFirstResponder]; 833 } 834} 835 836- (void)textFieldDidBeginEditing:(UITextField *)textField 837{ 838 if (textField != urlField) 839 return; 840 841#ifdef TRACE 842 NSLog(@"[WebViewController] started editing"); 843#endif 844 845 [urlField setText:[self.curWebViewTab.url absoluteString]]; 846 847 if (bookmarks == nil) 848 [self showBookmarksForEditing:NO]; 849 850 [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ 851 [urlField setTextAlignment:NSTextAlignmentNatural]; 852 [backButton setHidden:true]; 853 [forwardButton setHidden:true]; 854 [urlField setFrame:[self frameForUrlField]]; 855 } completion:^(BOOL finished) { 856 [urlField performSelector:@selector(selectAll:) withObject:nil afterDelay:0.1]; 857 }]; 858 859 [self updateSearchBarDetails]; 860} 861 862- (void)textFieldDidEndEditing:(UITextField *)textField 863{ 864 if (textField != nil && textField != urlField) 865 return; 866 867#ifdef TRACE 868 NSLog(@"[WebViewController] ended editing with: %@", [textField text]); 869#endif 870 [self hideBookmarks]; 871 872 [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ 873 [urlField setTextAlignment:NSTextAlignmentCenter]; 874 [backButton setHidden:false]; 875 [forwardButton setHidden:!(self.curWebViewTab && self.curWebViewTab.canGoForward)]; 876 [urlField setFrame:[self frameForUrlField]]; 877 } completion:nil]; 878 879 [self updateSearchBarDetails]; 880} 881 882- (BOOL)textFieldShouldReturn:(UITextField *)textField { 883 if (textField != urlField) { 884 return YES; 885 } 886 887 [self prepareForNewURLFromString:urlField.text]; 888 889 return NO; 890} 891 892- (void)prepareForNewURLFromString:(NSString *)url 893{ 894 /* user is shifting to a new place, probably a good time to clear old data */ 895 [[appDelegate cookieJar] clearAllOldNonWhitelistedData]; 896 897 NSURL *enteredURL = [NSURL URLWithString:url]; 898 899 /* for some reason NSURL thinks "example.com:9091" should be "example.com" as the scheme with no host, so fix up first */ 900 if ([enteredURL host] == nil && [enteredURL scheme] != nil && [enteredURL resourceSpecifier] != nil) 901 enteredURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", url]]; 902 903 if (![enteredURL scheme] || [[enteredURL scheme] isEqualToString:@""]) { 904 /* no scheme so if it has a space or no dots, assume it's a search query */ 905 if ([url containsString:@" "] || ![url containsString:@"."]) { 906 [[self curWebViewTab] searchFor:url]; 907 enteredURL = nil; 908 } 909 else 910 enteredURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", url]]; 911 } 912 913 [urlField resignFirstResponder]; /* will unfocus and call textFieldDidEndEditing */ 914 915 if (enteredURL != nil) 916 [[self curWebViewTab] loadURL:enteredURL]; 917} 918 919- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 920{ 921 if (scrollView != tabScroller) 922 return; 923 924 int page = round(scrollView.contentOffset.x / scrollView.frame.size.width); 925 if (page < 0) { 926 page = 0; 927 } 928 else if (page > tabChooser.numberOfPages) { 929 page = (int)tabChooser.numberOfPages; 930 } 931 [self setCurTabIndex:page]; 932} 933 934- (void)goBack:(id)_id 935{ 936 [self.curWebViewTab goBack]; 937} 938 939- (void)goForward:(id)_id 940{ 941 [self.curWebViewTab goForward]; 942} 943 944- (void)refresh 945{ 946 [[self curWebViewTab] refresh]; 947} 948 949- (void)forceRefresh 950{ 951 [[self curWebViewTab] forceRefresh]; 952} 953 954- (void)showPopover:(id)_id 955{ 956 popover = [[WYPopoverController alloc] initWithContentViewController:[[WebViewMenuController alloc] init]]; 957 [popover setDelegate:self]; 958 959 [popover beginThemeUpdates]; 960 [popover setTheme:[WYPopoverTheme themeForIOS7]]; 961 [popover.theme setDimsBackgroundViewsTintColor:NO]; 962 [popover.theme setOuterCornerRadius:4]; 963 [popover.theme setOuterShadowBlurRadius:8]; 964 [popover.theme setOuterShadowColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.75]]; 965 [popover.theme setOuterShadowOffset:CGSizeMake(0, 2)]; 966 [popover.theme setOverlayColor:[UIColor clearColor]]; 967 if ([self darkInterface]) 968 [popover.theme setTintColor:[UIColor darkGrayColor]]; 969 [popover endThemeUpdates]; 970 971 [popover presentPopoverFromRect:CGRectMake(settingsButton.frame.origin.x, toolbar.frame.origin.y + settingsButton.frame.origin.y + settingsButton.frame.size.height - 30, settingsButton.frame.size.width, settingsButton.frame.size.height) inView:self.view permittedArrowDirections:WYPopoverArrowDirectionAny animated:YES options:WYPopoverAnimationOptionFadeWithScale]; 972} 973 974- (void)dismissPopover 975{ 976 [popover dismissPopoverAnimated:YES]; 977} 978 979- (BOOL)popoverControllerShouldDismissPopover:(WYPopoverController *)controller 980{ 981 return YES; 982} 983 984- (void)settingsViewController:(IASKAppSettingsViewController *)sender buttonTappedForSpecifier:(IASKSpecifier *)specifier 985{ 986 if ([[specifier key] isEqualToString:@"open_donation"]) { 987#ifdef SHOW_DONATION_CONTROLLER 988 DonationViewController *dvc = [[DonationViewController alloc] initWithNibName:nil bundle:nil]; 989 [[sender navigationController] pushViewController:dvc animated:YES]; 990#endif 991 } 992} 993 994- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender 995{ 996 [self dismissViewControllerAnimated:YES completion:nil]; 997 998 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 999 [URLInterceptor setSendDNT:[userDefaults boolForKey:@"send_dnt"]]; 1000 [[appDelegate cookieJar] setOldDataSweepTimeout:[NSNumber numberWithInteger:[userDefaults integerForKey:@"old_data_sweep_mins"]]]; 1001 1002 self.toolbarOnBottom = [userDefaults boolForKey:@"toolbar_on_bottom"]; 1003 self.darkInterface = [userDefaults boolForKey:@"dark_interface"]; 1004} 1005 1006- (void)showTabs:(id)_id 1007{ 1008 return [self showTabsWithCompletionBlock:nil]; 1009} 1010 1011- (void)showTabsWithCompletionBlock:(void(^)(BOOL))block 1012{ 1013 if (showingTabs == false) { 1014 /* zoom out to show all tabs */ 1015 1016 /* make sure no text is selected */ 1017 [urlField resignFirstResponder]; 1018 1019 [UIView animateWithDuration:0.15 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) { 1020 if (!self.toolbarOnBottom) 1021 tabScroller.frame = CGRectMake(tabScroller.frame.origin.x, (TOOLBAR_HEIGHT / 2), tabScroller.frame.size.width, tabScroller.frame.size.height); 1022 1023 for (int i = 0; i < webViewTabs.count; i++) 1024 [(WebViewTab *)webViewTabs[i] zoomOut]; 1025 1026 tabChooser.hidden = false; 1027 toolbar.hidden = true; 1028 tabToolbar.hidden = false; 1029 progressBar.alpha = 0.0; 1030 } completion:block]; 1031 1032 tabScroller.contentOffset = CGPointMake([self frameForTabIndex:curTabIndex].origin.x, 0); 1033 tabScroller.scrollEnabled = YES; 1034 tabScroller.pagingEnabled = YES; 1035 1036 UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedOnWebViewTab:)]; 1037 singleTapGestureRecognizer.numberOfTapsRequired = 1; 1038 singleTapGestureRecognizer.enabled = YES; 1039 singleTapGestureRecognizer.cancelsTouchesInView = NO; 1040 [tabScroller addGestureRecognizer:singleTapGestureRecognizer]; 1041 } 1042 else { 1043 [UIView animateWithDuration:0.15 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) { 1044 if (!self.toolbarOnBottom) 1045 tabScroller.frame = CGRectMake(tabScroller.frame.origin.x, TOOLBAR_HEIGHT, tabScroller.frame.size.width, tabScroller.frame.size.height); 1046 1047 for (int i = 0; i < webViewTabs.count; i++) 1048 [(WebViewTab *)webViewTabs[i] zoomNormal]; 1049 1050 tabChooser.hidden = true; 1051 toolbar.hidden = false; 1052 tabToolbar.hidden = true; 1053 progressBar.alpha = (progressBar.progress > 0.0 && progressBar.progress < 1.0 ? 1.0 : 0.0); 1054 } completion:block]; 1055 1056 tabScroller.scrollEnabled = NO; 1057 tabScroller.pagingEnabled = NO; 1058 1059 [self updateSearchBarDetails]; 1060 } 1061 1062 showingTabs = !showingTabs; 1063} 1064 1065- (void)doneWithTabsButton:(id)_id 1066{ 1067 [self showTabs:nil]; 1068} 1069 1070- (void)showSSLCertificate 1071{ 1072 if ([[self curWebViewTab] SSLCertificate] == nil) 1073 return; 1074 1075 SSLCertificateViewController *scvc = [[SSLCertificateViewController alloc] initWithSSLCertificate:[[self curWebViewTab] SSLCertificate]]; 1076 scvc.title = [[[self curWebViewTab] url] host]; 1077 1078 UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:scvc]; 1079 [self presentViewController:navController animated:YES completion:nil]; 1080} 1081 1082- (void)tappedOnWebViewTab:(UITapGestureRecognizer *)gesture 1083{ 1084 if (!showingTabs) { 1085 if ([urlField isFirstResponder]) { 1086 [urlField resignFirstResponder]; 1087 } 1088 1089 return; 1090 } 1091 1092 CGPoint point = [gesture locationInView:self.curWebViewTab.viewHolder]; 1093 1094 /* fuzz a bit to make it easier to tap */ 1095 int fuzz = 8; 1096 CGRect closerFrame = CGRectMake(self.curWebViewTab.closer.frame.origin.x - fuzz, self.curWebViewTab.closer.frame.origin.y - fuzz, self.curWebViewTab.closer.frame.size.width + (fuzz * 2), self.curWebViewTab.closer.frame.size.width + (fuzz * 2)); 1097 if (CGRectContainsPoint(closerFrame, point)) { 1098 [self removeTab:[NSNumber numberWithLong:curTabIndex]]; 1099 } 1100 else { 1101 [self showTabs:nil]; 1102 } 1103} 1104 1105- (void)slideToCurrentTabWithCompletionBlock:(void(^)(BOOL))block 1106{ 1107 [self updateProgress]; 1108 1109 [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 1110 [tabScroller setContentOffset:CGPointMake([self frameForTabIndex:curTabIndex].origin.x, 0) animated:NO]; 1111 } completion:block]; 1112} 1113 1114- (IBAction)slideToCurrentTab:(id)_id 1115{ 1116 [self slideToCurrentTabWithCompletionBlock:nil]; 1117} 1118 1119- (NSString *)buildDefaultUserAgent 1120{ 1121 /* 1122 * Some sites do mobile detection by looking for Safari in the UA, so make us look like Mobile Safari 1123 * 1124 * from "Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H321" 1125 * to "Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4" 1126 */ 1127 1128 UIWebView *twv = [[UIWebView alloc] initWithFrame:CGRectZero]; 1129 NSString *ua = [twv stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; 1130 1131 NSMutableArray *uapieces = [[NSMutableArray alloc] initWithArray:[ua componentsSeparatedByString:@" "]]; 1132 NSString *uamobile = uapieces[uapieces.count - 1]; 1133 1134 /* assume safari major version will match ios major */ 1135 NSArray *osv = [[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."]; 1136 uapieces[uapieces.count - 1] = [NSString stringWithFormat:@"Version/%@.0", osv[0]]; 1137 1138 [uapieces addObject:uamobile]; 1139 1140 /* now tack on "Safari/XXX.X.X" from webkit version */ 1141 for (id j in uapieces) { 1142 if ([(NSString *)j containsString:@"AppleWebKit/"]) { 1143 [uapieces addObject:[(NSString *)j stringByReplacingOccurrencesOfString:@"AppleWebKit" withString:@"Safari"]]; 1144 break; 1145 } 1146 } 1147 1148 return [uapieces componentsJoinedByString:@" "]; 1149} 1150 1151- (void)showBookmarksForEditing:(BOOL)editing 1152{ 1153 if (bookmarks) 1154 return; 1155 1156 bookmarks = [[BookmarkController alloc] init]; 1157 1158 if (editing) { 1159 UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookmarks]; 1160 [self presentViewController:navController animated:YES completion:nil]; 1161 } else { 1162 bookmarks.embedded = true; 1163 1164 if (self.toolbarOnBottom) 1165 /* we can't size according to keyboard height because we don't know it yet, so we'll just put it full height below the toolbar and we'll update it when the keyboard shows up */ 1166 bookmarks.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); 1167 else 1168 bookmarks.view.frame = CGRectMake(0, toolbar.frame.size.height + toolbar.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height); 1169 1170 [self addChildViewController:bookmarks]; 1171 [self.view insertSubview:[bookmarks view] belowSubview:toolbar]; 1172 } 1173} 1174 1175- (void)hideBookmarks 1176{ 1177 if (!bookmarks) 1178 return; 1179 1180 [[bookmarks view] removeFromSuperview]; 1181 [bookmarks removeFromParentViewController]; 1182 bookmarks = nil; 1183} 1184 1185@end