35 |
36 | static NSString *const kGetUserMedia = @"getUserMedia";
37 | static NSString *const kGetIpAndPort = @"qd_v1_getLocal";
38 | static NSString *const kConsoleLog = @"console.log";
39 |
40 | static NSString *startHtml = @"Console log";
41 | static NSString *logHtml = @"document.getElementById('log').innerText += \"%@\n\";window.scrollTo(0, document.body.scrollHeight);";
42 | static NSString *logDividerHtml = @"";
43 | static NSString *errorDividerHtml = @"
";
44 |
45 | #define kDefaultStartURL @"http://www.openwebrtc.org/bowser"
46 | #define kSearchEngineURL @"http://www.google.com/search?q=%@"
47 | #define kBridgeLocalURL @"http://localhost:10717/owr.js"
48 |
49 | @interface BowserViewController ()
50 |
51 | @property (nonatomic, strong) NSMutableArray *consoleLogArray;
52 | @property (nonatomic, strong) NSMutableDictionary *renderers;
53 |
54 | - (void)consoleLog:(NSString *)logString isError:(BOOL)isError;
55 |
56 | @end
57 |
58 | @implementation BowserViewController
59 |
60 | - (void)viewDidLoad
61 | {
62 | [super viewDidLoad];
63 |
64 | NSLog(@"BowserViewController viewDidLoad");
65 |
66 | self.browserView.scrollView.delegate = self;
67 | self.browserView.owrDelegate = self;
68 | self.consoleLogView.scrollView.scrollsToTop = NO;
69 | self.consoleLogView.scrollView.bounces = NO;
70 | self.headerView.scrollsToTop = NO;
71 | self.headerView.contentSize = CGSizeMake(self.view.bounds.size.width + 1, self.headerView.bounds.size.height);
72 | consoleIsVisible = NO;
73 | bookmarksAreVisible = NO;
74 | NSError *error;
75 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
76 | NSString *documentsDirectory = [paths objectAtIndex:0];
77 | historyFilePath = [documentsDirectory stringByAppendingPathComponent:@"BowserHistory.plist"];
78 | bookmarksFilePath = [documentsDirectory stringByAppendingPathComponent:@"Bookmarks.plist"];
79 |
80 | NSFileManager *fileManager = [NSFileManager defaultManager];
81 |
82 | if (![fileManager fileExistsAtPath:historyFilePath]) {
83 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"BowserHistory" ofType:@"plist"];
84 | [fileManager copyItemAtPath:filePath toPath:historyFilePath error:&error];
85 | }
86 | if (![fileManager fileExistsAtPath:bookmarksFilePath]) {
87 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Bookmarks" ofType:@"plist"];
88 | [fileManager copyItemAtPath:filePath toPath:bookmarksFilePath error:&error];
89 | }
90 | bowserHistory = [[NSMutableArray alloc] initWithContentsOfFile:historyFilePath];
91 |
92 | canChange = YES;
93 | headerIsAbove = NO;
94 |
95 | self.historyTableView.layer.borderWidth = 2.0;
96 | self.historyTableView.layer.borderColor = [UIColor blackColor].CGColor;
97 | self.historyTableView.layer.shadowColor = [UIColor grayColor].CGColor;
98 | self.historyTableView.layer.shadowRadius = 5.0;
99 | self.historyTableView.layer.shadowOpacity = 0.7;
100 |
101 | // Make native video elements
102 | self.selfView = [[OpenWebRTCVideoView alloc] initWithFrame:CGRectZero];
103 | self.remoteView = [[OpenWebRTCVideoView alloc] initWithFrame:CGRectZero];
104 |
105 | // TODO conflict: Maybe not add them here?
106 | //[self.browserView.scrollView addSubview:self.remoteView];
107 | //[self.browserView.scrollView addSubview:self.selfView];
108 |
109 | self.renderers = [NSMutableDictionary dictionary];
110 |
111 | [self.headerView addSubview:self.bookmarkButton];
112 | [self.consoleLogView loadHTMLString:[startHtml stringByAppendingString:@"
"] baseURL:nil];
113 |
114 | // Observe loading progress.
115 | [self.browserView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL];
116 |
117 | [self setOverlayVideoRenderingEnabled:NO];
118 | }
119 |
120 | - (void)viewDidAppear:(BOOL)animated
121 | {
122 | static BOOL isInitialLoad = YES;
123 | if (!isInitialLoad)
124 | return;
125 |
126 | BowserAppDelegate *bowserAppDelegate = (BowserAppDelegate *)[UIApplication sharedApplication].delegate;
127 |
128 | [bowserAppDelegate addObserver:self forKeyPath:@"launchURL" options:NSKeyValueObservingOptionNew context:nil];
129 |
130 | NSString *startURL = [[NSUserDefaults standardUserDefaults] objectForKey:@"lastURL"];
131 |
132 | if (![[NSUserDefaults standardUserDefaults] boolForKey:@"has_started"] || !startURL) {
133 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"has_started"];
134 | startURL = kDefaultStartURL;
135 | }
136 |
137 | dispatch_queue_t queue = dispatch_queue_create("OWR init queue", NULL);
138 | dispatch_async(queue, ^{
139 |
140 | [self performSelector:@selector(presentInitializingMessage)
141 | onThread:[NSThread mainThread]
142 | withObject:nil
143 | waitUntilDone:NO];
144 |
145 | // Initialisation is a blocking.
146 | [OpenWebRTCViewController initOpenWebRTC];
147 |
148 | [self dismissViewControllerAnimated:YES completion:^{
149 | // Let's get started...
150 | self.urlField.text = startURL;
151 | [self loadRequestWithURL:startURL];
152 | }];
153 | });
154 |
155 | isInitialLoad = NO;
156 | }
157 |
158 | - (void)presentInitializingMessage
159 | {
160 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Initializing"
161 | message:@"Please wait..."
162 | preferredStyle:UIAlertControllerStyleAlert];
163 | [self presentViewController:alert animated:YES completion:nil];
164 | }
165 |
166 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
167 | {
168 | if ([keyPath isEqualToString:@"launchURL"]) {
169 | NSString *startURL = change[@"new"];
170 |
171 | if(![startURL isEqual:nil] && ![startURL isEqualToString:@""]){
172 | self.urlField.text = startURL;
173 |
174 | [self loadRequestWithURL:startURL];
175 | }
176 | } else if ([keyPath isEqualToString:@"estimatedProgress"] && object == self.browserView) {
177 | [self webviewProgress:self.browserView.estimatedProgress];
178 | } else {
179 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
180 | }
181 | }
182 |
183 | - (IBAction)reloadButtonTapped:(id)sender
184 | {
185 | [[NSURLCache sharedURLCache] removeAllCachedResponses];
186 | [self loadRequestWithURL:self.lastURL];
187 | }
188 |
189 | - (IBAction)toggleBookmarks
190 | {
191 | UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Bowser Options"
192 | delegate:self
193 | cancelButtonTitle:@"Close"
194 | destructiveButtonTitle:nil
195 | otherButtonTitles:
196 | @"Clear History",
197 | consoleIsVisible? @"Hide Console": @"Show Console",
198 | @"About Bowser",
199 | @"Bookmarks",
200 | @"Add bookmark", nil];
201 | [actionSheet showInView:self.view];
202 | }
203 |
204 | - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
205 | {
206 | if (buttonIndex == BowserMenuOptionClearHistory) {
207 | [bowserHistory removeAllObjects];
208 | [[NSURLCache sharedURLCache] removeAllCachedResponses];
209 | } else if (buttonIndex == BowserMenuOptionShowConsole) {
210 | if (consoleIsVisible) {
211 | [self slideDownView:self.consoleLogView];
212 | } else {
213 | [self slideUpView:self.consoleLogView];
214 | }
215 | consoleIsVisible = !consoleIsVisible;
216 | } else if (buttonIndex == BowserMenuOptionAboutPage) {
217 | dispatch_async(dispatch_get_main_queue(), ^{
218 | [self performSegueWithIdentifier:@"aboutPageSegue" sender:self];
219 | });
220 | } else if (buttonIndex == BowserMenuOptionShowBookmarks) {
221 | dispatch_async(dispatch_get_main_queue(), ^{
222 | [self performSegueWithIdentifier:@"bookmarksSegue" sender:self];
223 | });
224 | } else if (buttonIndex == BowserMenuOptionAddBookmark) {
225 | dispatch_async(dispatch_get_main_queue(), ^{
226 | [self performSegueWithIdentifier:@"addBookmarkSegue" sender:self];
227 | });
228 | }
229 | }
230 |
231 | #pragma mark textfield delegate methods
232 |
233 | - (BOOL)textFieldShouldReturn:(UITextField *)textField
234 | {
235 | NSString *urlString;
236 | NSUInteger colonPos = [textField.text rangeOfString:@":"].location;
237 | NSUInteger dotPos = [textField.text rangeOfString:@"."].location;
238 |
239 | if (colonPos != NSNotFound && colonPos <= 10 && (dotPos == NSNotFound || colonPos < dotPos))
240 | urlString = textField.text;
241 | else if (dotPos == NSNotFound)
242 | urlString = [NSString stringWithFormat:kSearchEngineURL, textField.text];
243 | else
244 | urlString = [NSString stringWithFormat:@"http://%@", textField.text];
245 |
246 | [self.browserView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
247 | textField.text = urlString;
248 | [textField resignFirstResponder];
249 | return YES;
250 | }
251 |
252 | - (void)textFieldDidBeginEditing:(UITextField *)textField
253 | {
254 | [self.historyTableView reloadData];
255 | self.historyTableView.hidden = NO;
256 |
257 | if (textField.text.length > 0) {
258 | [textField selectAll:nil];
259 | }
260 | }
261 |
262 | - (void)textFieldDidEndEditing:(UITextField *)textField
263 | {
264 | self.historyTableView.hidden = YES;
265 | if (textField.text.length == 0) {
266 | textField.text = self.lastURL;
267 | }
268 | }
269 |
270 | - (IBAction)urlFieldValueChanged:(id)sender
271 | {
272 | if (!self.urlField.isEditing) {
273 | return;
274 | }
275 | NSString *currentText = self.urlField.text;
276 | if (currentText.length == 0) {
277 | [self.historyTableView reloadData];
278 | }
279 | if (currentText.length == 1) {
280 | filteredHistory = [bowserHistory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"title BEGINSWITH[cd] %@", currentText]];
281 | }
282 | if (currentText.length > 1) {
283 | filteredHistory = [bowserHistory filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"url CONTAINS[cd] %@ or title CONTAINS[cd] %@", currentText, currentText]];
284 | }
285 | [self.historyTableView reloadData];
286 |
287 | }
288 |
289 | - (void)sortHistoryArray
290 | {
291 | NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"url" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
292 | [bowserHistory sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
293 | }
294 |
295 | - (void)viewDidUnload
296 | {
297 | [self setBookMarkView:nil];
298 | [self setBrowserView:nil];
299 | [self setHeaderView:nil];
300 | [self setUrlField:nil];
301 | [self setProgressBar:nil];
302 | [self setBookmarkButton:nil];
303 | [self setHistoryTableView:nil];
304 | [self.browserView removeObserver:self forKeyPath:@"estimatedProgress"];
305 |
306 | [super viewDidUnload];
307 | }
308 |
309 | #pragma mark scroll view delegate methods
310 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
311 | {
312 | if ([scrollView isEqual:self.browserView.scrollView]) {
313 | if (scrollView.contentOffset.y < 0) {
314 | scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
315 | } else {
316 | if (scrollView.scrollIndicatorInsets.top != 0) {
317 | scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 0, 0);
318 | }
319 | }
320 | }
321 | }
322 |
323 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
324 | {
325 | if ([scrollView isEqual:self.headerView]) {
326 | CGFloat scrollOffset = scrollView.contentOffset.x;
327 | if (scrollOffset<-40.0) {
328 | [self.browserView goBack];
329 | } else if (scrollOffset>40.0) {
330 | [self.browserView goForward];
331 | }
332 | }
333 | if ([scrollView isEqual:self.browserView.scrollView]) {
334 | CGFloat yOffset = scrollView.contentOffset.y;
335 | CGFloat xOffset = scrollView.contentOffset.x;
336 | CGFloat headerHeight = self.headerView.frame.size.height;
337 |
338 | if (headerIsAbove) {
339 | if (yOffset < - 20) {
340 | self.browserView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
341 | self.browserView.scrollView.contentOffset = CGPointMake(xOffset, yOffset - headerHeight);
342 | headerIsAbove = NO;
343 |
344 | // TODO: Remove status bar, preferably animated.
345 | }
346 | } else if (yOffset < - headerHeight) {
347 | self.browserView.frame = CGRectMake(0, headerHeight, self.view.frame.size.width, self.view.frame.size.height - headerHeight);
348 | self.browserView.scrollView.contentOffset = CGPointMake(xOffset, yOffset + headerHeight);
349 | headerIsAbove = YES;
350 | }
351 | }
352 | }
353 |
354 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
355 | {
356 | canChange = YES;
357 | }
358 |
359 | #pragma mark webview delegate stuff
360 |
361 | - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
362 | {
363 | [self.renderers enumerateKeysAndObjectsUsingBlock:^(NSString *tag, OpenWebRTCWebView *videoView, BOOL *stop) {
364 | owr_window_registry_unregister(owr_window_registry_get(), [tag UTF8String]);
365 | [videoView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.3];
366 | }];
367 | [self.renderers removeAllObjects];
368 |
369 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
370 | self.urlField.text = webView.URL.absoluteString;
371 |
372 | NSLog(@"webViewDidStartLoading... %@", webView.URL.absoluteString);
373 | self.progressBar.hidden = NO;
374 | }
375 |
376 | /*
377 | TODO: Adapt to WKWebView? Maybe webView:didCommitNavigation: above is enough.
378 |
379 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
380 | {
381 | if (navigationType != UIWebViewNavigationTypeOther && !self.urlField.isEditing && !self.urlField.isFirstResponder) {
382 | self.urlField.text = request.URL.absoluteString;
383 | }
384 | return YES;
385 | }
386 | */
387 |
388 | - (void)newVideoRect:(CGRect)rect rotation:(int)degrees tag:(NSString *)tag
389 | {
390 | OpenWebRTCVideoView *videoView = [self.renderers valueForKey:tag];
391 |
392 | if (!videoView) {
393 | videoView = [[OpenWebRTCVideoView alloc] initWithFrame:rect];
394 | [self.renderers setObject:videoView forKey:tag];
395 | [self.browserView.scrollView addSubview:videoView];
396 | owr_window_registry_register(owr_window_registry_get(), [tag UTF8String], (__bridge gpointer)videoView);
397 | }
398 |
399 | videoView.transform = CGAffineTransformMakeRotation(2 * M_PI * degrees / 360);
400 | videoView.frame = rect;
401 | }
402 |
403 | - (void)webviewProgress:(float)progress
404 | {
405 | [self.progressBar setProgress:progress];
406 | if (progress == 0) {
407 | self.progressBar.hidden = YES;
408 | } else if (progress == 1.0) {
409 | // Let the user see that loading completed.
410 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
411 | self.progressBar.hidden = YES;
412 | });
413 | }
414 | }
415 |
416 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
417 | {
418 | NSURL *currentURL = webView.URL;
419 | self.urlField.text = currentURL.absoluteString;
420 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
421 | self.lastURL = currentURL.absoluteString;
422 |
423 | [[NSUserDefaults standardUserDefaults] setValue:self.lastURL forKey:@"lastURL"];
424 | NSLog(@"webViewDidFinishLoading... %@", self.lastURL);
425 |
426 | BOOL urlAlreadyExists = NO;
427 | for (NSDictionary *historyPost in bowserHistory) {
428 | if([[historyPost objectForKey:@"url"] rangeOfString:currentURL.absoluteString].location != NSNotFound){
429 | urlAlreadyExists = YES;
430 | break;
431 | }
432 | }
433 | if (!urlAlreadyExists) {
434 | NSString *pageTitle = webView.title;
435 | if (pageTitle.length == 0) {
436 | pageTitle = @"No title";
437 | }
438 | NSString *domain = [NSString stringWithFormat:@"%@://%@", currentURL.scheme, currentURL.host];
439 | NSDictionary *newHistoryPost = [NSDictionary dictionaryWithObjectsAndKeys:currentURL.absoluteString, @"url", pageTitle, @"title", domain, @"domain", nil];
440 | [bowserHistory addObject:newHistoryPost];
441 | [self sortHistoryArray];
442 | }
443 | // Re-set console.log()
444 | self.consoleLogArray = nil;
445 | [self.consoleLogView loadHTMLString:[startHtml stringByAppendingString:@"