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
495 lines
20 KiB
Objective-C
495 lines
20 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 "AppDelegate.h"
|
|
|
|
#import "BrowserWindowController.h"
|
|
#import <WebKit/WKNavigationActionPrivate.h>
|
|
#import <WebKit/WKNavigationDelegatePrivate.h>
|
|
#import <WebKit/WKPreferencesPrivate.h>
|
|
#import <WebKit/WKProcessPoolPrivate.h>
|
|
#import <WebKit/WKUserContentControllerPrivate.h>
|
|
#import <WebKit/WKWebViewConfigurationPrivate.h>
|
|
#import <WebKit/WKWebViewPrivate.h>
|
|
#import <WebKit/WKWebsiteDataStorePrivate.h>
|
|
#import <WebKit/WebNSURLExtras.h>
|
|
#import <WebKit/WebKit.h>
|
|
#import <WebKit/_WKDownload.h>
|
|
#import <WebKit/_WKExperimentalFeature.h>
|
|
#import <WebKit/_WKInternalDebugFeature.h>
|
|
#import <WebKit/_WKProcessPoolConfiguration.h>
|
|
#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
|
|
|
|
@implementation NSApplication (PlaywrightApplicationExtensions)
|
|
|
|
- (BrowserAppDelegate *)browserAppDelegate
|
|
{
|
|
return (BrowserAppDelegate *)[self delegate];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface NSApplication (TouchBar)
|
|
@property (getter=isAutomaticCustomizeTouchBarMenuItemEnabled) BOOL automaticCustomizeTouchBarMenuItemEnabled;
|
|
|
|
@property (readonly, nonatomic) WKWebViewConfiguration *defaultConfiguration;
|
|
|
|
@end
|
|
|
|
@implementation WebViewDialog
|
|
- (void)dealloc
|
|
{
|
|
[_webView release];
|
|
_webView = nil;
|
|
[super dealloc];
|
|
}
|
|
@end
|
|
|
|
enum {
|
|
_NSBackingStoreUnbuffered = 3
|
|
};
|
|
|
|
NSString* const ActivityReason = @"Batch headless process";
|
|
const NSActivityOptions ActivityOptions =
|
|
(NSActivityUserInitiatedAllowingIdleSystemSleep |
|
|
NSActivityLatencyCritical) &
|
|
~(NSActivitySuddenTerminationDisabled |
|
|
NSActivityAutomaticTerminationDisabled);
|
|
|
|
@implementation BrowserAppDelegate
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (!self)
|
|
return nil;
|
|
|
|
_initialURL = nil;
|
|
_userDataDir = nil;
|
|
_proxyServer = nil;
|
|
_proxyBypassList = nil;
|
|
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
|
|
NSRange subargs = NSMakeRange(1, [arguments count] - 1);
|
|
NSArray *subArray = [arguments subarrayWithRange:subargs];
|
|
|
|
for (NSString *argument in subArray) {
|
|
if (![argument hasPrefix:@"--"])
|
|
_initialURL = argument;
|
|
if ([argument hasPrefix:@"--user-data-dir="]) {
|
|
NSRange range = NSMakeRange(16, [argument length] - 16);
|
|
_userDataDir = [[argument substringWithRange:range] copy];
|
|
}
|
|
if ([argument hasPrefix:@"--proxy="]) {
|
|
NSRange range = NSMakeRange(8, [argument length] - 8);
|
|
_proxyServer = [[argument substringWithRange:range] copy];
|
|
}
|
|
if ([argument hasPrefix:@"--proxy-bypass-list="]) {
|
|
NSRange range = NSMakeRange(20, [argument length] - 20);
|
|
_proxyBypassList = [[argument substringWithRange:range] copy];
|
|
}
|
|
}
|
|
|
|
_headless = [arguments containsObject: @"--headless"];
|
|
_noStartupWindow = [arguments containsObject: @"--no-startup-window"];
|
|
_browserContexts = [[NSMutableSet alloc] init];
|
|
|
|
if (_headless) {
|
|
_headlessWindows = [[NSMutableSet alloc] init];
|
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
|
[[NSProcessInfo processInfo] beginActivityWithOptions:ActivityOptions
|
|
reason:ActivityReason];
|
|
_dialogs = [[NSMutableSet alloc] init];
|
|
} else {
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
}
|
|
if ([arguments containsObject: @"--inspector-pipe"])
|
|
[_WKBrowserInspector initializeRemoteInspectorPipe:self headless:_headless];
|
|
return self;
|
|
}
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
if ([NSApp respondsToSelector:@selector(setAutomaticCustomizeTouchBarMenuItemEnabled:)])
|
|
[NSApp setAutomaticCustomizeTouchBarMenuItemEnabled:YES];
|
|
}
|
|
|
|
|
|
- (NSDictionary *)proxyConfiguration:(NSString *)proxyServer WithBypassList:(NSString *)proxyBypassList
|
|
{
|
|
if (!proxyServer || ![proxyServer length])
|
|
return nil;
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
|
|
NSURL *proxyURL = [NSURL URLWithString:proxyServer];
|
|
NSString *host = [proxyURL host];
|
|
NSNumber *port = [proxyURL port];
|
|
if ([proxyServer hasPrefix:@"socks5://"]) {
|
|
[dictionary setObject:host forKey:(NSString *)kCFStreamPropertySOCKSProxyHost];
|
|
if (port)
|
|
[dictionary setObject:port forKey:(NSString *)kCFStreamPropertySOCKSProxyPort];
|
|
} else {
|
|
[dictionary setObject:host forKey:(NSString *)kCFStreamPropertyHTTPSProxyHost];
|
|
[dictionary setObject:host forKey:(NSString *)kCFStreamPropertyHTTPProxyHost];
|
|
if (port) {
|
|
[dictionary setObject:port forKey:(NSString *)kCFStreamPropertyHTTPSProxyPort];
|
|
[dictionary setObject:port forKey:(NSString *)kCFStreamPropertyHTTPProxyPort];
|
|
}
|
|
}
|
|
|
|
if (proxyBypassList && [proxyBypassList length]) {
|
|
NSArray* bypassList = [proxyBypassList componentsSeparatedByString:@","];
|
|
[dictionary setObject:bypassList forKey:@"ExceptionsList"];
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
return dictionary;
|
|
}
|
|
|
|
- (WKWebsiteDataStore *)persistentDataStore
|
|
{
|
|
static WKWebsiteDataStore *dataStore;
|
|
|
|
if (!dataStore) {
|
|
_WKWebsiteDataStoreConfiguration *configuration = [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
|
|
if (_userDataDir) {
|
|
NSURL *cookieFile = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/cookie.db", _userDataDir]];
|
|
[configuration _setCookieStorageFile:cookieFile];
|
|
|
|
NSURL *applicationCacheDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/ApplicationCache", _userDataDir]];
|
|
[configuration setApplicationCacheDirectory:applicationCacheDirectory];
|
|
|
|
NSURL *cacheStorageDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/CacheStorage", _userDataDir]];
|
|
[configuration _setCacheStorageDirectory:cacheStorageDirectory];
|
|
|
|
NSURL *indexedDBDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/IndexedDB", _userDataDir]];
|
|
[configuration _setIndexedDBDatabaseDirectory:indexedDBDirectory];
|
|
|
|
NSURL *localStorageDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/LocalStorage", _userDataDir]];
|
|
[configuration _setWebStorageDirectory:localStorageDirectory];
|
|
|
|
NSURL *mediaCacheDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/MediaCache", _userDataDir]];
|
|
[configuration setMediaCacheDirectory:mediaCacheDirectory];
|
|
|
|
NSURL *mediaKeysDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/MediaKeys", _userDataDir]];
|
|
[configuration setMediaKeysStorageDirectory:mediaKeysDirectory];
|
|
|
|
NSURL *networkCacheDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/NetworkCache", _userDataDir]];
|
|
[configuration setNetworkCacheDirectory:networkCacheDirectory];
|
|
|
|
NSURL *loadStatsDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/ResourceLoadStatistics", _userDataDir]];
|
|
[configuration _setResourceLoadStatisticsDirectory:loadStatsDirectory];
|
|
|
|
NSURL *serviceWorkersDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/ServiceWorkers", _userDataDir]];
|
|
[configuration _setServiceWorkerRegistrationDirectory:serviceWorkersDirectory];
|
|
|
|
NSURL *webSqlDirectory = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/WebSQL", _userDataDir]];
|
|
[configuration _setWebSQLDatabaseDirectory:webSqlDirectory];
|
|
}
|
|
[configuration setProxyConfiguration:[self proxyConfiguration:_proxyServer WithBypassList:_proxyBypassList]];
|
|
dataStore = [[WKWebsiteDataStore alloc] _initWithConfiguration:configuration];
|
|
}
|
|
|
|
return dataStore;
|
|
}
|
|
|
|
- (WKWebViewConfiguration *)defaultConfiguration
|
|
{
|
|
static WKWebViewConfiguration *configuration;
|
|
|
|
if (!configuration) {
|
|
configuration = [[WKWebViewConfiguration alloc] init];
|
|
configuration.websiteDataStore = [self persistentDataStore];
|
|
configuration.preferences._fullScreenEnabled = YES;
|
|
configuration.preferences._developerExtrasEnabled = YES;
|
|
configuration.preferences._mediaDevicesEnabled = YES;
|
|
configuration.preferences._mockCaptureDevicesEnabled = YES;
|
|
configuration.preferences._hiddenPageDOMTimerThrottlingEnabled = NO;
|
|
configuration.preferences._hiddenPageDOMTimerThrottlingAutoIncreases = NO;
|
|
configuration.preferences._pageVisibilityBasedProcessSuppressionEnabled = NO;
|
|
configuration.preferences._domTimersThrottlingEnabled = NO;
|
|
configuration.preferences._requestAnimationFrameEnabled = YES;
|
|
_WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease];
|
|
processConfiguration.forceOverlayScrollbars = YES;
|
|
configuration.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];
|
|
}
|
|
return configuration;
|
|
}
|
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
{
|
|
if (!_headless)
|
|
[self _updateNewWindowKeyEquivalents];
|
|
|
|
if (_noStartupWindow)
|
|
return;
|
|
|
|
// Force creation of the default browser context.
|
|
[self defaultConfiguration];
|
|
// Creating the first NSWindow immediately makes it invisible in headless mode,
|
|
// so we postpone it for 50ms. Experiments show that 10ms is not enough, and 20ms is enough.
|
|
// We give it 50ms just in case.
|
|
[NSTimer scheduledTimerWithTimeInterval: 0.05
|
|
repeats: NO
|
|
block:(void *)^(NSTimer* timer)
|
|
{
|
|
[self createNewPage:0 withURL:_initialURL ? _initialURL : @"about:blank"];
|
|
_initialURL = nil;
|
|
}];
|
|
}
|
|
|
|
- (void)_updateNewWindowKeyEquivalents
|
|
{
|
|
NSString *normalWindowEquivalent = @"n";
|
|
_newWebKit2WindowItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
|
|
_newWebKit2WindowItem.keyEquivalent = normalWindowEquivalent;
|
|
}
|
|
|
|
#pragma mark WKBrowserInspectorDelegate
|
|
|
|
- (WKWebViewConfiguration *) sessionConfiguration:(uint64_t)sessionID
|
|
{
|
|
for (_WKBrowserContext *browserContext in _browserContexts) {
|
|
if ([[browserContext dataStore] sessionID] != sessionID)
|
|
continue;
|
|
WKWebViewConfiguration *configuration = [[[self defaultConfiguration] copy] autorelease];
|
|
configuration.websiteDataStore = [browserContext dataStore];
|
|
configuration.processPool = [browserContext processPool];
|
|
return configuration;
|
|
}
|
|
return [self defaultConfiguration];
|
|
}
|
|
|
|
- (WKWebView *)createNewPage:(uint64_t)sessionID
|
|
{
|
|
return [self createNewPage:sessionID withURL:@"about:blank"];
|
|
}
|
|
|
|
- (WKWebView *)createNewPage:(uint64_t)sessionID withURL:(NSString*)urlString
|
|
{
|
|
WKWebViewConfiguration *configuration = [self sessionConfiguration:sessionID];
|
|
if (_headless)
|
|
return [self createHeadlessPage:configuration withURL:urlString];
|
|
return [self createHeadfulPage:configuration withURL:urlString];
|
|
}
|
|
|
|
- (WKWebView *)createHeadfulPage:(WKWebViewConfiguration *)configuration withURL:(NSString*)urlString
|
|
{
|
|
// WebView lifecycle will control the BrowserWindowController life times.
|
|
BrowserWindowController *controller = [[BrowserWindowController alloc] initWithConfiguration:configuration];
|
|
if (!controller)
|
|
return nil;
|
|
[controller loadURLString:urlString];
|
|
NSWindow *window = controller.window;
|
|
[window setIsVisible:YES];
|
|
return [controller webView];
|
|
}
|
|
|
|
- (WKWebView *)createHeadlessPage:(WKWebViewConfiguration *)configuration withURL:(NSString*)urlString
|
|
{
|
|
NSRect rect = NSMakeRect(0, 0, 1280, 720);
|
|
NSScreen *firstScreen = [[NSScreen screens] objectAtIndex:0];
|
|
NSRect windowRect = NSOffsetRect(rect, -10000, [firstScreen frame].size.height - rect.size.height + 10000);
|
|
NSWindow* window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSWindowStyleMaskBorderless backing:(NSBackingStoreType)_NSBackingStoreUnbuffered defer:YES];
|
|
|
|
WKWebView* webView = [[WKWebView alloc] initWithFrame:[window.contentView bounds] configuration:configuration];
|
|
webView._windowOcclusionDetectionEnabled = NO;
|
|
if (!webView)
|
|
return nil;
|
|
|
|
webView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
[window.contentView addSubview:webView];
|
|
[window setIsVisible:YES];
|
|
if (urlString) {
|
|
NSURL *url = [NSURL _webkit_URLWithUserTypedString:urlString];
|
|
[webView loadRequest:[NSURLRequest requestWithURL:url]];
|
|
}
|
|
[_headlessWindows addObject:window];
|
|
webView.navigationDelegate = self;
|
|
webView.UIDelegate = self;
|
|
return [webView autorelease];
|
|
}
|
|
|
|
- (_WKBrowserContext *)createBrowserContext:(NSString *)proxyServer WithBypassList:(NSString *) proxyBypassList
|
|
{
|
|
_WKBrowserContext *browserContext = [[_WKBrowserContext alloc] init];
|
|
_WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease];
|
|
processConfiguration.forceOverlayScrollbars = YES;
|
|
_WKWebsiteDataStoreConfiguration *dataStoreConfiguration = [[[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration] autorelease];
|
|
if (!proxyServer || ![proxyServer length])
|
|
proxyServer = _proxyServer;
|
|
if (!proxyBypassList || ![proxyBypassList length])
|
|
proxyBypassList = _proxyBypassList;
|
|
[dataStoreConfiguration setProxyConfiguration:[self proxyConfiguration:proxyServer WithBypassList:proxyBypassList]];
|
|
browserContext.dataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration] autorelease];
|
|
browserContext.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];
|
|
[_browserContexts addObject:browserContext];
|
|
return browserContext;
|
|
}
|
|
|
|
- (void)deleteBrowserContext:(uint64_t)sessionID
|
|
{
|
|
for (_WKBrowserContext *browserContext in _browserContexts) {
|
|
if ([[browserContext dataStore] sessionID] != sessionID)
|
|
continue;
|
|
[_browserContexts removeObject:browserContext];
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (void)quit
|
|
{
|
|
[NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
|
|
}
|
|
|
|
#pragma mark WKUIDelegate
|
|
|
|
- (void)webViewDidClose:(WKWebView *)webView {
|
|
[self webView:webView handleJavaScriptDialog:false value:nil];
|
|
for (NSWindow *window in _headlessWindows) {
|
|
if (webView.window != window)
|
|
continue;
|
|
[webView removeFromSuperview];
|
|
[window close];
|
|
[_headlessWindows removeObject:window];
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)_webView:(WKWebView *)webView getWindowFrameWithCompletionHandler:(void (^)(CGRect))completionHandler
|
|
{
|
|
completionHandler([webView.window frame]);
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
|
|
{
|
|
WebViewDialog* dialog = [[WebViewDialog alloc] autorelease];
|
|
dialog.webView = webView;
|
|
dialog.completionHandler = ^void (BOOL accept, NSString* value) {
|
|
completionHandler();
|
|
};
|
|
[_dialogs addObject:dialog];
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
|
|
{
|
|
WebViewDialog* dialog = [[WebViewDialog alloc] autorelease];
|
|
dialog.webView = webView;
|
|
dialog.completionHandler = ^void (BOOL accept, NSString* value) {
|
|
completionHandler(accept);
|
|
};
|
|
[_dialogs addObject:dialog];
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler
|
|
{
|
|
WebViewDialog* dialog = [[WebViewDialog alloc] autorelease];
|
|
dialog.webView = webView;
|
|
dialog.completionHandler = ^void (BOOL accept, NSString* value) {
|
|
completionHandler(accept && value ? value : nil);
|
|
};
|
|
[_dialogs addObject:dialog];
|
|
}
|
|
|
|
- (void)_webView:(WKWebView *)webView runBeforeUnloadConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler
|
|
{
|
|
WebViewDialog* dialog = [[WebViewDialog alloc] autorelease];
|
|
dialog.webView = webView;
|
|
dialog.completionHandler = ^void (BOOL accept, NSString* value) {
|
|
completionHandler(accept);
|
|
};
|
|
[_dialogs addObject:dialog];
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView handleJavaScriptDialog:(BOOL)accept value:(NSString *)value
|
|
{
|
|
for (WebViewDialog *dialog in _dialogs) {
|
|
if (dialog.webView != webView)
|
|
continue;
|
|
dialog.completionHandler(accept, value);
|
|
[_dialogs removeObject:dialog];
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
|
|
{
|
|
return [self createHeadlessPage:configuration withURL:nil];
|
|
}
|
|
|
|
- (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 navigationAction:(WKNavigationAction *)navigationAction didBecomeDownload:(WKDownload *)download
|
|
{
|
|
download.delegate = self;
|
|
}
|
|
|
|
- (void)webView:(WKWebView *)webView navigationResponse:(WKNavigationResponse *)navigationResponse didBecomeDownload:(WKDownload *)download
|
|
{
|
|
download.delegate = self;
|
|
}
|
|
|
|
#pragma mark WKDownloadDelegate
|
|
|
|
- (void)download:(WKDownload *)download decideDestinationUsingResponse:(NSURLResponse *)response suggestedFilename:(NSString *)suggestedFilename completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
|
|
{
|
|
completionHandler(nil);
|
|
}
|
|
|
|
@end
|