blob: 570aaa54db970e47424894f7bd871a73f2c7dd0e [file] [log] [blame]
/*
* Copyright (C) 2010-2016 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"
#import "AppDelegate.h"
#import "SettingsController.h"
#import <WebKit/WKFrameInfo.h>
#import <WebKit/WKNavigationActionPrivate.h>
#import <WebKit/WKNavigationDelegate.h>
#import <WebKit/WKOpenPanelParametersPrivate.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKUIDelegate.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/WKWebsiteDataStorePrivate.h>
#import <WebKit/WebNSURLExtras.h>
#import <WebKit/_WKIconLoadingDelegate.h>
#import <WebKit/_WKInspector.h>
#import <WebKit/_WKLinkIconParameters.h>
#import <WebKit/_WKUserInitiatedAction.h>
static void* keyValueObservingContext = &keyValueObservingContext;
static const int testHeaderBannerHeight = 42;
static const int testFooterBannerHeight = 58;
@interface MiniBrowserNSTextFinder : NSTextFinder
@property (nonatomic, copy) dispatch_block_t hideInterfaceCallback;
@end
@implementation MiniBrowserNSTextFinder
- (void)dealloc
{
[_hideInterfaceCallback release];
[super dealloc];
}
- (void)performAction:(NSTextFinderAction)op
{
[super performAction:op];
if (op == NSTextFinderActionHideFindInterface && _hideInterfaceCallback)
_hideInterfaceCallback();
}
@end
// Target for the FileExtensions NSPopupButton in the OpenPanel's AccessoryView.
@interface FileExtensionsPopupTarget : NSObject {
@private
NSOpenPanel *_openPanel;
NSArray<NSString *> *_allowedFileExtensions;
}
@end
@implementation FileExtensionsPopupTarget
- (id)initWithOpenPanel:(NSOpenPanel*)openPanel allowedFileExtensions:(NSArray<NSString *> *)allowedFileExtensions {
if ((self = [super init])) {
_openPanel = openPanel;
_allowedFileExtensions = [allowedFileExtensions copy];
}
return self;
}
- (void)popupAction:(id)sender {
// Last item.
if ([sender indexOfSelectedItem] == [sender numberOfItems] - 1)
[_openPanel setAllowedFileTypes:nil];
else if (![sender indexOfSelectedItem]) {
// First item.
if (![_allowedFileExtensions count])
[_openPanel setAllowedFileTypes:@[@""]];
else
[_openPanel setAllowedFileTypes:_allowedFileExtensions];
}
}
- (void)dealloc
{
[_allowedFileExtensions release];
[super dealloc];
}
@end
@interface WK2BrowserWindowController () <NSTextFinderBarContainer, WKNavigationDelegate, WKUIDelegate, _WKIconLoadingDelegate>
@end
@implementation WK2BrowserWindowController {
WKWebViewConfiguration *_configuration;
WKWebView *_webView;
BOOL _zoomTextOnly;
BOOL _isPrivateBrowsingWindow;
BOOL _useShrinkToFit;
MiniBrowserNSTextFinder *_textFinder;
NSView *_textFindBarView;
BOOL _findBarVisible;
FileExtensionsPopupTarget *_fileExtensionsPopupTarget;
}
- (void)awakeFromNib
{
_webView = [[WKWebView alloc] initWithFrame:[containerView bounds] configuration:_configuration];
[self didChangeSettings];
_webView.allowsMagnification = YES;
_webView.allowsBackForwardNavigationGestures = YES;
_webView._editable = self.isEditable;
[_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;
SettingsController *settingsController = [[NSApplication sharedApplication] browserAppDelegate].settingsController;
// This setting installs the new WK2 Icon Loading Delegate and tests that mechanism by
// telling WebKit to load every icon referenced by the page.
if (settingsController.loadsAllSiteIcons)
_webView._iconLoadingDelegate = self;
_webView._observedRenderingProgressEvents = _WKRenderingProgressEventFirstLayout
| _WKRenderingProgressEventFirstVisuallyNonEmptyLayout
| _WKRenderingProgressEventFirstPaintWithSignificantArea
| _WKRenderingProgressEventFirstLayoutAfterSuppressedIncrementalRendering
| _WKRenderingProgressEventFirstPaintAfterSuppressedIncrementalRendering;
if (settingsController.customUserAgent)
_webView.customUserAgent = settingsController.customUserAgent;
_webView._usePlatformFindUI = NO;
_textFinder = [[MiniBrowserNSTextFinder alloc] init];
_textFinder.incrementalSearchingEnabled = YES;
_textFinder.incrementalSearchingShouldDimContentView = NO;
_textFinder.client = _webView;
_textFinder.findBarContainer = self;
#if __has_feature(objc_arc)
__weak WKWebView *weakWebView = _webView;
#else
WKWebView *weakWebView = _webView;
#endif
_textFinder.hideInterfaceCallback = ^{
WKWebView *webView = weakWebView;
[webView _hideFindUI];
};
_zoomTextOnly = NO;
}
- (instancetype)initWithConfiguration:(WKWebViewConfiguration *)configuration
{
if (!(self = [super initWithWindowNibName:@"BrowserWindow"]))
return nil;
_configuration = [configuration copy];
_isPrivateBrowsingWindow = !_configuration.websiteDataStore.isPersistent;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userAgentDidChange:) name:kUserAgentChangedNotificationName object:nil];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_webView removeObserver:self forKeyPath:@"title"];
[_webView removeObserver:self forKeyPath:@"URL"];
[progressIndicator unbind:NSHiddenBinding];
[progressIndicator unbind:NSValueBinding];
[_textFinder release];
[_webView release];
[_configuration release];
[_fileExtensionsPopupTarget release];
[super dealloc];
}
- (void)userAgentDidChange:(NSNotification *)notification
{
SettingsController *settingsController = [[NSApplication sharedApplication] browserAppDelegate].settingsController;
_webView.customUserAgent = settingsController.customUserAgent;
[_webView reload];
}
- (IBAction)fetch:(id)sender
{
[urlText setStringValue:[self addProtocolIfNecessary:urlText.stringValue]];
NSURL *url = [NSURL _webkit_URLWithUserTypedString:urlText.stringValue];
[_webView loadRequest:[NSURLRequest requestWithURL:url]];
}
- (IBAction)setPageScale:(id)sender
{
CGFloat scale = [self pageScaleForMenuItemTag:[sender tag]];
[_webView _setPageScale:scale withOrigin:CGPointZero];
}
- (CGFloat)viewScaleForMenuItemTag:(NSInteger)tag
{
if (tag == 1)
return 1;
if (tag == 2)
return 0.75;
if (tag == 3)
return 0.5;
if (tag == 4)
return 0.25;
return 1;
}
- (IBAction)setViewScale:(id)sender
{
CGFloat scale = [self viewScaleForMenuItemTag:[sender tag]];
CGFloat oldScale = [_webView _viewScale];
if (scale == oldScale)
return;
[_webView _setLayoutMode:_WKLayoutModeDynamicSizeComputedFromViewScale];
NSRect oldFrame = self.window.frame;
NSSize newFrameSize = NSMakeSize(oldFrame.size.width * (scale / oldScale), oldFrame.size.height * (scale / oldScale));
[self.window setFrame:NSMakeRect(oldFrame.origin.x, oldFrame.origin.y - (newFrameSize.height - oldFrame.size.height), newFrameSize.width, newFrameSize.height) display:NO animate:NO];
[_webView _setViewScale:scale];
}
static BOOL areEssentiallyEqual(double a, double b)
{
double tolerance = 0.001;
return (fabs(a - b) <= tolerance);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
#pragma GCC diagnostic pop
{
SEL action = menuItem.action;
if (action == @selector(saveAsPDF:))
return YES;
if (action == @selector(saveAsWebArchive:))
return YES;
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(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(toggleFullWindowWebView:))
[menuItem setTitle:[self webViewFillsWindow] ? @"Inset Web View" : @"Fit Web View to Window"];
else if (action == @selector(toggleZoomMode:))
[menuItem setState:_zoomTextOnly ? NSControlStateValueOn : NSControlStateValueOff];
else if (action == @selector(toggleEditable:))
[menuItem setState:self.isEditable ? NSControlStateValueOn : NSControlStateValueOff];
else if (action == @selector(showHideWebInspector:))
[menuItem setTitle:_webView._inspector.isVisible ? @"Close Web Inspector" : @"Show Web Inspector"];
else if (action == @selector(toggleAlwaysShowsHorizontalScroller:))
menuItem.state = _webView._alwaysShowsHorizontalScroller ? NSControlStateValueOn : NSControlStateValueOff;
else if (action == @selector(toggleAlwaysShowsVerticalScroller:))
menuItem.state = _webView._alwaysShowsVerticalScroller ? NSControlStateValueOn : NSControlStateValueOff;
if (action == @selector(setPageScale:))
[menuItem setState:areEssentiallyEqual([_webView _pageScale], [self pageScaleForMenuItemTag:[menuItem tag]])];
if (action == @selector(setViewScale:))
[menuItem setState:areEssentiallyEqual([_webView _viewScale], [self viewScaleForMenuItemTag:[menuItem tag]])];
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.pageZoom = currentTextZoom;
} else {
_zoomTextOnly = YES;
double currentPageZoom = _webView._pageZoomFactor;
_webView._textZoomFactor = currentPageZoom;
_webView.pageZoom = 1;
}
}
- (IBAction)resetZoom:(id)sender
{
if (![self canResetZoom])
return;
if (_zoomTextOnly)
_webView._textZoomFactor = 1;
else
_webView.pageZoom = 1;
}
- (BOOL)canResetZoom
{
return _zoomTextOnly ? (_webView._textZoomFactor != 1) : (_webView.pageZoom != 1);
}
- (IBAction)toggleShrinkToFit:(id)sender
{
_useShrinkToFit = !_useShrinkToFit;
toggleUseShrinkToFitButton.image = _useShrinkToFit ? [NSImage imageNamed:@"NSExitFullScreenTemplate"] : [NSImage imageNamed:@"NSEnterFullScreenTemplate"];
[_webView _setLayoutMode:_useShrinkToFit ? _WKLayoutModeDynamicSizeComputedFromMinimumDocumentSize : _WKLayoutModeViewSize];
}
- (IBAction)dumpSourceToConsole:(id)sender
{
}
- (IBAction)showHideWebInspector:(id)sender
{
_WKInspector *inspector = _webView._inspector;
if (inspector.isVisible)
[inspector hide];
else
[inspector show];
}
- (IBAction)toggleAlwaysShowsHorizontalScroller:(id)sender
{
_webView._alwaysShowsHorizontalScroller = !_webView._alwaysShowsHorizontalScroller;
}
- (IBAction)toggleAlwaysShowsVerticalScroller:(id)sender
{
_webView._alwaysShowsVerticalScroller = !_webView._alwaysShowsVerticalScroller;
}
- (NSURL *)currentURL
{
return _webView.URL;
}
- (NSView *)mainContentView
{
return _webView;
}
- (void)setEditable:(BOOL)editable
{
[super setEditable:editable];
_webView._editable = editable;
}
- (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
{
[[[NSApplication sharedApplication] browserAppDelegate] browserWindowWillClose:self.window];
[self autorelease];
}
#define DefaultMinimumZoomFactor (.5)
#define DefaultMaximumZoomFactor (3.0)
#define DefaultZoomFactorRatio (1.2)
- (CGFloat)currentZoomFactor
{
return _zoomTextOnly ? _webView._textZoomFactor : _webView.pageZoom;
}
- (void)setCurrentZoomFactor:(CGFloat)factor
{
if (_zoomTextOnly)
_webView._textZoomFactor = factor;
else
_webView.pageZoom = 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 = [[NSApplication sharedApplication] browserAppDelegate].settingsController;
WKPreferences *preferences = _webView.configuration.preferences;
_webView._useSystemAppearance = settings.useSystemAppearance;
preferences._tiledScrollingIndicatorVisible = settings.tiledScrollingIndicatorVisible;
preferences._compositingBordersVisible = settings.layerBordersVisible;
preferences._compositingRepaintCountersVisible = settings.layerBordersVisible;
preferences._simpleLineLayoutEnabled = settings.simpleLineLayoutEnabled;
preferences._simpleLineLayoutDebugBordersEnabled = settings.simpleLineLayoutDebugBordersEnabled;
preferences._acceleratedDrawingEnabled = settings.acceleratedDrawingEnabled;
preferences._resourceUsageOverlayVisible = settings.resourceUsageOverlayVisible;
preferences._displayListDrawingEnabled = settings.displayListDrawingEnabled;
preferences._subpixelAntialiasedLayerTextEnabled = settings.subpixelAntialiasedLayerTextEnabled;
preferences._largeImageAsyncDecodingEnabled = settings.largeImageAsyncDecodingEnabled;
preferences._animatedImageAsyncDecodingEnabled = settings.animatedImageAsyncDecodingEnabled;
preferences._colorFilterEnabled = settings.appleColorFilterEnabled;
preferences._punchOutWhiteBackgroundsInDarkMode = settings.punchOutWhiteBackgroundsInDarkMode;
_webView.configuration.websiteDataStore._resourceLoadStatisticsEnabled = settings.resourceLoadStatisticsEnabled;
[self setWebViewFillsWindow:settings.webViewFillsWindow];
BOOL useTransparentWindows = settings.useTransparentWindows;
if (useTransparentWindows != !_webView._drawsBackground) {
[self.window setOpaque:!useTransparentWindows];
[self.window setBackgroundColor:[NSColor clearColor]];
[self.window setHasShadow:!useTransparentWindows];
_webView._drawsBackground = !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;
[_webView _setHeaderBannerHeight:[settings isSpaceReservedForBanners] ? testHeaderBannerHeight : 0];
[_webView _setFooterBannerHeight:[settings isSpaceReservedForBanners] ? testFooterBannerHeight : 0];
}
- (void)updateTitle:(NSString *)title
{
if (!title) {
NSURL *url = _webView.URL;
title = url.lastPathComponent ?: url._web_userVisibleString;
}
self.window.title = [NSString stringWithFormat:@"%@%@ [WK2 %d]%@", _isPrivateBrowsingWindow ? @"🙈 " : @"", title, _webView._webProcessIdentifier, _webView._editable ? @" [Editable]" : @""];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context != keyValueObservingContext || object != _webView)
return;
if ([keyPath isEqualToString:@"title"])
[self updateTitle:_webView.title];
else if ([keyPath isEqualToString:@"URL"])
[self updateTextFieldFromURL:_webView.URL];
}
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
WK2BrowserWindowController *controller = [[WK2BrowserWindowController alloc] initWithConfiguration:configuration];
[controller awakeFromNib];
[controller.window makeKeyAndOrderFront:self];
return controller->_webView;
}
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(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];
}];
}
- (nullable NSView *)createFilterView:(NSArray *)titles popupTarget:(FileExtensionsPopupTarget*)popupTarget
{
NSTextField* label = [[[NSTextField alloc] initWithFrame:NSZeroRect] autorelease];
[label setStringValue:@"Format:"];
[label setEditable:NO];
[label setSelectable:NO];
[label setBordered:NO];
[label setBackgroundColor:[NSColor clearColor]];
[label sizeToFit];
NSPopUpButton *button = [[[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO] autorelease];
[button addItemsWithTitles:titles];
[button setTarget:popupTarget];
[button setAction:@selector(popupAction:)];
[button sizeToFit];
[button selectItemAtIndex:0];
[popupTarget popupAction:button];
NSView* filterView = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
[filterView addSubview:label];
[filterView addSubview:button];
const CGFloat margin = 20;
const CGFloat spacing = 2;
const CGFloat minButtonWidth = 230;
NSRect labelFrame = [label frame];
NSRect buttonFrame = [button frame];
NSRect filterViewFrame = [filterView frame];
// Set a minimum width for 'button'.
buttonFrame.size = NSMakeSize(MAX(NSWidth(buttonFrame), minButtonWidth), NSHeight(buttonFrame));
// FilterView will lay out 'label' and 'button' horizontally and have a vertical margins.
filterViewFrame.size = NSMakeSize(NSWidth(labelFrame) + NSWidth(buttonFrame) + spacing, MAX(NSHeight(labelFrame), NSHeight(buttonFrame)) + margin * 2);
[filterView setFrame:filterViewFrame];
// 'label' will be laid out center vertically.
labelFrame.origin = NSMakePoint(NSMinX(labelFrame), (NSHeight(filterViewFrame) - NSHeight(labelFrame)) / 2);
[label setFrame:labelFrame];
// 'button' will be laid out center vertically and it will come after 'label' horizontally.
buttonFrame.origin = NSMakePoint(NSMaxX(labelFrame) + spacing, (NSHeight(filterViewFrame) - NSHeight(buttonFrame)) / 2);
[button setFrame:buttonFrame];
return filterView;
}
#if __has_feature(objc_generics)
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * URLs))completionHandler
#else
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray *URLs))completionHandler
#endif
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
[_fileExtensionsPopupTarget release];
_fileExtensionsPopupTarget = [[FileExtensionsPopupTarget alloc] initWithOpenPanel: openPanel allowedFileExtensions: parameters._allowedFileExtensions];
NSArray *allowedFileExtensionsTitles = parameters._allowedFileExtensionsTitles;
NSView *filterView = [self createFilterView:allowedFileExtensionsTitles popupTarget:_fileExtensionsPopupTarget];
if (filterView)
[openPanel setAccessoryView:filterView];
[openPanel beginSheetModalForWindow:webView.window completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK)
completionHandler(openPanel.URLs);
else
completionHandler(nil);
}];
}
- (void)_webView:(WebView *)sender runBeforeUnloadConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
{
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = [NSString stringWithFormat:@"JavaScript before unload dialog from %@.", [frame.request.URL absoluteString]];
alert.informativeText = message;
[alert addButtonWithTitle:@"Leave Page"];
[alert addButtonWithTitle:@"Stay On Page"];
[alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
completionHandler(response == NSAlertFirstButtonReturn);
[alert release];
}];
}
- (WKDragDestinationAction)_webView:(WKWebView *)webView dragDestinationActionMaskForDraggingInfo:(id)draggingInfo
{
return WKDragDestinationActionAny;
}
- (void)updateTextFieldFromURL:(NSURL *)URL
{
if (!URL)
return;
if (!URL.absoluteString.length)
return;
urlText.stringValue = [URL _web_userVisibleString];
}
- (void)loadURLString:(NSString *)urlString
{
// FIXME: We shouldn't have to set the url text here.
[urlText setStringValue:urlString];
[self fetch:nil];
}
- (void)loadHTMLString:(NSString *)HTMLString
{
[_webView loadHTMLString:HTMLString baseURL:nil];
}
static NSSet *dataTypes()
{
return [WKWebsiteDataStore allWebsiteDataTypes];
}
- (IBAction)fetchWebsiteData:(id)sender
{
[_configuration.websiteDataStore _fetchDataRecordsOfTypes:dataTypes() withOptions:_WKWebsiteDataStoreFetchOptionComputeSizes completionHandler:^(NSArray *websiteDataRecords) {
NSLog(@"did fetch website data %@.", websiteDataRecords);
}];
}
- (IBAction)fetchAndClearWebsiteData:(id)sender
{
[_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
[_configuration.websiteDataStore removeDataOfTypes:dataTypes() forDataRecords:websiteDataRecords completionHandler:^{
[_configuration.websiteDataStore fetchDataRecordsOfTypes:dataTypes() completionHandler:^(NSArray *websiteDataRecords) {
NSLog(@"did clear website data, after clearing data is %@.", websiteDataRecords);
}];
}];
}];
}
- (IBAction)clearWebsiteData:(id)sender
{
[_configuration.websiteDataStore removeDataOfTypes:dataTypes() modifiedSince:[NSDate distantPast] completionHandler:^{
NSLog(@"Did clear website data.");
}];
}
- (IBAction)printWebView:(id)sender
{
[[_webView printOperationWithPrintInfo:[NSPrintInfo sharedPrintInfo]] runOperationModalForWindow:self.window delegate:nil didRunSelector:nil contextInfo:nil];
}
#pragma mark WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
LOG(@"decidePolicyForNavigationAction");
if (navigationAction._canHandleRequest) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
if (navigationAction._userInitiatedAction && !navigationAction._userInitiatedAction.isConsumed) {
[navigationAction._userInitiatedAction consume];
[[NSWorkspace sharedWorkspace] openURL:navigationAction.request.URL];
}
decisionHandler(WKNavigationActionPolicyCancel);
}
- (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);
[self updateTitle:nil];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
LOG(@"didFinishNavigation: %@", navigation);
}
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler
{
LOG(@"didReceiveAuthenticationChallenge: %@", challenge);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]) {
NSAlert *alert = [[NSAlert alloc] init];
NSView *container = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 48)] autorelease];
NSTextField *userInput = [[[NSTextField alloc] initWithFrame:NSMakeRect(0, 24, 200, 24)] autorelease];
NSTextField *passwordInput = [[[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)] autorelease];
[alert setMessageText:[NSString stringWithFormat:@"Log in to %@:%lu.", challenge.protectionSpace.host, challenge.protectionSpace.port]];
[alert addButtonWithTitle:@"Log in"];
[alert addButtonWithTitle:@"Cancel"];
[container addSubview:userInput];
[container addSubview:passwordInput];
[alert setAccessoryView:container];
[userInput setNextKeyView:passwordInput];
[alert.window setInitialFirstResponder:userInput];
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse response) {
[userInput validateEditing];
if (response == NSAlertFirstButtonReturn)
completionHandler(NSURLSessionAuthChallengeUseCredential, [[[NSURLCredential alloc] initWithUser:[userInput stringValue] password:[passwordInput stringValue] persistence:NSURLCredentialPersistenceForSession] autorelease]);
else
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
[alert release];
}];
return;
}
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
LOG(@"didFailNavigation: %@, error %@", navigation, error);
}
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
NSLog(@"WebContent process crashed; reloading");
[self reload:nil];
}
- (void)_webView:(WKWebView *)webView renderingProgressDidChange:(_WKRenderingProgressEvents)progressEvents
{
if (progressEvents & _WKRenderingProgressEventFirstLayout)
LOG(@"renderingProgressDidChange: %@", @"first layout");
if (progressEvents & _WKRenderingProgressEventFirstVisuallyNonEmptyLayout)
LOG(@"renderingProgressDidChange: %@", @"first visually non-empty layout");
if (progressEvents & _WKRenderingProgressEventFirstPaintWithSignificantArea)
LOG(@"renderingProgressDidChange: %@", @"first paint with significant area");
if (progressEvents & _WKRenderingProgressEventFirstLayoutAfterSuppressedIncrementalRendering)
LOG(@"renderingProgressDidChange: %@", @"first layout after suppressed incremental rendering");
if (progressEvents & _WKRenderingProgressEventFirstPaintAfterSuppressedIncrementalRendering)
LOG(@"renderingProgressDidChange: %@", @"first paint after suppressed incremental rendering");
}
- (void)webView:(WKWebView *)webView shouldLoadIconWithParameters:(_WKLinkIconParameters *)parameters completionHandler:(void (^)(void (^)(NSData*)))completionHandler
{
completionHandler(^void (NSData *data) {
LOG(@"Icon URL %@ received icon data of length %u", parameters.url, (unsigned)data.length);
});
}
#pragma mark Find in Page
- (IBAction)performTextFinderAction:(id)sender
{
[_textFinder performAction:[sender tag]];
}
- (NSView *)findBarView
{
return _textFindBarView;
}
- (void)setFindBarView:(NSView *)findBarView
{
_textFindBarView = findBarView;
_findBarVisible = YES;
[_textFindBarView setFrame:NSMakeRect(0, 0, containerView.bounds.size.width, _textFindBarView.frame.size.height)];
}
- (BOOL)isFindBarVisible
{
return _findBarVisible;
}
- (void)setFindBarVisible:(BOOL)findBarVisible
{
_findBarVisible = findBarVisible;
if (findBarVisible)
[containerView addSubview:_textFindBarView];
else
[_textFindBarView removeFromSuperview];
}
- (NSView *)contentView
{
return _webView;
}
- (void)findBarViewDidChangeHeight
{
}
- (void)_webView:(WKWebView *)webView requestMediaCaptureAuthorization: (_WKCaptureDevices)devices decisionHandler:(void (^)(BOOL authorized))decisionHandler
{
decisionHandler(true);
}
- (void)_webView:(WKWebView *)webView includeSensitiveMediaDeviceDetails:(void (^)(BOOL includeSensitiveDetails))decisionHandler
{
decisionHandler(false);
}
- (IBAction)saveAsPDF:(id)sender
{
NSSavePanel *panel = [NSSavePanel savePanel];
panel.allowedFileTypes = @[ @"pdf" ];
[panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[_webView createPDFWithConfiguration:nil completionHandler:^(NSData *pdfSnapshotData, NSError *error) {
[pdfSnapshotData writeToURL:[panel URL] options:0 error:nil];
}];
}
}];
}
- (IBAction)saveAsWebArchive:(id)sender
{
NSSavePanel *panel = [NSSavePanel savePanel];
panel.allowedFileTypes = @[ @"webarchive" ];
[panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
[_webView createWebArchiveDataWithCompletionHandler:^(NSData *archiveData, NSError *error) {
[archiveData writeToURL:[panel URL] options:0 error:nil];
}];
}
}];
}
@end