blob: 69b1c65f5e1f2d737ed5ac313002dacbb1026d91 [file] [log] [blame] [edit]
/*
* Copyright (C) 2022-2024 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.
*/
#if !__has_feature(objc_arc)
#error This file requires ARC. Add the "-fobjc-arc" compiler flag for this file.
#endif
#import "config.h"
#if ENABLE(WK_WEB_EXTENSIONS)
#import "TestWebExtensionsDelegate.h"
#import "WebExtensionUtilities.h"
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKWebExtensionTabConfiguration.h>
#import <WebKit/WKWebExtensionWindowConfiguration.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <wtf/cocoa/TypeCastsCocoa.h>
#import <wtf/darwin/DispatchExtras.h>
// Enable this to test all web extension tests with site isolation.
static constexpr BOOL shouldEnableSiteIsolation = NO;
@interface TestWebExtensionManager () <WKWebExtensionControllerDelegatePrivate>
@end
@implementation TestWebExtensionManager {
bool _done;
bool _receivedMessage;
bool _runningTestFromQueue;
NSMutableDictionary *_messages;
NSMutableArray *_windows;
}
- (instancetype)initForExtension:(WKWebExtension *)extension
{
return [self initForExtension:extension extensionControllerConfiguration:nil];
}
- (instancetype)initForExtension:(WKWebExtension *)extension extensionControllerConfiguration:(WKWebExtensionControllerConfiguration *)configuration
{
if (!(self = [super init]))
return nil;
if (!configuration)
configuration = WKWebExtensionControllerConfiguration.nonPersistentConfiguration;
configuration.webViewConfiguration.preferences._siteIsolationEnabled = shouldEnableSiteIsolation;
_extension = extension;
_context = [[WKWebExtensionContext alloc] initForExtension:extension];
_controller = [[WKWebExtensionController alloc] initWithConfiguration:configuration];
// Grant all requested API permissions.
for (WKWebExtensionPermission permission in _extension.requestedPermissions)
[_context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:permission];
_controller._testingMode = YES;
// This should always be self. If you need the delegate, use the controllerDelegate property.
// Delegate method calls will be forwarded to the controllerDelegate.
_controller.delegate = self;
_internalDelegate = [[TestWebExtensionsDelegate alloc] init];
auto *window = [[TestWebExtensionWindow alloc] initWithExtensionController:_controller usesPrivateBrowsing:NO];
auto *windows = [NSMutableArray arrayWithObject:window];
_internalDelegate.openWindows = ^NSArray<id<WKWebExtensionWindow>> *(WKWebExtensionContext *) {
return [windows copy];
};
_internalDelegate.focusedWindow = ^id<WKWebExtensionWindow>(WKWebExtensionContext *) {
return window;
};
#if PLATFORM(MAC)
__weak TestWebExtensionManager *weakSelf = self;
_internalDelegate.openNewWindow = ^(WKWebExtensionWindowConfiguration *configuration, WKWebExtensionContext *context, void (^completionHandler)(id<WKWebExtensionWindow>, NSError *)) {
auto *newWindow = [weakSelf openNewWindowUsingPrivateBrowsing:configuration.shouldBePrivate];
newWindow.windowType = configuration.windowType;
newWindow.windowState = configuration.windowState;
CGRect currentFrame = newWindow.frame;
CGRect desiredFrame = configuration.frame;
if (std::isnan(desiredFrame.size.width))
desiredFrame.size.width = currentFrame.size.width;
if (std::isnan(desiredFrame.size.height))
desiredFrame.size.height = currentFrame.size.height;
if (std::isnan(desiredFrame.origin.x))
desiredFrame.origin.x = currentFrame.origin.x;
CGRect screenFrame = newWindow.screenFrame;
CGFloat screenTop = screenFrame.size.height + screenFrame.origin.y;
if (std::isnan(desiredFrame.origin.y)) {
// Calculate the current top to keep the top-left corner of the window at the same position if the height changed.
CGFloat currentTop = screenTop - currentFrame.size.height - currentFrame.origin.y;
desiredFrame.origin.y = screenTop - desiredFrame.size.height - currentTop;
}
newWindow.frame = desiredFrame;
bool isFirstURL = true;
for (NSURL *url in configuration.tabURLs) {
auto *targetTab = isFirstURL ? newWindow.tabs.firstObject : [newWindow openNewTab];
[targetTab changeWebViewIfNeededForURL:url forExtensionContext:context];
[targetTab.webView loadRequest:[NSURLRequest requestWithURL:url]];
isFirstURL = false;
}
completionHandler(newWindow, nil);
};
#endif // PLATFORM(MAC)
_internalDelegate.openNewTab = ^(WKWebExtensionTabConfiguration *configuration, WKWebExtensionContext *context, void (^completionHandler)(id<WKWebExtensionTab>, NSError *)) {
auto *desiredWindow = dynamic_objc_cast<TestWebExtensionWindow>(configuration.window) ?: window;
auto *newTab = [desiredWindow openNewTabAtIndex:configuration.index];
if (configuration.url) {
[newTab changeWebViewIfNeededForURL:configuration.url forExtensionContext:context];
[newTab.webView loadRequest:[NSURLRequest requestWithURL:configuration.url]];
}
newTab.parentTab = configuration.parentTab;
newTab.pinned = configuration.shouldBePinned;
newTab.muted = configuration.shouldBeMuted;
newTab.showingReaderMode = configuration.shouldReaderModeBeActive;
newTab.selected = configuration.shouldAddToSelection;
if (configuration.shouldBeActive)
desiredWindow.activeTab = newTab;
completionHandler(newTab, nil);
};
_windows = windows;
_defaultWindow = window;
_defaultTab = window.tabs.firstObject;
_controllerDelegate = _internalDelegate;
_testsAdded = [NSArray array];
_testsStarted = [NSArray array];
_testResults = [NSDictionary dictionary];
return self;
}
- (void)dealloc
{
[self unload];
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [_controllerDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}
- (id)forwardingTargetForSelector:(SEL)selector
{
return [_controllerDelegate respondsToSelector:selector] ? _controllerDelegate : [super forwardingTargetForSelector:selector];
}
- (TestWebExtensionWindow *)openNewWindow
{
return [self openNewWindowUsingPrivateBrowsing:NO];
}
- (TestWebExtensionWindow *)openNewWindowUsingPrivateBrowsing:(BOOL)usesPrivateBrowsing
{
auto *newWindow = [[TestWebExtensionWindow alloc] initWithExtensionController:_controller usesPrivateBrowsing:usesPrivateBrowsing];
__weak TestWebExtensionManager *weakSelf = self;
__weak TestWebExtensionWindow *weakWindow = newWindow;
newWindow.didFocus = ^{
[weakSelf focusWindow:weakWindow];
};
newWindow.didClose = ^{
[weakSelf closeWindow:weakWindow];
};
[_windows addObject:newWindow];
[_controller didOpenWindow:newWindow];
return newWindow;
}
- (void)focusWindow:(TestWebExtensionWindow *)window
{
if (window) {
[_windows removeObject:window];
[_windows insertObject:window atIndex:0];
}
[_controller didFocusWindow:window];
}
- (void)closeWindow:(TestWebExtensionWindow *)window
{
for (TestWebExtensionTab *tab in window.tabs)
[window closeTab:tab windowIsClosing:YES];
[_windows removeObject:window];
_defaultWindow = _windows.firstObject;
[_controller didCloseWindow:window];
[_controller didFocusWindow:_defaultWindow];
}
- (void)sendTestMessage:(NSString *)message
{
[self sendTestMessage:message withArgument:nil];
}
- (void)sendTestMessage:(NSString *)message withArgument:(id)argument
{
[_context _sendTestMessage:message withArgument:argument];
}
- (void)sendTestStartedWithArgument:(id)argument
{
[_context _sendTestStartedWithArgument:argument];
}
- (void)sendTestFinishedWithArgument:(id)argument
{
[_context _sendTestFinishedWithArgument:argument];
}
- (void)load
{
NSError *error;
EXPECT_TRUE([_controller loadExtensionContext:_context error:&error]);
EXPECT_NULL(error);
}
- (void)unload
{
NSError *error;
EXPECT_TRUE([_controller unloadExtensionContext:_context error:&error]);
EXPECT_NULL(error);
}
- (void)run
{
_done = false;
TestWebKitAPI::Util::run(&_done);
}
- (void)runForTimeInterval:(NSTimeInterval)interval
{
_done = false;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), mainDispatchQueueSingleton(), ^{
self->_done = true;
});
TestWebKitAPI::Util::run(&_done);
}
- (id)runUntilTestMessage:(NSString *)message
{
id (^processMessage)(void) = ^id {
NSMutableArray *messagesArray = self->_messages[message];
if (!messagesArray.count)
return nil;
id argument = messagesArray.firstObject;
[messagesArray removeObjectAtIndex:0];
return argument;
};
if (id result = processMessage())
return result;
while (true) {
_receivedMessage = false;
TestWebKitAPI::Util::run(&_receivedMessage);
if (id result = processMessage())
return result;
}
}
- (void)loadAndRun
{
[self load];
[self run];
}
- (void)done
{
_done = true;
}
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestAssertionResult:(BOOL)result withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
if (result || _runningTestFromQueue)
return;
if (!message.length)
message = @"Assertion failed with no message.";
::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, sourceURL.UTF8String, lineNumber, message.UTF8String) = ::testing::Message();
}
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestEqualityResult:(BOOL)result expectedValue:(NSString *)expectedValue actualValue:(NSString *)actualValue withMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
if (result || _runningTestFromQueue)
return;
if (!message.length)
message = @"Expected equality of these values";
::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, sourceURL.UTF8String, lineNumber, "") = ::testing::Message()
<< message.UTF8String << ":\n"
<< " Actual: " << actualValue.UTF8String << "\n"
<< "Expected: " << expectedValue.UTF8String;
}
- (void)_webExtensionController:(WKWebExtensionController *)controller logTestMessage:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
printf("\n%s:%u\n%s\n\n", sourceURL.UTF8String, lineNumber, message.UTF8String);
}
- (void)_webExtensionController:(WKWebExtensionController *)controller receivedTestMessage:(NSString *)message withArgument:(id)argument andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
_receivedMessage = true;
if (!_messages)
_messages = [NSMutableDictionary dictionary];
NSMutableArray *messagesArray = _messages[message];
if (!messagesArray) {
messagesArray = [NSMutableArray array];
_messages[message] = messagesArray;
}
[messagesArray addObject:argument ?: NSNull.null];
}
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestAddedWithName:(NSString *)testName andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
_testsAdded = [_testsAdded arrayByAddingObject:testName];
}
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestStartedWithName:(NSString *)testName andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
_runningTestFromQueue = YES;
_testsStarted = [_testsStarted arrayByAddingObject:testName];
}
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestFinishedWithName:(NSString *)testName result:(BOOL)result message:(NSString *)message andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber
{
if (_runningTestFromQueue) {
_runningTestFromQueue = NO;
NSMutableDictionary<NSString *, id> *testResults = [_testResults mutableCopy];
testResults[testName] = @(result);
_testResults = [NSDictionary dictionaryWithDictionary:testResults];
return;
}
_done = true;
if (result)
return;
if (!message.length)
message = @"Test failed with no message.";
::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, sourceURL.UTF8String, lineNumber, message.UTF8String) = ::testing::Message();
}
@end
static WKUserContentController *userContentController(BOOL usingPrivateBrowsing)
{
static WKUserContentController *privateController = [[WKUserContentController alloc] init];
static WKUserContentController *normalController = [[WKUserContentController alloc] init];
return usingPrivateBrowsing ? privateController : normalController;
}
@interface TestWebExtensionTab () <WKNavigationDelegate>
@end
@implementation TestWebExtensionTab {
__weak WKWebExtensionController *_extensionController;
}
- (instancetype)init
{
return [self initWithWindow:nil extensionController:nil];
}
- (instancetype)initWithWindow:(TestWebExtensionWindow *)window extensionController:(WKWebExtensionController *)extensionController
{
if (!(self = [super init]))
return nil;
_window = window;
if (extensionController) {
BOOL usingPrivateBrowsing = _window.usingPrivateBrowsing;
auto *configuration = [[WKWebViewConfiguration alloc] init];
configuration.webExtensionController = extensionController;
configuration.websiteDataStore = usingPrivateBrowsing ? WKWebsiteDataStore.nonPersistentDataStore : WKWebsiteDataStore.defaultDataStore;
configuration.userContentController = userContentController(usingPrivateBrowsing);
auto *preferences = configuration.preferences;
preferences._siteIsolationEnabled = shouldEnableSiteIsolation;
preferences._developerExtrasEnabled = YES;
_webView = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration];
_webView.navigationDelegate = self;
_extensionController = extensionController;
}
return self;
}
- (void)assignWindow:(TestWebExtensionWindow *)window
{
_window = window;
}
- (void)setWindow:(TestWebExtensionWindow *)newWindow
{
auto *oldWindow = _window;
_window = newWindow;
NSUInteger oldIndex = oldWindow ? [oldWindow removeTab:self] : NSNotFound;
if (newWindow)
[newWindow insertTab:self atIndex:newWindow.tabs.count];
[_extensionController didMoveTab:self fromIndex:oldIndex inWindow:oldWindow];
}
- (id<WKWebExtensionWindow>)windowForWebExtensionContext:(WKWebExtensionContext *)context
{
return _window;
}
- (NSUInteger)indexInWindowForWebExtensionContext:(WKWebExtensionContext *)context
{
return _window ? [_window.tabs indexOfObject:self] : NSNotFound;
}
- (void)changeWebViewIfNeededForURL:(NSURL *)url forExtensionContext:(WKWebExtensionContext *)context
{
BOOL usingPrivateBrowsing = _window.usingPrivateBrowsing;
if ([_webView.URL.scheme isEqualToString:url.scheme])
return;
auto *configuration = [url.scheme hasPrefix:@"http"] ? [[WKWebViewConfiguration alloc] init] : context.webViewConfiguration;
configuration.webExtensionController = _extensionController;
configuration.websiteDataStore = usingPrivateBrowsing ? WKWebsiteDataStore.nonPersistentDataStore : WKWebsiteDataStore.defaultDataStore;
configuration.userContentController = userContentController(usingPrivateBrowsing);
auto *preferences = configuration.preferences;
preferences._siteIsolationEnabled = shouldEnableSiteIsolation;
preferences._developerExtrasEnabled = YES;
_webView = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration];
_webView.navigationDelegate = self;
}
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
[_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesLoading forTab:self];
}
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
{
[_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesLoading forTab:self];
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
[_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesLoading forTab:self];
}
- (void)_webView:(WKWebView *)webView navigationDidFinishDocumentLoad:(WKNavigation *)navigation
{
[_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesTitle | WKWebExtensionTabChangedPropertiesURL forTab:self];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
[_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesLoading forTab:self];
}
- (WKWebView *)webViewForWebExtensionContext:(WKWebExtensionContext *)context
{
return _webView;
}
- (BOOL)isReaderModeActiveForWebExtensionContext:(WKWebExtensionContext *)context
{
return _showingReaderMode;
}
- (void)setReaderModeActive:(BOOL)showing forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_setReaderModeShowing)
self->_setReaderModeShowing(showing);
self->_showingReaderMode = showing;
[self->_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesReaderMode forTab:self];
completionHandler(nil);
});
}
- (void)detectWebpageLocaleForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSLocale *, NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_webpageLocale)
completionHandler(self->_webpageLocale(), nil);
else
completionHandler(nil, nil);
});
}
- (void)reloadFromOrigin:(BOOL)fromOrigin forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_reload)
self->_reload(fromOrigin);
else if (fromOrigin)
[self->_webView reloadFromOrigin];
else
[self->_webView reload];
completionHandler(nil);
});
}
- (void)goBackForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_goBack)
self->_goBack();
else
[self->_webView goBack];
completionHandler(nil);
});
}
- (void)goForwardForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_goForward)
self->_goForward();
else
[self->_webView goForward];
completionHandler(nil);
});
}
- (id<WKWebExtensionTab>)parentTabForWebExtensionContext:(WKWebExtensionContext *)context
{
return _parentTab;
}
- (void)setParentTab:(id<WKWebExtensionTab>)parentTab forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_parentTab = dynamic_objc_cast<TestWebExtensionTab>(parentTab);
completionHandler(nil);
});
}
- (BOOL)isPinnedForWebExtensionContext:(WKWebExtensionContext *)context
{
return _pinned;
}
- (void)setPinned:(BOOL)pinned forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_pinned = pinned;
[self->_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesPinned forTab:self];
completionHandler(nil);
});
}
- (BOOL)isMutedForWebExtensionContext:(WKWebExtensionContext *)context
{
return _muted;
}
- (void)setMuted:(BOOL)muted forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_muted = muted;
[self->_extensionController didChangeTabProperties:WKWebExtensionTabChangedPropertiesMuted forTab:self];
completionHandler(nil);
});
}
- (void)duplicateUsingConfiguration:(WKWebExtensionTabConfiguration *)configuration forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(id<WKWebExtensionTab> duplicatedTab, NSError *error))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_duplicate)
self->_duplicate(configuration, completionHandler);
else
completionHandler(nil, nil);
});
}
- (void)activateForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
auto *previousActiveTab = self->_window.activeTab;
self->_window.activeTab = self;
self->_selected = YES;
[self->_extensionController didActivateTab:self previousActiveTab:previousActiveTab];
completionHandler(nil);
});
}
- (BOOL)isSelectedForWebExtensionContext:(WKWebExtensionContext *)context
{
return _selected || _window.activeTab == self;
}
- (void)setSelected:(BOOL)selected forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_selected = selected;
if (selected)
[self->_extensionController didSelectTabs:@[ self ]];
else
[self->_extensionController didDeselectTabs:@[ self ]];
completionHandler(nil);
});
}
- (void)closeForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
[self->_window closeTab:self];
completionHandler(nil);
});
}
- (BOOL)shouldBypassPermissionsForWebExtensionContext:(WKWebExtensionContext *)context
{
return _shouldBypassPermissions;
}
@end
@implementation TestWebExtensionWindow {
__weak WKWebExtensionController *_extensionController;
CGRect _previousFrame;
NSMutableArray<TestWebExtensionTab *> *_tabs;
}
- (instancetype)init
{
return [self initWithExtensionController:nil usesPrivateBrowsing:NO];
}
- (instancetype)initWithExtensionController:(WKWebExtensionController *)extensionController usesPrivateBrowsing:(BOOL)usesPrivateBrowsing
{
if (!(self = [super init]))
return nil;
_extensionController = extensionController;
_windowState = WKWebExtensionWindowStateNormal;
_windowType = WKWebExtensionWindowTypeNormal;
_usingPrivateBrowsing = usesPrivateBrowsing;
_screenFrame = CGRectMake(0, 0, 1920, 1080);
#if PLATFORM(MAC)
// This is 50pt from top on the screen in Mac screen coordinates.
_frame = CGRectMake(100, _screenFrame.size.height - 600 - 50, 800, 600);
#else
_frame = CGRectMake(100, 50, 800, 600);
#endif
_previousFrame = CGRectNull;
_tabs = [NSMutableArray array];
_activeTab = [self openNewTab];
return self;
}
- (NSArray<TestWebExtensionTab *> *)tabs
{
return [_tabs copy];
}
- (void)setTabs:(NSArray<TestWebExtensionTab *> *)tabs
{
auto *previousActiveTab = _activeTab;
for (TestWebExtensionTab *tab in _tabs) {
[tab.webView _close];
tab.webView = nil;
[_extensionController didCloseTab:tab windowIsClosing:NO];
[tab assignWindow:nil];
}
_tabs = [tabs mutableCopy];
_activeTab = _tabs.firstObject;
for (TestWebExtensionTab *tab in _tabs)
[_extensionController didOpenTab:tab];
if (_activeTab)
[_extensionController didActivateTab:_activeTab previousActiveTab:previousActiveTab];
}
- (NSUInteger)removeTab:(TestWebExtensionTab *)tab
{
NSUInteger oldIndex = [_tabs indexOfObject:tab];
if (oldIndex == NSNotFound)
return oldIndex;
[_tabs removeObjectAtIndex:oldIndex];
if (_activeTab == tab)
_activeTab = _tabs.firstObject;
return oldIndex;
}
- (void)insertTab:(TestWebExtensionTab *)tab atIndex:(NSUInteger)index
{
ASSERT(index <= _tabs.count);
[_tabs insertObject:tab atIndex:index];
}
- (TestWebExtensionTab *)openNewTab
{
return [self openNewTabAtIndex:_tabs.count];
}
- (TestWebExtensionTab *)openNewTabAtIndex:(NSUInteger)index
{
index = MIN(index, _tabs.count);
auto *newTab = [[TestWebExtensionTab alloc] initWithWindow:self extensionController:_extensionController];
__weak TestWebExtensionTab *weakTab = newTab;
newTab.duplicate = ^(WKWebExtensionTabConfiguration *configuration, void (^completionHandler)(TestWebExtensionTab *, NSError *)) {
auto *desiredWindow = dynamic_objc_cast<TestWebExtensionWindow>(configuration.window) ?: weakTab.window;
auto *duplicatedTab = [desiredWindow openNewTabAtIndex:configuration.index];
[duplicatedTab.webView loadRequest:[NSURLRequest requestWithURL:weakTab.webView.URL]];
duplicatedTab.selected = configuration.shouldAddToSelection;
if (configuration.shouldBeActive)
desiredWindow.activeTab = duplicatedTab;
completionHandler(duplicatedTab, nil);
};
if (!_activeTab)
_activeTab = newTab;
[_tabs insertObject:newTab atIndex:index];
[_extensionController didOpenTab:newTab];
return newTab;
}
- (void)closeTab:(TestWebExtensionTab *)tab
{
[self closeTab:tab windowIsClosing:NO];
}
- (void)closeTab:(TestWebExtensionTab *)tab windowIsClosing:(BOOL)windowIsClosing
{
[tab.webView _close];
tab.webView = nil;
[_tabs removeObject:tab];
if (tab == _activeTab) {
_activeTab = _tabs.firstObject;
if (_activeTab)
[_extensionController didActivateTab:_activeTab previousActiveTab:tab];
}
[_extensionController didCloseTab:tab windowIsClosing:windowIsClosing];
[tab assignWindow:nil];
}
- (void)replaceTab:(TestWebExtensionTab *)oldTab withTab:(TestWebExtensionTab *)newTab
{
ASSERT([_tabs containsObject:oldTab]);
ASSERT(![_tabs containsObject:newTab]);
[oldTab.webView _close];
oldTab.webView = nil;
if (_activeTab == oldTab)
_activeTab = newTab;
[_tabs replaceObjectAtIndex:[_tabs indexOfObject:oldTab] withObject:newTab];
[_extensionController didReplaceTab:oldTab withTab:newTab];
[oldTab assignWindow:nil];
}
- (void)moveTab:(TestWebExtensionTab *)tab toIndex:(NSUInteger)newIndex
{
if (tab.window != self) {
TestWebExtensionWindow *oldWindow = tab.window;
auto oldIndex = [oldWindow->_tabs indexOfObject:tab];
ASSERT(oldIndex != NSNotFound);
[oldWindow->_tabs removeObjectAtIndex:oldIndex];
[_tabs insertObject:tab atIndex:newIndex];
[tab assignWindow:self];
[_extensionController didMoveTab:tab fromIndex:oldIndex inWindow:oldWindow];
return;
}
auto oldIndex = [_tabs indexOfObject:tab];
ASSERT(oldIndex != NSNotFound);
[_tabs removeObjectAtIndex:oldIndex];
[_tabs insertObject:tab atIndex:newIndex];
[_extensionController didMoveTab:tab fromIndex:oldIndex inWindow:self];
}
- (NSArray<id<WKWebExtensionTab>> *)tabsForWebExtensionContext:(WKWebExtensionContext *)context
{
return _tabs;
}
- (id<WKWebExtensionTab>)activeTabForWebExtensionContext:(WKWebExtensionContext *)context
{
return _activeTab;
}
- (WKWebExtensionWindowType)windowTypeForWebExtensionContext:(WKWebExtensionContext *)context
{
return _windowType;
}
- (WKWebExtensionWindowState)windowStateForWebExtensionContext:(WKWebExtensionContext *)context
{
return _windowState;
}
- (void)setWindowState:(WKWebExtensionWindowState)state forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *error))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_windowState = state;
if (state == WKWebExtensionWindowStateFullscreen) {
self->_previousFrame = self->_frame;
self->_frame = self->_screenFrame;
} else if (!CGRectIsEmpty(self->_previousFrame)) {
self->_frame = self->_previousFrame;
self->_previousFrame = CGRectNull;
}
completionHandler(nil);
});
}
- (BOOL)isPrivateForWebExtensionContext:(WKWebExtensionContext *)context
{
return _usingPrivateBrowsing;
}
- (CGRect)screenFrameForWebExtensionContext:(WKWebExtensionContext *)context
{
return _screenFrame;
}
- (CGRect)frameForWebExtensionContext:(WKWebExtensionContext *)context
{
return _frame;
}
- (void)setFrame:(CGRect)frame forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *error))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
self->_frame = frame;
completionHandler(nil);
});
}
- (void)focusForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *error))completionHandler
{
if (_didFocus)
_didFocus();
completionHandler(nil);
}
- (void)closeForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *error))completionHandler
{
dispatch_async(mainDispatchQueueSingleton(), ^{
if (self->_didClose)
self->_didClose();
completionHandler(nil);
});
}
@end
namespace TestWebKitAPI {
namespace Util {
RetainPtr<TestWebExtensionManager> parseExtension(NSDictionary *manifest, NSDictionary *resources, WKWebExtensionControllerConfiguration *configuration)
{
auto extension = adoptNS([[WKWebExtension alloc] _initWithManifestDictionary:manifest resources:resources]);
return adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get() extensionControllerConfiguration:configuration]);
}
RetainPtr<TestWebExtensionManager> loadExtension(NSDictionary *manifest, NSDictionary *resources, WKWebExtensionControllerConfiguration *configuration)
{
auto manager = parseExtension(manifest, resources, configuration);
[manager load];
return manager;
}
void loadAndRunExtension(NSDictionary *manifest, NSDictionary *resources, WKWebExtensionControllerConfiguration *configuration)
{
[loadExtension(manifest, resources, configuration) run];
}
NSData *makePNGData(CGSize size, SEL colorSelector)
{
#if USE(APPKIT)
auto image = adoptNS([[NSImage alloc] initWithSize:size]);
[image lockFocus];
[[NSColor performSelector:colorSelector] setFill];
NSRectFill(NSMakeRect(0, 0, size.width, size.height));
[image unlockFocus];
auto cgImageRef = [image CGImageForProposedRect:NULL context:nil hints:nil];
auto newImageRep = adoptNS([[NSBitmapImageRep alloc] initWithCGImage:cgImageRef]);
newImageRep.get().size = size;
return [newImageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{ }];
#else
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
[[UIColor performSelector:colorSelector] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
auto *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return UIImagePNGRepresentation(image);
#endif
}
void performWithAppearance(Appearance appearance, void (^block)(void))
{
#if USE(APPKIT)
auto *appearanceName = appearance == Appearance::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua;
[[NSAppearance appearanceNamed:appearanceName] performAsCurrentDrawingAppearance:block];
#else
auto *traitCollection = appearance == Appearance::Dark ? [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]
: [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
[traitCollection performAsCurrentTraitCollection:block];
#endif
}
} // namespace Util
} // namespace TestWebKitAPI
#endif // ENABLE(WK_WEB_EXTENSIONS)