53b9723577ebdf83ce10fd5f4c6f3586523f8066
[WebKit-https.git] / Tools / MiniBrowser / mac / WK2BrowserWindowController.m
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "WK2BrowserWindowController.h"
27
28 #if WK_API_ENABLED
29
30 #import "AppDelegate.h"
31 #import "SettingsController.h"
32 #import <WebKit/WKFrameInfo.h>
33 #import <WebKit/WKNavigationDelegate.h>
34 #import <WebKit/WKPreferencesPrivate.h>
35 #import <WebKit/WKUIDelegate.h>
36 #import <WebKit/WKWebViewConfigurationPrivate.h>
37 #import <WebKit/WKWebViewPrivate.h>
38 #import <WebKit/WebNSURLExtras.h>
39
40 static void* keyValueObservingContext = &keyValueObservingContext;
41
42 @interface WK2BrowserWindowController () <WKNavigationDelegate, WKUIDelegate>
43 @end
44
45 @implementation WK2BrowserWindowController {
46     WKWebViewConfiguration *_configuration;
47     WKWebView *_webView;
48     BOOL _zoomTextOnly;
49     BOOL _isPrivateBrowsingWindow;
50
51     BOOL _useMinimumViewSize;
52 }
53
54 - (void)awakeFromNib
55 {
56     _webView = [[WKWebView alloc] initWithFrame:[containerView bounds] configuration:_configuration];
57     [self didChangeSettings];
58
59     _webView.allowsMagnification = YES;
60     _webView.allowsBackForwardNavigationGestures = YES;
61
62     [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
63     [containerView addSubview:_webView];
64
65     [progressIndicator bind:NSHiddenBinding toObject:_webView withKeyPath:@"loading" options:@{ NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName }];
66     [progressIndicator bind:NSValueBinding toObject:_webView withKeyPath:@"estimatedProgress" options:nil];
67
68     [_webView addObserver:self forKeyPath:@"title" options:0 context:keyValueObservingContext];
69     [_webView addObserver:self forKeyPath:@"URL" options:0 context:keyValueObservingContext];
70
71     _webView.navigationDelegate = self;
72     _webView.UIDelegate = self;
73     
74     _zoomTextOnly = NO;
75 }
76
77 - (instancetype)initWithConfiguration:(WKWebViewConfiguration *)configuration
78 {
79     if (!(self = [super initWithWindowNibName:@"BrowserWindow"]))
80         return nil;
81
82     _configuration = [configuration copy];
83     _isPrivateBrowsingWindow = !_configuration.websiteDataStore.isPersistent;
84
85     return self;
86 }
87
88 - (void)dealloc
89 {
90     [_webView removeObserver:self forKeyPath:@"title"];
91     [_webView removeObserver:self forKeyPath:@"URL"];
92     
93     [progressIndicator unbind:NSHiddenBinding];
94     [progressIndicator unbind:NSValueBinding];
95
96     [_webView release];
97     [_configuration release];
98
99     [super dealloc];
100 }
101
102 - (IBAction)fetch:(id)sender
103 {
104     [urlText setStringValue:[self addProtocolIfNecessary:[urlText stringValue]]];
105
106     [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL _web_URLWithUserTypedString:[urlText stringValue]]]];
107 }
108
109 - (IBAction)showHideWebView:(id)sender
110 {
111     BOOL hidden = ![_webView isHidden];
112     
113     [_webView setHidden:hidden];
114 }
115
116 - (IBAction)removeReinsertWebView:(id)sender
117 {
118     if ([_webView window]) {
119         [_webView retain];
120         [_webView removeFromSuperview]; 
121     } else {
122         [containerView addSubview:_webView];
123         [_webView release];
124     }
125 }
126
127 static CGFloat viewScaleForMenuItemTag(NSInteger tag)
128 {
129     if (tag == 1)
130         return 1;
131     if (tag == 2)
132         return 0.75;
133     if (tag == 3)
134         return 0.5;
135     if (tag == 4)
136         return 0.25;
137
138     return 1;
139 }
140
141 - (IBAction)setScale:(id)sender
142 {
143     CGFloat scale = viewScaleForMenuItemTag([sender tag]);
144     CGFloat oldScale = [_webView _viewScale];
145
146     if (scale == oldScale)
147         return;
148
149     [_webView _setLayoutMode:_WKLayoutModeDynamicSizeComputedFromViewScale];
150
151     NSRect oldFrame = self.window.frame;
152     NSSize newFrameSize = NSMakeSize(oldFrame.size.width * (scale / oldScale), oldFrame.size.height * (scale / oldScale));
153     [self.window setFrame:NSMakeRect(oldFrame.origin.x, oldFrame.origin.y - (newFrameSize.height - oldFrame.size.height), newFrameSize.width, newFrameSize.height) display:NO animate:NO];
154
155     [_webView _setViewScale:scale];
156 }
157
158 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
159 {
160     SEL action = [menuItem action];
161
162     if (action == @selector(zoomIn:))
163         return [self canZoomIn];
164     if (action == @selector(zoomOut:))
165         return [self canZoomOut];
166     if (action == @selector(resetZoom:))
167         return [self canResetZoom];
168     
169     // Disabled until missing WK2 functionality is exposed via API/SPI.
170     if (action == @selector(dumpSourceToConsole:)
171         || action == @selector(find:)
172         || action == @selector(forceRepaint:))
173         return NO;
174     
175     if (action == @selector(showHideWebView:))
176         [menuItem setTitle:[_webView isHidden] ? @"Show Web View" : @"Hide Web View"];
177     else if (action == @selector(removeReinsertWebView:))
178         [menuItem setTitle:[_webView window] ? @"Remove Web View" : @"Insert Web View"];
179     else if (action == @selector(toggleZoomMode:))
180         [menuItem setState:_zoomTextOnly ? NSOnState : NSOffState];
181
182     if (action == @selector(setScale:))
183         [menuItem setState:[_webView _viewScale] == viewScaleForMenuItemTag([menuItem tag])];
184
185     return YES;
186 }
187
188 - (IBAction)reload:(id)sender
189 {
190     [_webView reload];
191 }
192
193 - (IBAction)forceRepaint:(id)sender
194 {
195     // FIXME: This doesn't actually force a repaint.
196     [_webView setNeedsDisplay:YES];
197 }
198
199 - (IBAction)goBack:(id)sender
200 {
201     [_webView goBack];
202 }
203
204 - (IBAction)goForward:(id)sender
205 {
206     [_webView goForward];
207 }
208
209 - (IBAction)toggleZoomMode:(id)sender
210 {
211     if (_zoomTextOnly) {
212         _zoomTextOnly = NO;
213         double currentTextZoom = _webView._textZoomFactor;
214         _webView._textZoomFactor = 1;
215         _webView._pageZoomFactor = currentTextZoom;
216     } else {
217         _zoomTextOnly = YES;
218         double currentPageZoom = _webView._pageZoomFactor;
219         _webView._textZoomFactor = currentPageZoom;
220         _webView._pageZoomFactor = 1;
221     }
222 }
223
224 - (IBAction)resetZoom:(id)sender
225 {
226     if (![self canResetZoom])
227         return;
228
229     if (_zoomTextOnly)
230         _webView._textZoomFactor = 1;
231     else
232         _webView._pageZoomFactor = 1;
233 }
234
235 - (BOOL)canResetZoom
236 {
237     return _zoomTextOnly ? (_webView._textZoomFactor != 1) : (_webView._pageZoomFactor != 1);
238 }
239
240 - (IBAction)toggleUseMinimumViewSize:(id)sender
241 {
242     _useMinimumViewSize = !_useMinimumViewSize;
243     toggleUseMinimumViewSizeButton.image = _useMinimumViewSize ? [NSImage imageNamed:@"NSExitFullScreenTemplate"] : [NSImage imageNamed:@"NSEnterFullScreenTemplate"];
244     [_webView _setMinimumViewSize:CGSizeMake(1024, 0)];
245     [_webView _setLayoutMode:_useMinimumViewSize ? _WKLayoutModeDynamicSizeWithMinimumViewSize : _WKLayoutModeViewSize];
246 }
247
248 - (IBAction)dumpSourceToConsole:(id)sender
249 {
250 }
251
252 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
253 {
254     SEL action = item.action;
255
256     if (action == @selector(goBack:) || action == @selector(goForward:))
257         return [_webView validateUserInterfaceItem:item];
258
259     return YES;
260 }
261
262 - (void)validateToolbar
263 {
264     [toolbar validateVisibleItems];
265 }
266
267 - (BOOL)windowShouldClose:(id)sender
268 {
269     return YES;
270 }
271
272 - (void)windowWillClose:(NSNotification *)notification
273 {
274     [(BrowserAppDelegate *)[[NSApplication sharedApplication] delegate] browserWindowWillClose:self.window];
275     [self autorelease];
276 }
277
278 - (void)applicationTerminating
279 {
280 }
281
282 #define DefaultMinimumZoomFactor (.5)
283 #define DefaultMaximumZoomFactor (3.0)
284 #define DefaultZoomFactorRatio (1.2)
285
286 - (CGFloat)currentZoomFactor
287 {
288     return _zoomTextOnly ? _webView._textZoomFactor : _webView._pageZoomFactor;
289 }
290
291 - (void)setCurrentZoomFactor:(CGFloat)factor
292 {
293     if (_zoomTextOnly)
294         _webView._textZoomFactor = factor;
295     else
296         _webView._pageZoomFactor = factor;
297 }
298
299 - (BOOL)canZoomIn
300 {
301     return self.currentZoomFactor * DefaultZoomFactorRatio < DefaultMaximumZoomFactor;
302 }
303
304 - (void)zoomIn:(id)sender
305 {
306     if (!self.canZoomIn)
307         return;
308
309     self.currentZoomFactor *= DefaultZoomFactorRatio;
310 }
311
312 - (BOOL)canZoomOut
313 {
314     return self.currentZoomFactor / DefaultZoomFactorRatio > DefaultMinimumZoomFactor;
315 }
316
317 - (void)zoomOut:(id)sender
318 {
319     if (!self.canZoomIn)
320         return;
321
322     self.currentZoomFactor /= DefaultZoomFactorRatio;
323 }
324
325 - (void)didChangeSettings
326 {
327     SettingsController *settings = [SettingsController shared];
328     WKPreferences *preferences = _webView.configuration.preferences;
329
330     preferences._tiledScrollingIndicatorVisible = settings.tiledScrollingIndicatorVisible;
331     preferences._compositingBordersVisible = settings.layerBordersVisible;
332     preferences._compositingRepaintCountersVisible = settings.layerBordersVisible;
333     preferences._simpleLineLayoutDebugBordersEnabled = settings.simpleLineLayoutDebugBordersEnabled;
334
335     BOOL useTransparentWindows = settings.useTransparentWindows;
336     if (useTransparentWindows != _webView._drawsTransparentBackground) {
337         [self.window setOpaque:!useTransparentWindows];
338         [self.window setHasShadow:!useTransparentWindows];
339
340         _webView._drawsTransparentBackground = useTransparentWindows;
341
342         [self.window display];
343     }
344
345     BOOL usePaginatedMode = settings.usePaginatedMode;
346     if (usePaginatedMode != (_webView._paginationMode != _WKPaginationModeUnpaginated)) {
347         if (usePaginatedMode) {
348             _webView._paginationMode = _WKPaginationModeLeftToRight;
349             _webView._pageLength = _webView.bounds.size.width / 2;
350             _webView._gapBetweenPages = 10;
351         } else
352             _webView._paginationMode = _WKPaginationModeUnpaginated;
353     }
354     
355     NSUInteger visibleOverlayRegions = 0;
356     if (settings.nonFastScrollableRegionOverlayVisible)
357         visibleOverlayRegions |= _WKNonFastScrollableRegion;
358     if (settings.wheelEventHandlerRegionOverlayVisible)
359         visibleOverlayRegions |= _WKWheelEventHandlerRegion;
360     
361     preferences._visibleDebugOverlayRegions = visibleOverlayRegions;
362 }
363
364 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
365 {
366     if (context != keyValueObservingContext || object != _webView)
367         return;
368
369     if ([keyPath isEqualToString:@"title"])
370         self.window.title = [NSString stringWithFormat:@"%@%@ [WK2 %d]", _isPrivateBrowsingWindow ? @"🙈 " : @"", _webView.title, _webView._webProcessIdentifier];
371     else if ([keyPath isEqualToString:@"URL"])
372         [self updateTextFieldFromURL:_webView.URL];
373 }
374
375 - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
376 {
377     NSAlert* alert = [[NSAlert alloc] init];
378
379     [alert setMessageText:[NSString stringWithFormat:@"JavaScript alert dialog from %@.", [frame.request.URL absoluteString]]];
380     [alert setInformativeText:message];
381     [alert addButtonWithTitle:@"OK"];
382
383     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
384         completionHandler();
385         [alert release];
386     }];
387 }
388
389 - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
390 {
391     NSAlert* alert = [[NSAlert alloc] init];
392
393     [alert setMessageText:[NSString stringWithFormat:@"JavaScript confirm dialog from %@.", [frame.request.URL  absoluteString]]];
394     [alert setInformativeText:message];
395     
396     [alert addButtonWithTitle:@"OK"];
397     [alert addButtonWithTitle:@"Cancel"];
398
399     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
400         completionHandler(response == NSAlertFirstButtonReturn);
401         [alert release];
402     }];
403 }
404
405 - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler
406 {
407     NSAlert* alert = [[NSAlert alloc] init];
408
409     [alert setMessageText:[NSString stringWithFormat:@"JavaScript prompt dialog from %@.", [frame.request.URL absoluteString]]];
410     [alert setInformativeText:prompt];
411     
412     [alert addButtonWithTitle:@"OK"];
413     [alert addButtonWithTitle:@"Cancel"];
414     
415     NSTextField* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
416     [input setStringValue:defaultText];
417     [alert setAccessoryView:input];
418     
419     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
420         [input validateEditing];
421         completionHandler(response == NSAlertFirstButtonReturn ? [input stringValue] : nil);
422         [alert release];
423     }];
424 }
425
426 - (void)updateTextFieldFromURL:(NSURL *)URL
427 {
428     if (!URL)
429         return;
430
431     if (!URL.absoluteString.length)
432         return;
433
434     urlText.stringValue = [URL _web_userVisibleString];
435 }
436
437 - (void)loadURLString:(NSString *)urlString
438 {
439     // FIXME: We shouldn't have to set the url text here.
440     [urlText setStringValue:urlString];
441     [self fetch:nil];
442 }
443
444 - (IBAction)performFindPanelAction:(id)sender
445 {
446     [findPanelWindow makeKeyAndOrderFront:sender];
447 }
448
449 - (IBAction)find:(id)sender
450 {
451 }
452
453 static NSSet *dataTypes()
454 {
455     return [WKWebsiteDataStore allWebsiteDataTypes];
456 }
457
458 - (IBAction)fetchWebsiteData:(id)sender
459 {
460     [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
461         NSLog(@"did fetch website data %@.", websiteDataRecords);
462     }];
463 }
464
465 - (IBAction)fetchAndClearWebsiteData:(id)sender
466 {
467     [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
468         [_configuration.websiteDataStore removeDataOfTypes:dataTypes() forDataRecords:websiteDataRecords completionHandler:^{
469             [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
470                 NSLog(@"did clear website data, after clearing data is %@.", websiteDataRecords);
471             }];
472         }];
473     }];
474 }
475
476 - (IBAction)clearWebsiteData:(id)sender
477 {
478     [_configuration.websiteDataStore removeDataOfTypes:dataTypes() modifiedSince:[NSDate distantPast] completionHandler:^{
479         NSLog(@"Did clear website data.");
480     }];
481 }
482
483 #pragma mark WKNavigationDelegate
484
485 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
486 {
487     LOG(@"decidePolicyForNavigationResponse");
488     decisionHandler(WKNavigationResponsePolicyAllow);
489 }
490
491 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
492 {
493     LOG(@"didStartProvisionalNavigation: %@", navigation);
494 }
495
496 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
497 {
498     LOG(@"didReceiveServerRedirectForProvisionalNavigation: %@", navigation);
499 }
500
501 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
502 {
503     LOG(@"didFailProvisionalNavigation: %@navigation, error: %@", navigation, error);
504 }
505
506 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
507 {
508     LOG(@"didCommitNavigation: %@", navigation);
509 }
510
511 - (void)webView:(WKWebView *)webView didFinishLoadingNavigation:(WKNavigation *)navigation
512 {
513     LOG(@"didFinishLoadingNavigation: %@", navigation);
514 }
515
516 - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
517 {
518     LOG(@"didFailNavigation: %@, error %@", navigation, error);
519 }
520
521 - (void)_webViewWebProcessDidCrash:(WKWebView *)webView
522 {
523     NSLog(@"WebContent process crashed; reloading");
524     [self reload:nil];
525 }
526
527 @end
528
529 #endif // WK_API_ENABLED