blob: ed9fb1e540ee34e6eeb4837b951ba5255b13390a [file] [log] [blame] [edit]
/*
* Copyright (C) 2023 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 "config.h"
#if ENABLE(WK_WEB_EXTENSIONS)
#import "TestWebExtensionsDelegate.h"
#import "WebExtensionUtilities.h"
#import <WebKit/_WKWebExtensionWindowCreationOptions.h>
@interface DummyWebExtensionWindow : NSObject <_WKWebExtensionWindow>
@end
@implementation DummyWebExtensionWindow
@end
namespace TestWebKitAPI {
static auto *windowsManifest = @{
@"manifest_version": @3,
@"name": @"Windows Test",
@"description": @"Windows Test",
@"version": @"1",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
};
TEST(WKWebExtensionAPIWindows, Errors)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.test.assertThrows(() => browser.windows.get('bad'), /'windowID' value is invalid, because a number is expected/i)",
@"browser.test.assertThrows(() => browser.windows.get(NaN), /'windowID' value is invalid, because a number is expected/i)",
@"browser.test.assertThrows(() => browser.windows.get(Infinity), /'windowID' value is invalid, because a number is expected/i)",
@"browser.test.assertThrows(() => browser.windows.get(-Infinity), /'windowID' value is invalid, because a number is expected/i)",
@"browser.test.assertThrows(() => browser.windows.get(-3), /'windowID' value is invalid, because it is not a window identifier/i)",
@"browser.test.assertThrows(() => browser.windows.get(1.2), /'windowID' value is invalid, because it is not a window identifier/i)",
@"browser.test.assertThrows(() => browser.windows.get(browser.windows.WINDOW_ID_NONE), /'windowID' value is invalid, because 'windows.WINDOW_ID_NONE' is not allowed/i)",
@"await browser.test.assertRejects(browser.windows.get(42), /window not found/i)",
#if PLATFORM(MAC)
// iOS does not support create() and update().
@"await browser.test.assertRejects(browser.windows.update(99, { focused: true }), /window not found/i)",
@"browser.test.assertThrows(() => browser.windows.create({ url: 42 }), /'url' is expected to be a string or an array of strings, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.windows.create({ url: 'bad' }), /'url' value is invalid, because 'bad' is not a valid URL/i)",
@"browser.test.assertThrows(() => browser.windows.create({ url: ['bad'] }), /'url' value is invalid, because 'bad' is not a valid URL/i)",
@"browser.test.assertThrows(() => browser.windows.create({ left: 'bad' }), /'left' is expected to be a number/i)",
@"browser.test.assertThrows(() => browser.windows.create({ top: 'bad' }), /'top' is expected to be a number/i)",
@"browser.test.assertThrows(() => browser.windows.create({ width: 'bad' }), /'width' is expected to be a number/i)",
@"browser.test.assertThrows(() => browser.windows.create({ height: 'bad' }), /'height' is expected to be a number/i)",
@"browser.test.assertThrows(() => browser.windows.create({ incognito: 'bad' }), /'incognito' is expected to be a boolean/i)",
@"browser.test.assertThrows(() => browser.windows.create({ focused: 'bad' }), /'focused' is expected to be a boolean/i)",
@"browser.test.assertThrows(() => browser.windows.create({ tabId: 'bad' }), /'tabId' is expected to be a number/i)",
@"const window = await browser.test.assertSafeResolve(() => browser.windows.getCurrent())",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { left: 'bad' }), /'left' is expected to be a number, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { top: 'bad' }), /'top' is expected to be a number, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { width: 'bad' }), /'width' is expected to be a number, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { height: 'bad' }), /'height' is expected to be a number, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { focused: 'bad' }), /'focused' is expected to be a boolean, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { state: 'bad' }), /'state' value is invalid, because it must specify 'normal', 'minimized', 'maximized', or 'fullscreen'/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { top: 100, state: 'fullscreen' }), /'properties' value is invalid, because when 'top', 'left', 'width', or 'height' are specified, 'state' must specify 'normal'/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { left: 100, state: 'minimized' }), /'properties' value is invalid, because when 'top', 'left', 'width', or 'height' are specified, 'state' must specify 'normal'/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { width: 100, state: 'maximized' }), /'properties' value is invalid, because when 'top', 'left', 'width', or 'height' are specified, 'state' must specify 'normal'/i)",
@"browser.test.assertThrows(() => browser.windows.update(window.id, { height: 100, state: 'fullscreen' }), /'properties' value is invalid, because when 'top', 'left', 'width', or 'height' are specified, 'state' must specify 'normal'/i)",
#endif
@"browser.test.notifyPass()"
]);
Util::loadAndRunExtension(windowsManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIWindows, GetCurrent)
{
auto *backgroundScript = Util::constructScript(@[
@"const window = await browser.windows.getCurrent();",
@"browser.test.assertEq(typeof window, 'object', 'The window should be an object');",
@"browser.test.assertEq(typeof window.id, 'number', 'The window id should be a number');",
@"browser.test.assertEq(window.type, 'normal', 'Window type should be normal');",
@"browser.test.assertEq(window.state, 'normal', 'Window state should be normal');",
@"browser.test.assertTrue(window.focused, 'Window should be focused');",
@"browser.test.assertFalse(window.incognito, 'Window should not be in incognito mode');",
@"browser.test.assertFalse(window.alwaysOnTop, 'Window should not be always on top');",
@"browser.test.assertEq(window.top, 50, 'Window top position should be 50');",
@"browser.test.assertEq(window.left, 100, 'Window left position should be 100');",
@"browser.test.assertEq(window.width, 800, 'Window width should be 800');",
@"browser.test.assertEq(window.height, 600, 'Window height should be 600');",
@"browser.test.notifyPass();"
]);
Util::loadAndRunExtension(windowsManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIWindows, GetLastFocused)
{
auto *backgroundScript = Util::constructScript(@[
@"const window = await browser.windows.getLastFocused();",
@"browser.test.assertEq(typeof window, 'object', 'The window should be an object');",
@"browser.test.assertEq(typeof window.id, 'number', 'The window id should be a number');",
@"browser.test.assertEq(window.type, 'normal', 'Window type should be normal');",
@"browser.test.assertEq(window.state, 'normal', 'Window state should be normal');",
@"browser.test.assertFalse(window.focused, 'Window should be focused');",
@"browser.test.assertFalse(window.incognito, 'Window should not be in incognito mode');",
@"browser.test.assertFalse(window.alwaysOnTop, 'Window should not be always on top');",
@"browser.test.assertEq(window.top, 50, 'Window top position should be 50');",
@"browser.test.assertEq(window.left, 100, 'Window left position should be 100');",
@"browser.test.assertEq(window.width, 800, 'Window width should be 800');",
@"browser.test.assertEq(window.height, 600, 'Window height should be 600');",
@"browser.test.notifyPass();"
]);
auto extension = adoptNS([[_WKWebExtension alloc] _initWithManifestDictionary:windowsManifest resources:@{ @"background.js": backgroundScript }]);
auto manager = adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get()]);
manager.get().internalDelegate.focusedWindow = ^id<_WKWebExtensionWindow>(_WKWebExtensionContext *) {
return nil;
};
[manager loadAndRun];
}
TEST(WKWebExtensionAPIWindows, GetAll)
{
auto *backgroundScript = Util::constructScript(@[
@"const windows = await browser.windows.getAll();",
@"const windowOne = windows[0];",
@"const windowTwo = windows[1];",
@"browser.test.assertEq(windows.length, 2, 'Two windows should be returned');",
// Validate first window's properties
@"browser.test.assertEq(typeof windowOne, 'object', 'windowOne should be an object');",
@"browser.test.assertEq(typeof windowOne.id, 'number', 'windowOne id should be a number');",
@"browser.test.assertEq(windowOne.type, 'normal', 'windowOne type should be normal');",
@"browser.test.assertEq(windowOne.state, 'normal', 'windowOne state should be normal');",
@"browser.test.assertTrue(windowOne.focused, 'windowOne should be focused');",
@"browser.test.assertFalse(windowOne.incognito, 'windowOne should not be in incognito mode');",
@"browser.test.assertFalse(windowOne.alwaysOnTop, 'windowOne should not be always on top');",
@"browser.test.assertEq(windowOne.top, 50, 'windowOne top position should be 50');",
@"browser.test.assertEq(windowOne.left, 100, 'windowOne left position should be 100');",
@"browser.test.assertEq(windowOne.width, 800, 'windowOne width should be 800');",
@"browser.test.assertEq(windowOne.height, 600, 'windowOne height should be 600');",
// Validate second window's properties
@"browser.test.assertEq(typeof windowTwo, 'object', 'windowTwo should be an object');",
@"browser.test.assertEq(typeof windowTwo.id, 'number', 'windowTwo id should be a number');",
@"browser.test.assertEq(windowTwo.type, 'popup', 'windowTwo type should be popup');",
@"browser.test.assertEq(windowTwo.state, 'minimized', 'windowTwo state should be minimized');",
@"browser.test.assertFalse(windowTwo.focused, 'windowTwo should not be focused');",
@"browser.test.assertTrue(windowTwo.incognito, 'windowTwo should be in incognito mode');",
@"browser.test.assertEq(windowTwo.top, 75, 'windowTwo top position should be 75');",
@"browser.test.assertEq(windowTwo.left, 110, 'windowTwo left position should be 110');",
@"browser.test.assertEq(windowTwo.width, 300, 'windowTwo width should be 300');",
@"browser.test.assertEq(windowTwo.height, 700, 'windowTwo height should be 700');",
// Validate that windowOne.id and windowTwo.id are different
@"browser.test.assertTrue(windowOne.id !== windowTwo.id, 'windowOne.id and windowTwo.id should be different');",
@"browser.test.notifyPass();"
]);
auto extension = adoptNS([[_WKWebExtension alloc] _initWithManifestDictionary:windowsManifest resources:@{ @"background.js": backgroundScript }]);
auto manager = adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get()]);
auto *windowTwo = [manager openNewWindow];
#if PLATFORM(MAC)
// This is 75pt from top on a screen of 1920 x 1080 in Mac screen coordinates.
windowTwo.frame = CGRectMake(110, 305, 300, 700);
#else
windowTwo.frame = CGRectMake(110, 75, 300, 700);
#endif
windowTwo.windowState = _WKWebExtensionWindowStateMinimized;
windowTwo.windowType = _WKWebExtensionWindowTypePopup;
windowTwo.usingPrivateBrowsing = YES;
[manager loadAndRun];
}
TEST(WKWebExtensionAPIWindows, CreatedEvent)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.windows.onCreated.addListener((window) => {",
@" browser.test.assertEq(typeof window, 'object', 'The window should be an object');",
@" browser.test.assertEq(typeof window.id, 'number', 'The window id should be a number');",
@" browser.test.assertEq(window.type, 'normal', 'Window type should be normal');",
@" browser.test.assertEq(window.state, 'normal', 'Window state should be normal');",
@" browser.test.assertTrue(window.focused, 'Window should be focused');",
@" browser.test.assertFalse(window.incognito, 'Window should not be in incognito mode');",
@" browser.test.assertFalse(window.alwaysOnTop, 'Window should not be always on top');",
@" browser.test.assertEq(window.top, 50, 'Window top position should be 50');",
@" browser.test.assertEq(window.left, 100, 'Window left position should be 100');",
@" browser.test.assertEq(window.width, 800, 'Window width should be 800');",
@" browser.test.assertEq(window.height, 600, 'Window height should be 600');",
@" browser.test.notifyPass();",
@"});",
@"browser.test.yield('Open Window');"
]);
auto extension = adoptNS([[_WKWebExtension alloc] _initWithManifestDictionary:windowsManifest resources:@{ @"background.js": backgroundScript }]);
auto manager = adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get()]);
[manager loadAndRun];
EXPECT_NS_EQUAL(manager.get().yieldMessage, @"Open Window");
[manager openNewWindow];
[manager run];
}
TEST(WKWebExtensionAPIWindows, FocusChangedEvent)
{
auto *backgroundScript = Util::constructScript(@[
@"let focusedTwice = false;",
@"browser.windows.onFocusChanged.addListener((windowId) => {",
@" if (windowId !== browser.windows.WINDOW_ID_NONE) {",
@" if (!focusedTwice) {",
@" browser.test.assertEq(typeof windowId, 'number', 'The window id should be a number when a window is focused');",
@" browser.test.yield('Focus None');",
@" focusedTwice = true;",
@" } else {",
@" browser.test.assertEq(typeof windowId, 'number', 'The window id should be a number when a window is focused again');",
@" browser.test.notifyPass();",
@" }",
@" } else {",
@" browser.test.assertEq(windowId, browser.windows.WINDOW_ID_NONE, 'The window id should be WINDOW_ID_NONE when nil is focused');",
@" browser.test.yield('Focus Window Again');",
@" }",
@"});",
@"browser.test.yield('Focus Window');"
]);
auto extension = adoptNS([[_WKWebExtension alloc] _initWithManifestDictionary:windowsManifest resources:@{ @"background.js": backgroundScript }]);
auto manager = adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get()]);
auto *windowOne = manager.get().defaultWindow;
[manager load];
auto *windowTwo = [manager openNewWindow];
[manager run];
EXPECT_NS_EQUAL(manager.get().yieldMessage, @"Focus Window");
[manager focusWindow:windowOne];
[manager run];
EXPECT_NS_EQUAL(manager.get().yieldMessage, @"Focus None");
[manager focusWindow:nil];
[manager run];
EXPECT_NS_EQUAL(manager.get().yieldMessage, @"Focus Window Again");
[manager focusWindow:windowTwo];
[manager run];
}
TEST(WKWebExtensionAPIWindows, RemovedEvent)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.windows.onRemoved.addListener((windowId) => {",
@" browser.test.assertEq(typeof windowId, 'number', 'The window id should be a number');",
@" browser.test.notifyPass();",
@"});",
@"browser.test.yield('Close Window');"
]);
auto extension = adoptNS([[_WKWebExtension alloc] _initWithManifestDictionary:windowsManifest resources:@{ @"background.js": backgroundScript }]);
auto manager = adoptNS([[TestWebExtensionManager alloc] initForExtension:extension.get()]);
[manager load];
[manager openNewWindow];
[manager run];
EXPECT_NS_EQUAL(manager.get().yieldMessage, @"Close Window");
[manager closeWindow:manager.get().defaultWindow];
[manager run];
}
#if PLATFORM(MAC)
// iOS does not support create() and update().
TEST(WKWebExtensionAPIWindows, Create)
{
auto *backgroundScript = Util::constructScript(@[
@"const windowOptions = {",
@" focused: true,",
@" left: 300,",
@" height: 400,",
@" incognito: true,",
@" state: 'normal',",
@" type: 'popup',",
@"};",
@"const window = await browser.windows.create(windowOptions);",
@"browser.test.assertEq(typeof window, 'object', 'The window should be an object');",
@"browser.test.assertEq(window.top, 50, 'The window should have the specified top');",
@"browser.test.assertEq(window.left, 300, 'The window should have the specified left');",
@"browser.test.assertEq(window.width, 800, 'The window should have the specified width');",
@"browser.test.assertEq(window.height, 400, 'The window should have the specified height');",
@"browser.test.assertTrue(window.incognito, 'The window should be in incognito mode');",
@"browser.test.assertEq(window.type, 'popup', 'The window should be of type popup');",
@"browser.test.assertEq(window.state, 'normal', 'The window state should be normal');",
@"browser.test.assertTrue(window.focused, 'The window should be focused');",
@"browser.test.notifyPass();"
]);
Util::loadAndRunExtension(windowsManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIWindows, Update)
{
auto *backgroundScript = Util::constructScript(@[
@"const newWindow = await browser.windows.create();",
@"browser.test.assertEq(newWindow.top, 50, 'The new window top position should be 50 initially');",
@"browser.test.assertEq(newWindow.left, 100, 'The new window left position should be 100 initially');",
@"browser.test.assertEq(newWindow.width, 800, 'The new window width should be 800 initially');",
@"browser.test.assertEq(newWindow.height, 600, 'The new window height should be 600 initially');",
@"browser.test.assertTrue(newWindow.focused, 'The new window should be focused initially');",
@"browser.test.assertEq(newWindow.state, 'normal', 'The window state should be normal initially');",
@"let updatedWindow = await browser.windows.update(newWindow.id, { top: 10, width: 500, focused: true });",
@"browser.test.assertEq(updatedWindow.top, 10, 'The window top position should be updated to 10');",
@"browser.test.assertEq(updatedWindow.left, 100, 'The window left position should remain 100 after the first update');",
@"browser.test.assertEq(updatedWindow.width, 500, 'The window width should be updated to 500');",
@"browser.test.assertEq(updatedWindow.height, 600, 'The window height should remain 600 after the first update');",
@"browser.test.assertTrue(updatedWindow.focused, 'The window should be focused');",
@"browser.test.assertEq(updatedWindow.state, 'normal', 'The window state should remain normal after the first update');",
@"updatedWindow = await browser.windows.update(newWindow.id, { state: 'fullscreen' });",
@"browser.test.assertEq(updatedWindow.state, 'fullscreen', 'The window state should be updated to fullscreen');",
@"browser.test.assertEq(updatedWindow.top, 0, 'The window top position should be 0 in fullscreen');",
@"browser.test.assertEq(updatedWindow.left, 0, 'The window left position should be 0 in fullscreen');",
@"browser.test.assertEq(updatedWindow.width, 1920, 'The window width should be 1920 in fullscreen');",
@"browser.test.assertEq(updatedWindow.height, 1080, 'The window height should be 1080 in fullscreen');",
@"updatedWindow = await browser.windows.update(newWindow.id, { state: 'normal' });",
@"browser.test.assertEq(updatedWindow.top, 10, 'The window top position should be reverted back to 10');",
@"browser.test.assertEq(updatedWindow.left, 100, 'The window left position should remain 100');",
@"browser.test.assertEq(updatedWindow.width, 500, 'The window width should be reverted back to 500');",
@"browser.test.assertEq(updatedWindow.height, 600, 'The window height should be reverted back to 600');",
@"browser.test.assertEq(updatedWindow.state, 'normal', 'The window state should be reverted back to normal');",
@"browser.test.notifyPass();"
]);
Util::loadAndRunExtension(windowsManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIWindows, Remove)
{
auto *backgroundScript = Util::constructScript(@[
// Create a new window and verify the count
@"const initialWindows = await browser.windows.getAll();",
@"const newWindow = await browser.windows.create();",
@"const windowsAfterCreate = await browser.windows.getAll();",
@"browser.test.assertEq(windowsAfterCreate.length, initialWindows.length + 1, 'One window should be added after creation');",
// Remove the window and verify the count
@"await browser.windows.remove(newWindow.id);",
@"const windowsAfterRemove = await browser.windows.getAll();",
@"browser.test.assertEq(windowsAfterRemove.length, initialWindows.length, 'Window count should match the initial count after removal');",
@"browser.test.notifyPass();"
]);
Util::loadAndRunExtension(windowsManifest, @{ @"background.js": backgroundScript });
}
#endif // PLATFORM(MAC)
} // namespace TestWebKitAPI
#endif // ENABLE(WK_WEB_EXTENSIONS)