| /* |
| * Copyright (C) 2010 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "WK2BrowserWindowController.h" |
| |
| #if WK_API_ENABLED |
| |
| #import "AppDelegate.h" |
| #import "SettingsController.h" |
| #import <WebKit/WKFrameInfo.h> |
| #import <WebKit/WKNavigationDelegate.h> |
| #import <WebKit/WKPreferencesPrivate.h> |
| #import <WebKit/WKUIDelegate.h> |
| #import <WebKit/WKWebView.h> |
| #import <WebKit/WKWebViewConfigurationPrivate.h> |
| #import <WebKit/WKWebViewPrivate.h> |
| #import <WebKit/_WKWebsiteDataStore.h> |
| |
| static void* keyValueObservingContext = &keyValueObservingContext; |
| |
| @interface WK2BrowserWindowController () <WKNavigationDelegate, WKUIDelegate> |
| @end |
| |
| @implementation WK2BrowserWindowController { |
| WKWebViewConfiguration *_configuration; |
| WKWebView *_webView; |
| BOOL _zoomTextOnly; |
| BOOL _isPrivateBrowsingWindow; |
| } |
| |
| - (void)awakeFromNib |
| { |
| _webView = [[WKWebView alloc] initWithFrame:[containerView bounds] configuration:_configuration]; |
| [self didChangeSettings]; |
| |
| _webView.allowsMagnification = YES; |
| _webView.allowsBackForwardNavigationGestures = YES; |
| |
| [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; |
| [containerView addSubview:_webView]; |
| |
| [progressIndicator bind:NSHiddenBinding toObject:_webView withKeyPath:@"loading" options:@{ NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName }]; |
| [progressIndicator bind:NSValueBinding toObject:_webView withKeyPath:@"estimatedProgress" options:nil]; |
| |
| [_webView addObserver:self forKeyPath:@"title" options:0 context:keyValueObservingContext]; |
| [_webView addObserver:self forKeyPath:@"URL" options:0 context:keyValueObservingContext]; |
| |
| _webView.navigationDelegate = self; |
| _webView.UIDelegate = self; |
| |
| _zoomTextOnly = NO; |
| } |
| |
| - (instancetype)initWithConfiguration:(WKWebViewConfiguration *)configuration |
| { |
| if (!(self = [super initWithWindowNibName:@"BrowserWindow"])) |
| return nil; |
| |
| _configuration = [configuration copy]; |
| _isPrivateBrowsingWindow = _configuration._websiteDataStore.isNonPersistent; |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| [_webView removeObserver:self forKeyPath:@"title"]; |
| [_webView removeObserver:self forKeyPath:@"URL"]; |
| |
| [progressIndicator unbind:NSHiddenBinding]; |
| [progressIndicator unbind:NSValueBinding]; |
| |
| [_webView release]; |
| [_configuration release]; |
| |
| [super dealloc]; |
| } |
| |
| - (IBAction)fetch:(id)sender |
| { |
| [urlText setStringValue:[self addProtocolIfNecessary:[urlText stringValue]]]; |
| |
| [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[[urlText stringValue] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]]; |
| } |
| |
| - (IBAction)showHideWebView:(id)sender |
| { |
| BOOL hidden = ![_webView isHidden]; |
| |
| [_webView setHidden:hidden]; |
| } |
| |
| - (IBAction)removeReinsertWebView:(id)sender |
| { |
| if ([_webView window]) { |
| [_webView retain]; |
| [_webView removeFromSuperview]; |
| } else { |
| [containerView addSubview:_webView]; |
| [_webView release]; |
| } |
| } |
| |
| - (BOOL)validateMenuItem:(NSMenuItem *)menuItem |
| { |
| SEL action = [menuItem action]; |
| |
| if (action == @selector(zoomIn:)) |
| return [self canZoomIn]; |
| if (action == @selector(zoomOut:)) |
| return [self canZoomOut]; |
| if (action == @selector(resetZoom:)) |
| return [self canResetZoom]; |
| |
| // Disabled until missing WK2 functionality is exposed via API/SPI. |
| if (action == @selector(dumpSourceToConsole:) |
| || action == @selector(find:) |
| || action == @selector(forceRepaint:)) |
| return NO; |
| |
| if (action == @selector(showHideWebView:)) |
| [menuItem setTitle:[_webView isHidden] ? @"Show Web View" : @"Hide Web View"]; |
| else if (action == @selector(removeReinsertWebView:)) |
| [menuItem setTitle:[_webView window] ? @"Remove Web View" : @"Insert Web View"]; |
| else if (action == @selector(toggleZoomMode:)) |
| [menuItem setState:_zoomTextOnly ? NSOnState : NSOffState]; |
| |
| return YES; |
| } |
| |
| - (IBAction)reload:(id)sender |
| { |
| [_webView reload]; |
| } |
| |
| - (IBAction)forceRepaint:(id)sender |
| { |
| // FIXME: This doesn't actually force a repaint. |
| [_webView setNeedsDisplay:YES]; |
| } |
| |
| - (IBAction)goBack:(id)sender |
| { |
| [_webView goBack]; |
| } |
| |
| - (IBAction)goForward:(id)sender |
| { |
| [_webView goForward]; |
| } |
| |
| - (IBAction)toggleZoomMode:(id)sender |
| { |
| if (_zoomTextOnly) { |
| _zoomTextOnly = NO; |
| double currentTextZoom = _webView._textZoomFactor; |
| _webView._textZoomFactor = 1; |
| _webView._pageZoomFactor = currentTextZoom; |
| } else { |
| _zoomTextOnly = YES; |
| double currentPageZoom = _webView._pageZoomFactor; |
| _webView._textZoomFactor = currentPageZoom; |
| _webView._pageZoomFactor = 1; |
| } |
| } |
| |
| - (IBAction)resetZoom:(id)sender |
| { |
| if (![self canResetZoom]) |
| return; |
| |
| if (_zoomTextOnly) |
| _webView._textZoomFactor = 1; |
| else |
| _webView._pageZoomFactor = 1; |
| } |
| |
| - (BOOL)canResetZoom |
| { |
| return _zoomTextOnly ? (_webView._textZoomFactor != 1) : (_webView._pageZoomFactor != 1); |
| } |
| |
| - (IBAction)dumpSourceToConsole:(id)sender |
| { |
| } |
| |
| - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item |
| { |
| SEL action = item.action; |
| |
| if (action == @selector(goBack:) || action == @selector(goForward:)) |
| return [_webView validateUserInterfaceItem:item]; |
| |
| return YES; |
| } |
| |
| - (void)validateToolbar |
| { |
| [toolbar validateVisibleItems]; |
| } |
| |
| - (BOOL)windowShouldClose:(id)sender |
| { |
| return YES; |
| } |
| |
| - (void)windowWillClose:(NSNotification *)notification |
| { |
| [(BrowserAppDelegate *)[[NSApplication sharedApplication] delegate] browserWindowWillClose:self.window]; |
| [self autorelease]; |
| } |
| |
| - (void)applicationTerminating |
| { |
| } |
| |
| #define DefaultMinimumZoomFactor (.5) |
| #define DefaultMaximumZoomFactor (3.0) |
| #define DefaultZoomFactorRatio (1.2) |
| |
| - (CGFloat)currentZoomFactor |
| { |
| return _zoomTextOnly ? _webView._textZoomFactor : _webView._pageZoomFactor; |
| } |
| |
| - (void)setCurrentZoomFactor:(CGFloat)factor |
| { |
| if (_zoomTextOnly) |
| _webView._textZoomFactor = factor; |
| else |
| _webView._pageZoomFactor = factor; |
| } |
| |
| - (BOOL)canZoomIn |
| { |
| return self.currentZoomFactor * DefaultZoomFactorRatio < DefaultMaximumZoomFactor; |
| } |
| |
| - (void)zoomIn:(id)sender |
| { |
| if (!self.canZoomIn) |
| return; |
| |
| self.currentZoomFactor *= DefaultZoomFactorRatio; |
| } |
| |
| - (BOOL)canZoomOut |
| { |
| return self.currentZoomFactor / DefaultZoomFactorRatio > DefaultMinimumZoomFactor; |
| } |
| |
| - (void)zoomOut:(id)sender |
| { |
| if (!self.canZoomIn) |
| return; |
| |
| self.currentZoomFactor /= DefaultZoomFactorRatio; |
| } |
| |
| - (void)didChangeSettings |
| { |
| SettingsController *settings = [SettingsController shared]; |
| WKPreferences *preferences = _webView.configuration.preferences; |
| |
| preferences._tiledScrollingIndicatorVisible = settings.tiledScrollingIndicatorVisible; |
| preferences._compositingBordersVisible = settings.layerBordersVisible; |
| preferences._compositingRepaintCountersVisible = settings.layerBordersVisible; |
| preferences._simpleLineLayoutDebugBordersEnabled = settings.simpleLineLayoutDebugBordersEnabled; |
| |
| BOOL useTransparentWindows = settings.useTransparentWindows; |
| if (useTransparentWindows != _webView._drawsTransparentBackground) { |
| [self.window setOpaque:!useTransparentWindows]; |
| [self.window setHasShadow:!useTransparentWindows]; |
| |
| _webView._drawsTransparentBackground = useTransparentWindows; |
| |
| [self.window display]; |
| } |
| |
| BOOL usePaginatedMode = settings.usePaginatedMode; |
| if (usePaginatedMode != (_webView._paginationMode != _WKPaginationModeUnpaginated)) { |
| if (usePaginatedMode) { |
| _webView._paginationMode = _WKPaginationModeLeftToRight; |
| _webView._pageLength = _webView.bounds.size.width / 2; |
| _webView._gapBetweenPages = 10; |
| } else |
| _webView._paginationMode = _WKPaginationModeUnpaginated; |
| } |
| |
| NSUInteger visibleOverlayRegions = 0; |
| if (settings.nonFastScrollableRegionOverlayVisible) |
| visibleOverlayRegions |= _WKNonFastScrollableRegion; |
| if (settings.wheelEventHandlerRegionOverlayVisible) |
| visibleOverlayRegions |= _WKWheelEventHandlerRegion; |
| |
| preferences._visibleDebugOverlayRegions = visibleOverlayRegions; |
| } |
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context |
| { |
| if (context != keyValueObservingContext || object != _webView) |
| return; |
| |
| if ([keyPath isEqualToString:@"title"]) |
| self.window.title = [NSString stringWithFormat:@"%@%@ [WK2 %d]", _isPrivateBrowsingWindow ? @"🙈 " : @"", _webView.title, _webView._webProcessIdentifier]; |
| else if ([keyPath isEqualToString:@"URL"]) |
| [self updateTextFieldFromURL:_webView.URL]; |
| } |
| |
| - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)())completionHandler |
| { |
| NSAlert* alert = [[NSAlert alloc] init]; |
| |
| [alert setMessageText:[NSString stringWithFormat:@"JavaScript alert dialog from %@.", [frame.request.URL absoluteString]]]; |
| [alert setInformativeText:message]; |
| [alert addButtonWithTitle:@"OK"]; |
| |
| [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) { |
| completionHandler(); |
| [alert release]; |
| }]; |
| } |
| |
| - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler |
| { |
| NSAlert* alert = [[NSAlert alloc] init]; |
| |
| [alert setMessageText:[NSString stringWithFormat:@"JavaScript confirm dialog from %@.", [frame.request.URL absoluteString]]]; |
| [alert setInformativeText:message]; |
| |
| [alert addButtonWithTitle:@"OK"]; |
| [alert addButtonWithTitle:@"Cancel"]; |
| |
| [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) { |
| completionHandler(response == NSAlertFirstButtonReturn); |
| [alert release]; |
| }]; |
| } |
| |
| - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler |
| { |
| NSAlert* alert = [[NSAlert alloc] init]; |
| |
| [alert setMessageText:[NSString stringWithFormat:@"JavaScript prompt dialog from %@.", [frame.request.URL absoluteString]]]; |
| [alert setInformativeText:prompt]; |
| |
| [alert addButtonWithTitle:@"OK"]; |
| [alert addButtonWithTitle:@"Cancel"]; |
| |
| NSTextField* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; |
| [input setStringValue:defaultText]; |
| [alert setAccessoryView:input]; |
| |
| [alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) { |
| [input validateEditing]; |
| completionHandler(response == NSAlertFirstButtonReturn ? [input stringValue] : nil); |
| [alert release]; |
| }]; |
| } |
| |
| - (void)updateTextFieldFromURL:(NSURL *)URL |
| { |
| if (!URL) |
| return; |
| |
| if (!URL.absoluteString.length) |
| return; |
| |
| urlText.stringValue = [[URL absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
| } |
| |
| - (void)loadURLString:(NSString *)urlString |
| { |
| // FIXME: We shouldn't have to set the url text here. |
| [urlText setStringValue:urlString]; |
| [self fetch:nil]; |
| } |
| |
| - (IBAction)performFindPanelAction:(id)sender |
| { |
| [findPanelWindow makeKeyAndOrderFront:sender]; |
| } |
| |
| - (IBAction)find:(id)sender |
| { |
| } |
| |
| - (IBAction)clearWebsiteData:(id)sender |
| { |
| [_configuration._websiteDataStore removeDataOfTypes:WKWebsiteDataTypeAll modifiedSince:[NSDate distantPast] completionHandler:^{ |
| NSLog(@"Did clear website data."); |
| }]; |
| } |
| |
| #pragma mark WKNavigationDelegate |
| |
| - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler |
| { |
| LOG(@"decidePolicyForNavigationResponse"); |
| decisionHandler(WKNavigationResponsePolicyAllow); |
| } |
| |
| - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation |
| { |
| LOG(@"didStartProvisionalNavigation: %@", navigation); |
| } |
| |
| - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation |
| { |
| LOG(@"didReceiveServerRedirectForProvisionalNavigation: %@", navigation); |
| } |
| |
| - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error |
| { |
| LOG(@"didFailProvisionalNavigation: %@navigation, error: %@", navigation, error); |
| } |
| |
| - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation |
| { |
| LOG(@"didCommitNavigation: %@", navigation); |
| } |
| |
| - (void)webView:(WKWebView *)webView didFinishLoadingNavigation:(WKNavigation *)navigation |
| { |
| LOG(@"didFinishLoadingNavigation: %@", navigation); |
| } |
| |
| - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error |
| { |
| LOG(@"didFailNavigation: %@, error %@", navigation, error); |
| } |
| |
| - (void)_webViewWebProcessDidCrash:(WKWebView *)webView |
| { |
| NSLog(@"WebContent process crashed; reloading"); |
| [self reload:nil]; |
| } |
| |
| @end |
| |
| #endif // WK_API_ENABLED |