mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00

Official WebKit no longer supports Mac 10.14. However, since this system is still very much in use, we want to be able to keep it running for a while. This patch adds a new browser that we would compile and maintain specifically for Mac 10.14: `deprecated-webkit-mac-10.14`. This browser is a clone of Webkit r1443 that is the last known revision to compile on Mac 10.14. As we move on, we're free to modify this browser however we want, backporting important patches. References #5833
878 lines
28 KiB
Objective-C
878 lines
28 KiB
Objective-C
/*
|
|
* 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 "BrowserWindowController.h"
|
|
|
|
#import "AppDelegate.h"
|
|
#import <WebKit/WKFrameInfo.h>
|
|
#import <WebKit/WKNavigationActionPrivate.h>
|
|
#import <WebKit/WKNavigationDelegatePrivate.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;
|
|
|
|
@interface PlaywrightNSTextFinder : NSTextFinder
|
|
|
|
@property (nonatomic, copy) dispatch_block_t hideInterfaceCallback;
|
|
|
|
@end
|
|
|
|
@implementation PlaywrightNSTextFinder
|
|
|
|
- (void)performAction:(NSTextFinderAction)op
|
|
{
|
|
[super performAction:op];
|
|
|
|
if (op == NSTextFinderActionHideFindInterface && _hideInterfaceCallback)
|
|
_hideInterfaceCallback();
|
|
}
|
|
|
|
@end
|
|
|
|
@interface BrowserWindowController () <NSTextFinderBarContainer, WKNavigationDelegate, WKUIDelegate, _WKIconLoadingDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate>
|
|
@end
|
|
|
|
@implementation BrowserWindowController {
|
|
IBOutlet NSProgressIndicator *progressIndicator;
|
|
IBOutlet NSButton *reloadButton;
|
|
IBOutlet NSButton *backButton;
|
|
IBOutlet NSButton *forwardButton;
|
|
IBOutlet NSButton *share;
|
|
IBOutlet NSToolbar *toolbar;
|
|
IBOutlet NSTextField *urlText;
|
|
IBOutlet NSView *containerView;
|
|
IBOutlet NSButton *toggleUseShrinkToFitButton;
|
|
|
|
WKWebViewConfiguration *_configuration;
|
|
WKWebView *_webView;
|
|
BOOL _zoomTextOnly;
|
|
BOOL _isPrivateBrowsingWindow;
|
|
NSAlert* _alert;
|
|
|
|
BOOL _useShrinkToFit;
|
|
|
|
PlaywrightNSTextFinder *_textFinder;
|
|
NSView *_textFindBarView;
|
|
BOOL _findBarVisible;
|
|
}
|
|
|
|
- (id)initWithWindow:(NSWindow *)window
|
|
{
|
|
self = [super initWithWindow:window];
|
|
return self;
|
|
}
|
|
|
|
- (void)windowDidLoad
|
|
{
|
|
[share sendActionOn:NSEventMaskLeftMouseDown];
|
|
[super windowDidLoad];
|
|
}
|
|
|
|
- (IBAction)openLocation:(id)sender
|
|
{
|
|
[[self window] makeFirstResponder:urlText];
|
|
}
|
|
|
|
- (NSString *)addProtocolIfNecessary:(NSString *)address
|
|
{
|
|
if ([address rangeOfString:@"://"].length > 0)
|
|
return address;
|
|
|
|
if ([address hasPrefix:@"data:"])
|
|
return address;
|
|
|
|
if ([address hasPrefix:@"about:"])
|
|
return address;
|
|
|
|
return [@"http://" stringByAppendingString:address];
|
|
}
|
|
|
|
- (IBAction)share:(id)sender
|
|
{
|
|
NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[ self.currentURL ]];
|
|
picker.delegate = self;
|
|
[picker showRelativeToRect:NSZeroRect ofView:sender preferredEdge:NSRectEdgeMinY];
|
|
}
|
|
|
|
- (IBAction)showHideWebView:(id)sender
|
|
{
|
|
self.mainContentView.hidden = !self.mainContentView.isHidden;
|
|
}
|
|
|
|
- (CGFloat)pageScaleForMenuItemTag:(NSInteger)tag
|
|
{
|
|
if (tag == 1)
|
|
return 1;
|
|
if (tag == 2)
|
|
return 1.25;
|
|
if (tag == 3)
|
|
return 1.5;
|
|
if (tag == 4)
|
|
return 2.0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
_webView = [[WKWebView alloc] initWithFrame:[containerView bounds] configuration:_configuration];
|
|
_webView._windowOcclusionDetectionEnabled = NO;
|
|
|
|
_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;
|
|
|
|
_webView._observedRenderingProgressEvents = _WKRenderingProgressEventFirstLayout
|
|
| _WKRenderingProgressEventFirstVisuallyNonEmptyLayout
|
|
| _WKRenderingProgressEventFirstPaintWithSignificantArea
|
|
| _WKRenderingProgressEventFirstLayoutAfterSuppressedIncrementalRendering
|
|
| _WKRenderingProgressEventFirstPaintAfterSuppressedIncrementalRendering;
|
|
|
|
_webView.customUserAgent = @"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15";
|
|
|
|
_webView._usePlatformFindUI = NO;
|
|
|
|
_textFinder = [[PlaywrightNSTextFinder alloc] init];
|
|
_textFinder.incrementalSearchingEnabled = YES;
|
|
_textFinder.incrementalSearchingShouldDimContentView = NO;
|
|
_textFinder.client = _webView;
|
|
_textFinder.findBarContainer = self;
|
|
_textFinder.hideInterfaceCallback = ^{
|
|
[_webView _hideFindUI];
|
|
};
|
|
|
|
_zoomTextOnly = NO;
|
|
}
|
|
|
|
- (instancetype)initWithConfiguration:(WKWebViewConfiguration *)configuration
|
|
{
|
|
if (!(self = [super initWithWindowNibName:@"BrowserWindow"]))
|
|
return nil;
|
|
_configuration = [configuration copy];
|
|
_isPrivateBrowsingWindow = !_configuration.websiteDataStore.isPersistent;
|
|
self.window.styleMask &= ~NSWindowStyleMaskFullSizeContentView;
|
|
[self.window makeKeyAndOrderFront:nil];
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
[progressIndicator unbind:NSHiddenBinding];
|
|
[progressIndicator unbind:NSValueBinding];
|
|
|
|
[_textFinder release];
|
|
[_webView release];
|
|
[_configuration release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (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];
|
|
|
|
if (action == @selector(toggleZoomMode:))
|
|
[menuItem setState:_zoomTextOnly ? NSControlStateValueOn : NSControlStateValueOff];
|
|
else if (action == @selector(showHideWebInspector:))
|
|
[menuItem setTitle:_webView._inspector.isVisible ? @"Close Web Inspector" : @"Show Web Inspector"];
|
|
|
|
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)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)showHideWebInspector:(id)sender
|
|
{
|
|
_WKInspector *inspector = _webView._inspector;
|
|
if (inspector.isVisible)
|
|
[inspector hide];
|
|
else
|
|
[inspector show];
|
|
}
|
|
|
|
- (NSURL *)currentURL
|
|
{
|
|
return _webView.URL;
|
|
}
|
|
|
|
- (NSView *)mainContentView
|
|
{
|
|
return _webView;
|
|
}
|
|
|
|
- (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
|
|
{
|
|
[_webView removeObserver:self forKeyPath:@"title"];
|
|
[_webView removeObserver:self forKeyPath:@"URL"];
|
|
[_webView removeFromSuperview];
|
|
_textFinder.hideInterfaceCallback = nil;
|
|
[self release];
|
|
|
|
// Post two events (don't ask me why!) to spin event loop and drain
|
|
// automatically created autorelease pools that will release our window.
|
|
// See https://www.mikeash.com/pyblog/more-fun-with-autorelease.html
|
|
// for some discussion.
|
|
NSEvent* event1 = [NSEvent
|
|
otherEventWithType:NSEventTypeApplicationDefined
|
|
location:NSMakePoint(0, 0)
|
|
modifierFlags:0
|
|
timestamp:[[NSDate date] timeIntervalSince1970]
|
|
windowNumber:0
|
|
context:nil
|
|
subtype:0
|
|
data1:0
|
|
data2:0];
|
|
[NSApp postEvent:event1 atStart:YES];
|
|
NSEvent* event2 = [NSEvent
|
|
otherEventWithType:NSEventTypeApplicationDefined
|
|
location:NSMakePoint(0, 0)
|
|
modifierFlags:0
|
|
timestamp:[[NSDate date] timeIntervalSince1970]
|
|
windowNumber:0
|
|
context:nil
|
|
subtype:0
|
|
data1:0
|
|
data2:0];
|
|
[NSApp postEvent:event2 atStart:NO];
|
|
}
|
|
|
|
- (void)webViewDidClose:(WKWebView *)webView {
|
|
[self webView:webView handleJavaScriptDialog:false value:nil];
|
|
[self.window close];
|
|
}
|
|
|
|
- (void)_webView:(WKWebView *)webView getWindowFrameWithCompletionHandler:(void (^)(CGRect))completionHandler
|
|
{
|
|
completionHandler([self.window frame]);
|
|
}
|
|
|
|
#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)updateTitle:(NSString *)title
|
|
{
|
|
if (!title) {
|
|
NSURL *url = _webView.URL;
|
|
title = url.lastPathComponent ?: url._web_userVisibleString;
|
|
}
|
|
|
|
self.window.title = title;
|
|
}
|
|
|
|
- (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
|
|
{
|
|
// WebView lifecycle will control the BrowserWindowController life times.
|
|
BrowserWindowController *controller = [[BrowserWindowController alloc] initWithConfiguration:configuration];
|
|
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 = alert;
|
|
[alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
|
|
completionHandler();
|
|
[alert release];
|
|
_alert = nil;
|
|
}];
|
|
}
|
|
|
|
- (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 = alert;
|
|
[alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
|
|
completionHandler(response == NSAlertFirstButtonReturn);
|
|
[alert release];
|
|
_alert = nil;
|
|
}];
|
|
}
|
|
|
|
- (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 = alert;
|
|
[alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
|
|
[input validateEditing];
|
|
completionHandler(response == NSAlertFirstButtonReturn ? [input stringValue] : nil);
|
|
[alert release];
|
|
_alert = nil;
|
|
}];
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView handleJavaScriptDialog:(BOOL)accept value:(NSString *)value
|
|
{
|
|
if (!_alert)
|
|
return;
|
|
NSTextField* input = (NSTextField*)_alert.accessoryView;
|
|
if (accept && input && value)
|
|
[input setStringValue:value];
|
|
[self.window endSheet:_alert.window returnCode: accept ? NSAlertFirstButtonReturn : NSModalResponseCancel];
|
|
}
|
|
|
|
#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;
|
|
|
|
[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 = alert;
|
|
[alert beginSheetModalForWindow:self.window completionHandler:^void (NSModalResponse response) {
|
|
completionHandler(response == NSAlertFirstButtonReturn);
|
|
[alert release];
|
|
_alert = nil;
|
|
}];
|
|
}
|
|
|
|
- (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.shouldPerformDownload) {
|
|
decisionHandler(WKNavigationActionPolicyDownload);
|
|
return;
|
|
}
|
|
|
|
if (navigationAction._canHandleRequest) {
|
|
decisionHandler(WKNavigationActionPolicyAllow);
|
|
return;
|
|
}
|
|
decisionHandler(WKNavigationActionPolicyCancel);
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
|
|
{
|
|
if (![navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
decisionHandler(WKNavigationResponsePolicyAllow);
|
|
return;
|
|
}
|
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)navigationResponse.response;
|
|
|
|
NSString *disposition = [[httpResponse allHeaderFields] objectForKey:@"Content-Disposition"];
|
|
if (disposition && [disposition hasPrefix:@"attachment"]) {
|
|
decisionHandler(WKNavigationResponsePolicyDownload);
|
|
return;
|
|
}
|
|
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];
|
|
}];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (WKWebView *)webView
|
|
{
|
|
return _webView;
|
|
}
|
|
|
|
@end
|