iOS web browser with a focus on security and privacy
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