Skip to content

Commit

Permalink
Some APIs like tabs.query need to prompt for permission before return…
Browse files Browse the repository at this point in the history
…ing.

https://webkit.org/b/262714
rdar://problem/116535817

Reviewed by Jeff Miller.

Change the existing permission delegate calls from being just used for permissions.request()
and into helper methods on WebExtensionContext. Now filter out already granted permissions,
and only send the delegate messages if some of them are still required.

Also add the timeout for these requests, so they auto expire in 2 minutes, preventing the
extensions scripts from waiting for a response for too long.

Also add expirationDate parameter to the completionHandler, so the app can grant for a limited
amount of time, or pass nil to grant indefinitely.

Finally, wrap many extension APIs with the new requestPermissionToAccessURLs() and request the
URLs for the tabs and cookies needed by those APIs. This includes some like scripting, which
were never wrapped like this in Safari's implementation.

Deleted some redundant toImpl / toAPI helpers that we already had elsewhere.

* Source/WebCore/page/UserContentURLPattern.cpp:
(WebCore::UserContentURLPattern::UserContentURLPattern):
* Source/WebKit/Platform/cocoa/CocoaHelpers.h:
* Source/WebKit/Platform/cocoa/CocoaHelpers.mm:
(WebKit::toAPI):
(WebKit::toAPIArray):
(WebKit::toImpl):
(WebKit::toImplSet): Deleted.
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtensionControllerDelegate.h:
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPICookiesCocoa.mm:
(WebKit::WebExtensionContext::cookiesGet):
(WebKit::WebExtensionContext::cookiesGetAll):
(WebKit::WebExtensionContext::cookiesSet):
(WebKit::WebExtensionContext::cookiesRemove):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIDeclarativeNetRequestCocoa.mm:
(WebKit::WebExtensionContext::declarativeNetRequestGetMatchedRules):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIDevToolsInspectedWindow.mm:
(WebKit::WebExtensionContext::devToolsInspectedWindowEval):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIPermissionsCocoa.mm:
(WebKit::WebExtensionContext::permissionsRequest):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIScriptingCocoa.mm:
(WebKit::WebExtensionContext::scriptingExecuteScript):
(WebKit::WebExtensionContext::scriptingInsertCSS):
(WebKit::WebExtensionContext::scriptingRemoveCSS):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm:
(WebKit::WebExtensionContext::tabsGet):
(WebKit::WebExtensionContext::tabsGetCurrent):
(WebKit::WebExtensionContext::tabsQuery):
(WebKit::WebExtensionContext::tabsDetectLanguage):
(WebKit::WebExtensionContext::tabsCaptureVisibleTab):
(WebKit::WebExtensionContext::tabsRemove):
(WebKit::WebExtensionContext::tabsExecuteScript):
(WebKit::WebExtensionContext::tabsInsertCSS):
(WebKit::WebExtensionContext::tabsRemoveCSS):
* Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIWindowsCocoa.mm:
(WebKit::WebExtensionContext::windowsGet):
(WebKit::WebExtensionContext::windowsGetLastFocused):
(WebKit::WebExtensionContext::windowsGetAll):
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm:
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:
(WebKit::WebExtensionContext::grantPermissions):
(WebKit::WebExtensionContext::denyPermissions):
(WebKit::WebExtensionContext::grantPermissionMatchPatterns):
(WebKit::WebExtensionContext::denyPermissionMatchPatterns):
(WebKit::WebExtensionContext::requestPermissionMatchPatterns):
(WebKit::WebExtensionContext::requestPermissionToAccessURLs):
(WebKit::WebExtensionContext::requestPermissions):
(WebKit::WebExtensionContext::hasPermission):
(WebKit::WebExtensionContext::permissionState):
(WebKit::WebExtensionContext::setPermissionState):
(WebKit::WebExtensionContext::getCurrentTab const):
(WebKit::WebExtensionContext::unloadBackgroundContentIfPossible):
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionMatchPatternCocoa.mm:
(WebKit::WebExtensionMatchPattern::getOrCreate):
(WebKit::toPatterns):
(WebKit::toAPI):
* Source/WebKit/UIProcess/Extensions/WebExtension.h:
* Source/WebKit/UIProcess/Extensions/WebExtensionContext.h:
(WebKit::WebExtensionContext::hasPermission):
(WebKit::WebExtensionContext::permissionState):
* Source/WebKit/UIProcess/Extensions/WebExtensionMatchPattern.h:
(WebKit::WebExtensionMatchPattern::getOrCreate):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIDevTools.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPIPermissions.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionAPITabs.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtensionMatchPattern.mm:
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/cocoa/TestWebExtensionsDelegate.h:
* Tools/TestWebKitAPI/cocoa/TestWebExtensionsDelegate.mm:
(-[TestWebExtensionsDelegate webExtensionController:promptForPermissions:inTab:forExtensionContext:completionHandler:]):
(-[TestWebExtensionsDelegate webExtensionController:promptForPermissionMatchPatterns:inTab:forExtensionContext:completionHandler:]):
(-[TestWebExtensionsDelegate webExtensionController:promptForPermissionToAccessURLs:inTab:forExtensionContext:completionHandler:]):

Canonical link: https://commits.webkit.org/275950@main
  • Loading branch information
xeenon committed Mar 12, 2024
1 parent 33c8b3c commit 33e22ac
Show file tree
Hide file tree
Showing 24 changed files with 758 additions and 323 deletions.
4 changes: 3 additions & 1 deletion Source/WebCore/page/UserContentURLPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ UserContentURLPattern::UserContentURLPattern(StringView scheme, StringView host,
return;
}

bool isFileScheme = equalLettersIgnoringASCIICase(m_scheme, "file"_s);

m_host = host.toString();
if (m_host.isEmpty()) {
if (!isFileScheme && m_host.isEmpty()) {
m_error = Error::MissingHost;
return;
}
Expand Down
10 changes: 6 additions & 4 deletions Source/WebKit/Platform/cocoa/CocoaHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import <wtf/HashSet.h>
#import <wtf/OptionSet.h>
#import <wtf/RetainPtr.h>
#import <wtf/URLHash.h>
#import <wtf/UUID.h>
#import <wtf/WallTime.h>
#import <wtf/text/StringHash.h>
Expand Down Expand Up @@ -123,9 +124,10 @@ void callAfterRandomDelay(Function<void()>&&);
NSDate *toAPI(const WallTime&);
WallTime toImpl(NSDate *);

NSSet *toAPI(HashSet<String>&);
NSArray *toAPIArray(HashSet<String>&);
Vector<String> toImpl(NSArray *);
HashSet<String> toImplSet(NSArray *);
NSSet *toAPI(const HashSet<URL>&);

NSSet *toAPI(const HashSet<String>&);
NSArray *toAPIArray(const HashSet<String>&);
HashSet<String> toImpl(NSSet *);

} // namespace WebKit
29 changes: 15 additions & 14 deletions Source/WebKit/Platform/cocoa/CocoaHelpers.mm
Original file line number Diff line number Diff line change
Expand Up @@ -462,38 +462,39 @@ WallTime toImpl(NSDate *date)
return WallTime::fromRawSeconds(date.timeIntervalSince1970);
}

NSSet *toAPI(HashSet<String>& set)
NSSet *toAPI(const HashSet<URL>& set)
{
NSMutableSet *result = [[NSMutableSet alloc] initWithCapacity:set.size()];
for (auto& element : set)
[result addObject:static_cast<NSString *>(element)];

[result addObject:static_cast<NSURL *>(element)];
return [result copy];
}

NSArray *toAPIArray(HashSet<String>& set)
NSSet *toAPI(const HashSet<String>& set)
{
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:set.size()];
NSMutableSet *result = [[NSMutableSet alloc] initWithCapacity:set.size()];
for (auto& element : set)
[result addObject:static_cast<NSString *>(element)];

return [result copy];
}

Vector<String> toImpl(NSArray *array)
NSArray *toAPIArray(const HashSet<String>& set)
{
return Vector<String>(array.count, [array](size_t i) {
return (NSString *)array[i];
});
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:set.size()];
for (auto& element : set)
[result addObject:static_cast<NSString *>(element)];
return [result copy];
}

HashSet<String> toImplSet(NSArray *array)
HashSet<String> toImpl(NSSet *set)
{
HashSet<String> result;
result.reserveInitialCapacity(array.count);
result.reserveInitialCapacity(set.count);

for (NSString *element in array)
result.addVoid(element);
for (id element in set) {
if (auto *string = dynamic_objc_cast<NSString>(element))
result.addVoid(string);
}

return result;
}
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#import "config.h"
#import "_WKWebExtensionInternal.h"

#import "CocoaHelpers.h"
#import "CocoaImage.h"
#import "WebExtension.h"
#import "_WKWebExtensionMatchPatternInternal.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,40 +113,43 @@ WK_API_AVAILABLE(macos(13.3), ios(16.4))
@abstract Called when an extension context requests permissions.
@param controller The web extension controller that is managing the extension.
@param permissions The set of permissions being requested by the extension.
@param tab The tab in which the extension is running, or \c nil if the request are not specific to a tab.
@param tab The tab in which the extension is running, or \c nil if the request is not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed permissions.
@param completionHandler A block to be called with the set of allowed permissions and an optional expiration date.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of permissions that were granted. If not implemented or the completion handler is not called within a reasonable amount of time, the
request is assumed to have been denied.
set of permissions that were granted and an optional expiration date. If not implemented or the completion handler is not called within a reasonable
amount of time, the request is assumed to have been denied. The expiration date can be used to specify when the permissions expire. If `nil`,
permissions are assumed to not expire.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissions:(NSSet<_WKWebExtensionPermission> *)permissions inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionPermission> *allowedPermissions))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissions:in:for:completionHandler:));
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissions:(NSSet<_WKWebExtensionPermission> *)permissions inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionPermission> *allowedPermissions, NSDate * _Nullable expirationDate))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissions:in:for:completionHandler:));

/*!
@abstract Called when an extension context requests access to a set of URLs.
@param controller The web extension controller that is managing the extension.
@param urls The set of URLs that the extension is requesting access to.
@param tab The tab in which the extension is running, or \c nil if the request is not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed URLs.
@param completionHandler A block to be called with the set of allowed URLs and an optional expiration date.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of URLs that were granted access to. If not implemented or the completion handler is not called within a reasonable amount of time, the
request is assumed to have been denied.
set of URLs that were granted access to and an optional expiration date. If not implemented or the completion handler is not called within a
reasonable amount of time, the request is assumed to have been denied. The expiration date can be used to specify when the URLs expire.
If `nil`, URLs are assumed to not expire.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionToAccessURLs:(NSSet<NSURL *> *)urls inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<NSURL *> *allowedURLs))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionToAccess:in:for:completionHandler:));
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionToAccessURLs:(NSSet<NSURL *> *)urls inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<NSURL *> *allowedURLs, NSDate * _Nullable expirationDate))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionToAccess:in:for:completionHandler:));

/*!
@abstract Called when an extension context requests access to a set of match patterns.
@param controller The web extension controller that is managing the extension.
@param matchPatterns The set of match patterns that the extension is requesting access to.
@param tab The tab in which the extension is running, or \c nil if the request is not specific to a tab.
@param extensionContext The context in which the web extension is running.
@param completionHandler A block to be called with the set of allowed match patterns.
@param completionHandler A block to be called with the set of allowed match patterns and an optional expiration date.
@discussion This method should be implemented by the app to prompt the user for permission and call the completion handler with the
set of match patterns that were granted access to. If not implemented or the completion handler is not called within a reasonable amount of time,
the request is assumed to have been denied.
set of match patterns that were granted access to and an optional expiration date. If not implemented or the completion handler is not called
within a reasonable amount of time, the request is assumed to have been denied. The expiration date can be used to specify when the match
patterns expire. If `nil`, match patterns are assumed to not expire.
*/
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionMatchPatterns:(NSSet<_WKWebExtensionMatchPattern *> *)matchPatterns inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionMatchPattern *> *allowedMatchPatterns))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionMatchPatterns:in:for:completionHandler:));
- (void)webExtensionController:(_WKWebExtensionController *)controller promptForPermissionMatchPatterns:(NSSet<_WKWebExtensionMatchPattern *> *)matchPatterns inTab:(nullable id <_WKWebExtensionTab>)tab forExtensionContext:(_WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSSet<_WKWebExtensionMatchPattern *> *allowedMatchPatterns, NSDate * _Nullable expirationDate))completionHandler NS_SWIFT_NAME(webExtensionController(_:promptForPermissionMatchPatterns:in:for:completionHandler:));

/*!
@abstract Called when a popup is requested to be displayed for a specific action.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,22 +123,24 @@ static inline URL toURL(const WebCore::Cookie& cookie)
WebExtensionCookieFilterParameters filterParameters;
filterParameters.name = name;

fetchCookies(*dataStore, url, filterParameters, [completionHandler = WTFMove(completionHandler), dataStore, name = name.isolatedCopy()](Expected<Vector<WebExtensionCookieParameters>, WebExtensionError>&& result) mutable {
if (!result) {
completionHandler(makeUnexpected(result.error()));
return;
}
requestPermissionToAccessURLs({ url }, nullptr, [this, protectedThis = Ref { *this }, dataStore, name, url, filterParameters = WTFMove(filterParameters), completionHandler = WTFMove(completionHandler)](auto&& requestedURLs, auto&& allowedURLs, auto expirationDate) mutable {
fetchCookies(*dataStore, url, filterParameters, [completionHandler = WTFMove(completionHandler), dataStore, name](Expected<Vector<WebExtensionCookieParameters>, WebExtensionError>&& result) mutable {
if (!result) {
completionHandler(makeUnexpected(result.error()));
return;
}

auto& cookies = result.value();
if (cookies.isEmpty()) {
completionHandler({ });
return;
}

ASSERT(cookies.size() == 1);
auto& cookieParameters = cookies[0];

auto& cookies = result.value();
if (cookies.isEmpty()) {
completionHandler({ });
return;
}

ASSERT(cookies.size() == 1);
auto& cookieParameters = cookies[0];

completionHandler({ WTFMove(cookieParameters) });
completionHandler({ WTFMove(cookieParameters) });
});
});
}

Expand All @@ -150,7 +152,9 @@ static inline URL toURL(const WebCore::Cookie& cookie)
return;
}

fetchCookies(*dataStore, url, filterParameters, WTFMove(completionHandler));
requestPermissionToAccessURLs({ url }, nullptr, [this, protectedThis = Ref { *this }, dataStore, url, filterParameters, completionHandler = WTFMove(completionHandler)](auto&& requestedURLs, auto&& allowedURLs, auto expirationDate) mutable {
fetchCookies(*dataStore, url, filterParameters, WTFMove(completionHandler));
});
}

void WebExtensionContext::cookiesSet(std::optional<PAL::SessionID> sessionID, const WebExtensionCookieParameters& cookieParameters, CompletionHandler<void(Expected<std::optional<WebExtensionCookieParameters>, WebExtensionError>&&)>&& completionHandler)
Expand All @@ -161,13 +165,17 @@ static inline URL toURL(const WebCore::Cookie& cookie)
return;
}

if (!hasPermission(toURL(cookieParameters.cookie))) {
completionHandler(toWebExtensionError(@"cookies.set()", nil, @"host permissions are missing or not granted"));
return;
}
auto url = toURL(cookieParameters.cookie);

requestPermissionToAccessURLs({ url }, nullptr, [this, protectedThis = Ref { *this }, dataStore, url, cookieParameters, completionHandler = WTFMove(completionHandler)](auto&& requestedURLs, auto&& allowedURLs, auto expirationDate) mutable {
if (!hasPermission(url)) {
completionHandler(toWebExtensionError(@"cookies.set()", nil, @"host permissions are missing or not granted"));
return;
}

dataStore->cookieStore().setCookies({ cookieParameters.cookie }, [completionHandler = WTFMove(completionHandler), sessionID = dataStore->sessionID().isolatedCopy(), cookie = cookieParameters.cookie.isolatedCopy()]() mutable {
completionHandler({ WebExtensionCookieParameters { WTFMove(sessionID), WTFMove(cookie) } });
dataStore->cookieStore().setCookies({ cookieParameters.cookie }, [completionHandler = WTFMove(completionHandler), sessionID = dataStore->sessionID(), cookie = cookieParameters.cookie]() mutable {
completionHandler({ WebExtensionCookieParameters { WTFMove(sessionID), WTFMove(cookie) } });
});
});
}

Expand All @@ -179,31 +187,33 @@ static inline URL toURL(const WebCore::Cookie& cookie)
return;
}

if (!hasPermission(url)) {
completionHandler(toWebExtensionError(@"cookies.remove()", nil, @"host permissions are missing or not granted"));
return;
}

WebExtensionCookieFilterParameters filterParameters;
filterParameters.name = name;

fetchCookies(*dataStore, url, filterParameters, [completionHandler = WTFMove(completionHandler), dataStore](Expected<Vector<WebExtensionCookieParameters>, WebExtensionError>&& result) mutable {
if (!result) {
completionHandler(makeUnexpected(result.error()));
requestPermissionToAccessURLs({ url }, nullptr, [this, protectedThis = Ref { *this }, dataStore, name, url, completionHandler = WTFMove(completionHandler)](auto&& requestedURLs, auto&& allowedURLs, auto expirationDate) mutable {
if (!hasPermission(url)) {
completionHandler(toWebExtensionError(@"cookies.remove()", nil, @"host permissions are missing or not granted"));
return;
}

auto& cookies = result.value();
if (cookies.isEmpty()) {
completionHandler({ });
return;
}
WebExtensionCookieFilterParameters filterParameters;
filterParameters.name = name;

ASSERT(cookies.size() == 1);
auto& cookieParameters = cookies[0];
fetchCookies(*dataStore, url, filterParameters, [completionHandler = WTFMove(completionHandler), dataStore](Expected<Vector<WebExtensionCookieParameters>, WebExtensionError>&& result) mutable {
if (!result) {
completionHandler(makeUnexpected(result.error()));
return;
}

dataStore->cookieStore().deleteCookie(cookieParameters.cookie, [completionHandler = WTFMove(completionHandler), cookieParameters]() mutable {
completionHandler({ WTFMove(cookieParameters) });
auto& cookies = result.value();
if (cookies.isEmpty()) {
completionHandler({ });
return;
}

ASSERT(cookies.size() == 1);
auto& cookieParameters = cookies[0];

dataStore->cookieStore().deleteCookie(cookieParameters.cookie, [completionHandler = WTFMove(completionHandler), cookieParameters]() mutable {
completionHandler({ WTFMove(cookieParameters) });
});
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,22 +253,27 @@

WallTime minTime = minTimeStamp ? minTimeStamp.value() : WallTime::nan();

DeclarativeNetRequestMatchedRuleVector filteredMatchedRules;
for (auto matchedRule : matchedRules()) {
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=262714 - we should be requesting permission if we don't have access to this URL.
if (!hasPermission(matchedRule.url))
continue;
DeclarativeNetRequestMatchedRuleVector filteredRules;

URLVector allURLs;
for (auto& matchedRule : matchedRules()) {
if (tabIdentifier && matchedRule.tabIdentifier != tabIdentifier)
continue;

if (minTime != WallTime::nan() && matchedRule.timeStamp <= minTime)
continue;

filteredMatchedRules.append(matchedRule);
filteredRules.append(matchedRule);
allURLs.append(matchedRule.url);
}

completionHandler(WTFMove(filteredMatchedRules));
requestPermissionToAccessURLs(allURLs, nullptr, [this, protectedThis = Ref { *this }, filteredRules = WTFMove(filteredRules), completionHandler = WTFMove(completionHandler)](auto&& requestedURLs, auto&& allowedURLs, auto expirationDate) mutable {
auto result = WTF::compactMap(filteredRules, [&](auto& matchedRule) -> std::optional<WebExtensionMatchedRuleParameters> {
return hasPermission(matchedRule.url) ? std::optional(matchedRule) : std::nullopt;
});

completionHandler(WTFMove(result));
});
}

_WKWebExtensionDeclarativeNetRequestSQLiteStore *WebExtensionContext::declarativeNetRequestDynamicRulesStore()
Expand Down
Loading

0 comments on commit 33e22ac

Please sign in to comment.