Add a layout mode that scales down the view to try to fit the document
[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 _useShrinkToFit;
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)toggleShrinkToFit:(id)sender
241 {
242     _useShrinkToFit = !_useShrinkToFit;
243     toggleUseShrinkToFitButton.image = _useShrinkToFit ? [NSImage imageNamed:@"NSExitFullScreenTemplate"] : [NSImage imageNamed:@"NSEnterFullScreenTemplate"];
244     [_webView _setLayoutMode:_useShrinkToFit ? _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize : _WKLayoutModeViewSize];
245 }
246
247 - (IBAction)dumpSourceToConsole:(id)sender
248 {
249 }
250
251 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
252 {
253     SEL action = item.action;
254
255     if (action == @selector(goBack:) || action == @selector(goForward:))
256         return [_webView validateUserInterfaceItem:item];
257
258     return YES;
259 }
260
261 - (void)validateToolbar
262 {
263     [toolbar validateVisibleItems];
264 }
265
266 - (BOOL)windowShouldClose:(id)sender
267 {
268     return YES;
269 }
270
271 - (void)windowWillClose:(NSNotification *)notification
272 {
273     [(BrowserAppDelegate *)[[NSApplication sharedApplication] delegate] browserWindowWillClose:self.window];
274     [self autorelease];
275 }
276
277 - (void)applicationTerminating
278 {
279 }
280
281 #define DefaultMinimumZoomFactor (.5)
282 #define DefaultMaximumZoomFactor (3.0)
283 #define DefaultZoomFactorRatio (1.2)
284
285 - (CGFloat)currentZoomFactor
286 {
287     return _zoomTextOnly ? _webView._textZoomFactor : _webView._pageZoomFactor;
288 }
289
290 - (void)setCurrentZoomFactor:(CGFloat)factor
291 {
292     if (_zoomTextOnly)
293         _webView._textZoomFactor = factor;
294     else
295         _webView._pageZoomFactor = factor;
296 }
297
298 - (BOOL)canZoomIn
299 {
300     return self.currentZoomFactor * DefaultZoomFactorRatio < DefaultMaximumZoomFactor;
301 }
302
303 - (void)zoomIn:(id)sender
304 {
305     if (!self.canZoomIn)
306         return;
307
308     self.currentZoomFactor *= DefaultZoomFactorRatio;
309 }
310
311 - (BOOL)canZoomOut
312 {
313     return self.currentZoomFactor / DefaultZoomFactorRatio > DefaultMinimumZoomFactor;
314 }
315
316 - (void)zoomOut:(id)sender
317 {
318     if (!self.canZoomIn)
319         return;
320
321     self.currentZoomFactor /= DefaultZoomFactorRatio;
322 }
323
324 - (void)didChangeSettings
325 {
326     SettingsController *settings = [SettingsController shared];
327     WKPreferences *preferences = _webView.configuration.preferences;
328
329     preferences._tiledScrollingIndicatorVisible = settings.tiledScrollingIndicatorVisible;
330     preferences._compositingBordersVisible = settings.layerBordersVisible;
331     preferences._compositingRepaintCountersVisible = settings.layerBordersVisible;
332     preferences._simpleLineLayoutDebugBordersEnabled = settings.simpleLineLayoutDebugBordersEnabled;
333
334     BOOL useTransparentWindows = settings.useTransparentWindows;
335     if (useTransparentWindows != _webView._drawsTransparentBackground) {
336         [self.window setOpaque:!useTransparentWindows];
337         [self.window setHasShadow:!useTransparentWindows];
338
339         _webView._drawsTransparentBackground = useTransparentWindows;
340
341         [self.window display];
342     }
343
344     BOOL usePaginatedMode = settings.usePaginatedMode;
345     if (usePaginatedMode != (_webView._paginationMode != _WKPaginationModeUnpaginated)) {
346         if (usePaginatedMode) {
347             _webView._paginationMode = _WKPaginationModeLeftToRight;
348             _webView._pageLength = _webView.bounds.size.width / 2;
349             _webView._gapBetweenPages = 10;
350         } else
351             _webView._paginationMode = _WKPaginationModeUnpaginated;
352     }
353     
354     NSUInteger visibleOverlayRegions = 0;
355     if (settings.nonFastScrollableRegionOverlayVisible)
356         visibleOverlayRegions |= _WKNonFastScrollableRegion;
357     if (settings.wheelEventHandlerRegionOverlayVisible)
358         visibleOverlayRegions |= _WKWheelEventHandlerRegion;
359     
360     preferences._visibleDebugOverlayRegions = visibleOverlayRegions;
361 }
362
363 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
364 {
365     if (context != keyValueObservingContext || object != _webView)
366         return;
367
368     if ([keyPath isEqualToString:@"title"])
369         self.window.title = [NSString stringWithFormat:@"%@%@ [WK2 %d]", _isPrivateBrowsingWindow ? @"🙈 " : @"", _webView.title, _webView._webProcessIdentifier];
370     else if ([keyPath isEqualToString:@"URL"])
371         [self updateTextFieldFromURL:_webView.URL];
372 }
373
374 - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
375 {
376     NSAlert* alert = [[NSAlert alloc] init];
377
378     [alert setMessageText:[NSString stringWithFormat:@"JavaScript alert dialog from %@.", [frame.request.URL absoluteString]]];
379     [alert setInformativeText:message];
380     [alert addButtonWithTitle:@"OK"];
381
382     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
383         completionHandler();
384         [alert release];
385     }];
386 }
387
388 - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
389 {
390     NSAlert* alert = [[NSAlert alloc] init];
391
392     [alert setMessageText:[NSString stringWithFormat:@"JavaScript confirm dialog from %@.", [frame.request.URL  absoluteString]]];
393     [alert setInformativeText:message];
394     
395     [alert addButtonWithTitle:@"OK"];
396     [alert addButtonWithTitle:@"Cancel"];
397
398     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
399         completionHandler(response == NSAlertFirstButtonReturn);
400         [alert release];
401     }];
402 }
403
404 - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler
405 {
406     NSAlert* alert = [[NSAlert alloc] init];
407
408     [alert setMessageText:[NSString stringWithFormat:@"JavaScript prompt dialog from %@.", [frame.request.URL absoluteString]]];
409     [alert setInformativeText:prompt];
410     
411     [alert addButtonWithTitle:@"OK"];
412     [alert addButtonWithTitle:@"Cancel"];
413     
414     NSTextField* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
415     [input setStringValue:defaultText];
416     [alert setAccessoryView:input];
417     
418     [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
419         [input validateEditing];
420         completionHandler(response == NSAlertFirstButtonReturn ? [input stringValue] : nil);
421         [alert release];
422     }];
423 }
424
425 - (void)updateTextFieldFromURL:(NSURL *)URL
426 {
427     if (!URL)
428         return;
429
430     if (!URL.absoluteString.length)
431         return;
432
433     urlText.stringValue = [URL _web_userVisibleString];
434 }
435
436 - (void)loadURLString:(NSString *)urlString
437 {
438     // FIXME: We shouldn't have to set the url text here.
439     [urlText setStringValue:urlString];
440     [self fetch:nil];
441 }
442
443 - (IBAction)performFindPanelAction:(id)sender
444 {
445     [findPanelWindow makeKeyAndOrderFront:sender];
446 }
447
448 - (IBAction)find:(id)sender
449 {
450 }
451
452 static NSSet *dataTypes()
453 {
454     return [WKWebsiteDataStore allWebsiteDataTypes];
455 }
456
457 - (IBAction)fetchWebsiteData:(id)sender
458 {
459     [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
460         NSLog(@"did fetch website data %@.", websiteDataRecords);
461     }];
462 }
463
464 - (IBAction)fetchAndClearWebsiteData:(id)sender
465 {
466     [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
467         [_configuration.websiteDataStore removeDataOfTypes:dataTypes() forDataRecords:websiteDataRecords completionHandler:^{
468             [_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
469                 NSLog(@"did clear website data, after clearing data is %@.", websiteDataRecords);
470             }];
471         }];
472     }];
473 }
474
475 - (IBAction)clearWebsiteData:(id)sender
476 {
477     [_configuration.websiteDataStore removeDataOfTypes:dataTypes() modifiedSince:[NSDate distantPast] completionHandler:^{
478         NSLog(@"Did clear website data.");
479     }];
480 }
481
482 #pragma mark WKNavigationDelegate
483
484 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
485 {
486     LOG(@"decidePolicyForNavigationResponse");
487     decisionHandler(WKNavigationResponsePolicyAllow);
488 }
489
490 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
491 {
492     LOG(@"didStartProvisionalNavigation: %@", navigation);
493 }
494
495 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
496 {
497     LOG(@"didReceiveServerRedirectForProvisionalNavigation: %@", navigation);
498 }
499
500 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
501 {
502     LOG(@"didFailProvisionalNavigation: %@navigation, error: %@", navigation, error);
503 }
504
505 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
506 {
507     LOG(@"didCommitNavigation: %@", navigation);
508 }
509
510 - (void)webView:(WKWebView *)webView didFinishLoadingNavigation:(WKNavigation *)navigation
511 {
512     LOG(@"didFinishLoadingNavigation: %@", navigation);
513 }
514
515 - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
516 {
517     LOG(@"didFailNavigation: %@, error %@", navigation, error);
518 }
519
520 - (void)_webViewWebProcessDidCrash:(WKWebView *)webView
521 {
522     NSLog(@"WebContent process crashed; reloading");
523     [self reload:nil];
524 }
525
526 @end
527
528 #endif // WK_API_ENABLED