Skip to content

Commit

Permalink
Add support for simple Web Extension tabs APIs.
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=261412
rdar://problem/115291352

Reviewed by Brian Weinstein.

Support for tabs.reload(), goBack(), goForward(), detectLanguage(), toggleReaderMode(),
setZoom(), getZoom(), and remove().

* Source/WebKit/Platform/cocoa/CocoaHelpers.h:
* Source/WebKit/Platform/cocoa/CocoaHelpers.mm:
(WebKit::localeStringInWebExtensionFormat): Deleted.
* Source/WebKit/Shared/Extensions/WebExtensionTab.serialization.in:
* Source/WebKit/Shared/Extensions/WebExtensionTabIdentifier.h:
* Source/WebKit/Shared/Extensions/WebExtensionUtilities.h:
* Source/WebKit/Shared/Extensions/WebExtensionUtilities.mm:
(WebKit::toWebAPI): Added. Moved localeStringInWebExtensionFormat from CocoaHelpers.
Since this is a Web Extension specific function it makes more since here.
* Source/WebKit/Shared/Extensions/WebExtensionWindowIdentifier.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.mm:
(toAPI): Use WindowVector and TabMapValueIterator from WebExtensionContext.
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm:
(WebKit::WebExtensionContext::tabsReload): Added.
(WebKit::WebExtensionContext::tabsGoBack): Added.
(WebKit::WebExtensionContext::tabsGoForward): Added.
(WebKit::WebExtensionContext::tabsDetectLanguage): Added.
(WebKit::WebExtensionContext::tabsToggleReaderMode): Added.
(WebKit::WebExtensionContext::tabsGetZoom): Added.
(WebKit::WebExtensionContext::tabsSetZoom): Added.
(WebKit::WebExtensionContext::tabsRemove): Added.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:
(WebKit::WebExtensionContext::getWindow): Use page identifier to find the tab.
(WebKit::WebExtensionContext::getTab): Added. Use page identifier to lookup active tab in window
if a tab identifier isn't specified.
(WebKit::WebExtensionContext::openWindows const): Made const and fixed a typo.
(WebKit::WebExtensionContext::openWindows): Deleted.
(WebKit::WebExtensionContext::openTabs): Deleted. Made inline in the header.
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
(WebKit::WebExtensionContext::openTabs const):
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.messages.in:
* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPILocalizationCocoa.mm:
(WebKit::WebExtensionAPILocalization::getUILanguage):
* Source/WebKit/WebProcess/Extensions/API/Cocoa/WebExtensionAPITabsCocoa.mm:
(WebKit::WebExtensionAPITabs::remove): Implemented. Send message to the UIProcess.
(WebKit::WebExtensionAPITabs::reload): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::goBack): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::goForward): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::getZoom): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::setZoom): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::detectLanguage): Implemented. Ditto.
(WebKit::WebExtensionAPITabs::toggleReaderMode): Implemented. Ditto.
* Source/WebKit/WebProcess/Extensions/API/WebExtensionAPITabs.h:
* Source/WebKit/WebProcess/Extensions/Interfaces/WebExtensionAPITabs.idl: Added NeedsPage for more functions.
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPITabs.mm:
(TestWebKitAPI::TEST): Added tests for all added APIs.
* Tools/TestWebKitAPI/cocoa/WebExtensionUtilities.h:
* Tools/TestWebKitAPI/cocoa/WebExtensionUtilities.mm:
(-[TestWebExtensionTab initWithWindow:extensionController:]): Store the extensions controller for later.
(-[TestWebExtensionTab isShowingReaderModeForWebExtensionContext:]): Added.
(-[TestWebExtensionTab toggleReaderModeForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab detectWebpageLocaleForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab reloadForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab reloadFromOriginForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab goBackForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab goForwardForWebExtensionContext:completionHandler:]): Added.
(-[TestWebExtensionTab closeForWebExtensionContext:completionHandler:]):
(-[TestWebExtensionWindow init]): Make _tabs mutable.
(-[TestWebExtensionWindow tabs]): Added.
(-[TestWebExtensionWindow setTabs:]): Mutable copy.
(-[TestWebExtensionWindow closeTab:]): Added.

Canonical link: https://commits.webkit.org/267880@main
  • Loading branch information
xeenon committed Sep 11, 2023
1 parent b84c512 commit 4f73dc6
Show file tree
Hide file tree
Showing 19 changed files with 745 additions and 95 deletions.
4 changes: 0 additions & 4 deletions Source/WebKit/Platform/cocoa/CocoaHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ NSDictionary *dictionaryWithLowercaseKeys(NSDictionary *);
NSDictionary *mergeDictionaries(NSDictionary *, NSDictionary *);
NSDictionary *mergeDictionariesAndSetValues(NSDictionary *, NSDictionary *);

// MARK: NSLocale helper methods.

NSString *localeStringInWebExtensionFormat(NSLocale *);

// MARK: NSError helper methods.

NSString *privacyPreservingDescription(NSError *);
Expand Down
12 changes: 0 additions & 12 deletions Source/WebKit/Platform/cocoa/CocoaHelpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -242,18 +242,6 @@
return [newDictionary copy];
}

// MARK: NSLocale helper methods.

NSString *localeStringInWebExtensionFormat(NSLocale *locale)
{
if (!locale.languageCode)
return @"";

if (locale.countryCode.length)
return [NSString stringWithFormat:@"%@-%@", locale.languageCode, locale.countryCode];
return locale.languageCode;
}

// MARK: NSError helper methods

NSString *privacyPreservingDescription(NSError *error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#if ENABLE(WK_WEB_EXTENSIONS)

headers: "WebExtensionContext.h"

struct WebKit::WebExtensionTabParameters {
std::optional<WebKit::WebExtensionTabIdentifier> identifier;

Expand Down Expand Up @@ -64,4 +66,6 @@ struct WebKit::WebExtensionTabQueryParameters {
std::optional<bool> selected;
}

[Nested] enum class WebKit::WebExtensionContext::ReloadFromOrigin : bool;

#endif
6 changes: 4 additions & 2 deletions Source/WebKit/Shared/Extensions/WebExtensionTabIdentifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ namespace WebKit {
struct WebExtensionTabIdentifierType;
using WebExtensionTabIdentifier = ObjectIdentifier<WebExtensionTabIdentifierType>;

struct WebExtensionTabConstants {
namespace WebExtensionTabConstants {

static constexpr double None { -1 };

static constexpr const WebExtensionTabIdentifier NoneIdentifier { std::numeric_limits<uint64_t>::max() - 1 };
};

}

inline bool isNone(WebExtensionTabIdentifier identifier)
{
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/Shared/Extensions/WebExtensionUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ NSString *toErrorString(NSString *callingAPIName, NSString *sourceKey, NSString
/// Returns an error object that combines the provided information into a single, descriptive message.
JSObjectRef toJSError(JSContextRef, NSString *callingAPIName, NSString *sourceKey, NSString *underlyingErrorString);

NSString *toWebAPI(NSLocale *);

} // namespace WebKit

#endif // ENABLE(WK_WEB_EXTENSIONS)
10 changes: 10 additions & 0 deletions Source/WebKit/Shared/Extensions/WebExtensionUtilities.mm
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,16 @@ JSObjectRef toJSError(JSContextRef context, NSString *callingAPIName, NSString *
return toJSError(context, toErrorString(callingAPIName, sourceKey, underlyingErrorString));
}

NSString *toWebAPI(NSLocale *locale)
{
if (!locale.languageCode)
return nil;

if (locale.countryCode.length)
return [NSString stringWithFormat:@"%@-%@", locale.languageCode, locale.countryCode];
return locale.languageCode;
}

} // namespace WebKit

#endif // ENABLE(WK_WEB_EXTENSIONS)
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ namespace WebKit {
struct WebExtensionWindowIdentifierType;
using WebExtensionWindowIdentifier = ObjectIdentifier<WebExtensionWindowIdentifierType>;

struct WebExtensionWindowConstants {
namespace WebExtensionWindowConstants {

static constexpr double None { -1 };
static constexpr double Current { -2 };

static constexpr const WebExtensionWindowIdentifier NoneIdentifier { std::numeric_limits<uint64_t>::max() - 1 };
static constexpr const WebExtensionWindowIdentifier CurrentIdentifier { std::numeric_limits<uint64_t>::max() - 2 };
};

}

inline bool isNone(WebExtensionWindowIdentifier identifier)
{
Expand Down
4 changes: 2 additions & 2 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ - (BOOL)hasActiveUserGestureInTab:(id<_WKWebExtensionTab>)tab
return window ? window->delegate() : nil;
}

static inline NSArray *toAPI(const Vector<Ref<WebKit::WebExtensionWindow>>& windows)
static inline NSArray *toAPI(const WebKit::WebExtensionContext::WindowVector& windows)
{
if (windows.isEmpty())
return [NSArray array];
Expand All @@ -502,7 +502,7 @@ - (BOOL)hasActiveUserGestureInTab:(id<_WKWebExtensionTab>)tab
return toAPI(_webExtensionContext->focusedWindow());
}

static inline NSSet *toAPI(const HashSet<Ref<WebKit::WebExtensionTab>>& tabs)
static inline NSSet *toAPI(const WebKit::WebExtensionContext::TabMapValueIterator& tabs)
{
if (tabs.isEmpty())
return [NSSet set];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,141 @@
completionHandler(WTFMove(result), std::nullopt);
}

void WebExtensionContext::tabsReload(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, ReloadFromOrigin reloadFromOrigin, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.reload()", nil, @"tab not found"));
return;
}

if (reloadFromOrigin == ReloadFromOrigin::Yes)
tab->reloadFromOrigin(WTFMove(completionHandler));
else
tab->reload(WTFMove(completionHandler));
}

void WebExtensionContext::tabsGoBack(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.goBack()", nil, @"tab not found"));
return;
}

tab->goBack(WTFMove(completionHandler));
}

void WebExtensionContext::tabsGoForward(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.goForward()", nil, @"tab not found"));
return;
}

tab->goForward(WTFMove(completionHandler));
}

void WebExtensionContext::tabsDetectLanguage(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, CompletionHandler<void(std::optional<String>, WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(std::nullopt, toErrorString(@"tabs.detectLanguage()", nil, @"tab not found"));
return;
}

tab->detectWebpageLocale([completionHandler = WTFMove(completionHandler)](NSLocale *locale, WebExtensionTab::Error error) mutable {
if (error) {
completionHandler(std::nullopt, error);
return;
}

if (!locale) {
completionHandler(std::nullopt, std::nullopt);
return;
}

completionHandler(toWebAPI(locale), std::nullopt);
});
}

void WebExtensionContext::tabsToggleReaderMode(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.toggleReaderMode()", nil, @"tab not found"));
return;
}

tab->toggleReaderMode(WTFMove(completionHandler));
}

void WebExtensionContext::tabsGetZoom(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, CompletionHandler<void(std::optional<double>, WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(std::nullopt, toErrorString(@"tabs.getZoom()", nil, @"tab not found"));
return;
}

completionHandler(tab->zoomFactor(), std::nullopt);
}

void WebExtensionContext::tabsSetZoom(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> tabIdentifier, double zoomFactor, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tab = getTab(webPageProxyIdentifier, tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.setZoom()", nil, @"tab not found"));
return;
}

tab->setZoomFactor(zoomFactor, WTFMove(completionHandler));
}

void WebExtensionContext::tabsRemove(Vector<WebExtensionTabIdentifier> tabIdentifiers, CompletionHandler<void(WebExtensionTab::Error)>&& completionHandler)
{
auto tabs = tabIdentifiers.map([&](auto& tabIdentifier) -> RefPtr<WebExtensionTab> {
auto tab = getTab(tabIdentifier);
if (!tab) {
completionHandler(toErrorString(@"tabs.remove()", nil, @"tab '%llu' was not found", tabIdentifier.toUInt64()));
return nullptr;
}

return tab;
});

if (tabs.contains(nullptr)) {
// The completionHandler was called with an error in map() when returning nullptr.
return;
}

size_t completed = 0;
bool errorOccured = false;
auto internalCompletionHandler = WTFMove(completionHandler);

for (auto& tab : tabs) {
if (errorOccured)
break;

tab->close([&](WebExtensionTab::Error error) mutable {
if (errorOccured)
return;

if (error) {
errorOccured = true;
internalCompletionHandler(error);
return;
}

if (++completed < tabs.size())
return;

internalCompletionHandler(std::nullopt);
});
}
}

} // namespace WebKit

#endif // ENABLE(WK_WEB_EXTENSIONS)
Original file line number Diff line number Diff line change
Expand Up @@ -1165,9 +1165,18 @@ static _WKWebExtensionContextError toAPI(WebExtensionContext::Error error)
if (!isValid(identifier))
return nullptr;

// FIXME: <https://webkit.org/b/260995> Use the page identifier to get the current window based on web view.
if (isCurrent(identifier))
if (isCurrent(identifier)) {
for (auto& tab : openTabs()) {
for (WKWebView *webView in tab->webViews()) {
if (webView._page->identifier() == webPageProxyIdentifier)
return tab->window();
}
}

// FIXME: <https://webkit.org/b/260154> Use the page identifier to get the current window for popup pages.

return frontmostWindow();
}

auto* window = m_windowMap.get(identifier);
if (!window) {
Expand Down Expand Up @@ -1203,6 +1212,9 @@ static _WKWebExtensionContextError toAPI(WebExtensionContext::Error error)

RefPtr<WebExtensionTab> WebExtensionContext::getTab(WebExtensionTabIdentifier identifier)
{
if (!isValid(identifier))
return nullptr;

auto* tab = m_tabMap.get(identifier);
if (!tab) {
RELEASE_LOG_ERROR(Extensions, "Tab %{public}llu was not found", identifier.toUInt64());
Expand All @@ -1218,6 +1230,17 @@ static _WKWebExtensionContextError toAPI(WebExtensionContext::Error error)
return tab;
}

RefPtr<WebExtensionTab> WebExtensionContext::getTab(WebPageProxyIdentifier webPageProxyIdentifier, std::optional<WebExtensionTabIdentifier> identifier)
{
if (identifier)
return getTab(identifier.value());

if (auto window = getWindow(WebExtensionWindowConstants::CurrentIdentifier, webPageProxyIdentifier))
return window->activeTab();

return nullptr;
}

void WebExtensionContext::populateWindowsAndTabs()
{
ASSERT(isLoaded());
Expand Down Expand Up @@ -1248,32 +1271,19 @@ static _WKWebExtensionContextError toAPI(WebExtensionContext::Error error)
m_focusedWindowIdentifier = !m_openWindowIdentifiers.isEmpty() ? std::optional(m_openWindowIdentifiers.first()) : std::nullopt;
}

WebExtensionContext::WindowVector WebExtensionContext::openWindows()
WebExtensionContext::WindowVector WebExtensionContext::openWindows() const
{
WindowVector result;
result.reserveInitialCapacity(m_windowMap.size());
result.reserveInitialCapacity(m_openWindowIdentifiers.size());

for (auto& identifer : m_openWindowIdentifiers) {
if (auto window = getWindow(identifer))
for (auto& identifier : m_openWindowIdentifiers) {
if (auto window = m_windowMap.get(identifier))
result.uncheckedAppend(*window);
}

return result;
}

WebExtensionContext::TabSet WebExtensionContext::openTabs()
{
TabSet result;
result.reserveInitialCapacity(m_tabMap.size());

for (auto& tab : m_tabMap.values()) {
if (tab->isValid())
result.addVoid(tab);
}

return result;
}

RefPtr<WebExtensionWindow> WebExtensionContext::focusedWindow()
{
if (m_focusedWindowIdentifier)
Expand Down
Loading

0 comments on commit 4f73dc6

Please sign in to comment.