/* * 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 #import #import #import #import #import #import #import #import #import #import #import #import #import #import 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 () @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 )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.window close]; } #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 = [NSString stringWithFormat:@"%@%@ [%d]%@", _isPrivateBrowsingWindow ? @"🙈 " : @"", title, _webView._webProcessIdentifier, @""]; } - (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 * 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._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(_WKNavigationResponsePolicyBecomeDownload); 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