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