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