mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-17 00:04:50 +03:00
a3f34fb4b7
This leaves our firefox diff to gecko instrumentation changes only. Drive-by: rename webkit "src" folder into "embedder".
413 lines
16 KiB
Objective-C
413 lines
16 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;
|
|
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];
|
|
}
|
|
}
|
|
|
|
_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];
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
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 AndDataStore:configuration.websiteDataStore] autorelease];
|
|
}
|
|
return configuration;
|
|
}
|
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
{
|
|
if (!_headless)
|
|
[self _updateNewWindowKeyEquivalents];
|
|
|
|
if (_noStartupWindow)
|
|
return;
|
|
|
|
[self createNewPage:0];
|
|
_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
|
|
{
|
|
NSString* urlString = _initialURL ? _initialURL : @"about:blank";
|
|
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
|
|
{
|
|
_WKBrowserContext *browserContext = [[_WKBrowserContext alloc] init];
|
|
_WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease];
|
|
processConfiguration.forceOverlayScrollbars = YES;
|
|
browserContext.dataStore = [WKWebsiteDataStore nonPersistentDataStore];
|
|
browserContext.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];
|
|
[browserContext.processPool _setDownloadDelegate:self];
|
|
[_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 {
|
|
for (NSWindow *window in _headlessWindows) {
|
|
if (webView.window != window)
|
|
continue;
|
|
[webView removeFromSuperview];
|
|
[window close];
|
|
[_headlessWindows removeObject:window];
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (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(_WKNavigationResponsePolicyBecomeDownload);
|
|
return;
|
|
}
|
|
decisionHandler(WKNavigationResponsePolicyAllow);
|
|
}
|
|
|
|
#pragma mark _WKDownloadDelegate
|
|
|
|
- (void)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename completionHandler:(void (^)(BOOL allowOverwrite, NSString *destination))completionHandler
|
|
{
|
|
completionHandler(NO, @"");
|
|
}
|
|
|
|
@end
|