Skip to content

Commit

Permalink
User agent JavaScript from full web browsers should clear transient a…
Browse files Browse the repository at this point in the history
…ctivation.

rdar://104748908
https://bugs.webkit.org/show_bug.cgi?id=251276

Reviewed by Brent Fulgham.

Take the solution from https://commits.webkit.org/265168@main and extend it to full web browsers now.

* Source/WebCore/bindings/js/RunJavaScriptParameters.h:
(WebCore::RunJavaScriptParameters::RunJavaScriptParameters):
(WebCore::RunJavaScriptParameters::encode const):
(WebCore::RunJavaScriptParameters::decode):

* Source/WebCore/bindings/js/ScriptController.cpp:
(WebCore::ScriptController::executeScriptInWorldIgnoringException):
(WebCore::ScriptController::executeScriptInWorld):
(WebCore::ScriptController::executeUserAgentScriptInWorld):

* Source/WebKit/Shared/Cocoa/DefaultWebBrowserChecks.h:
* Source/WebKit/Shared/Cocoa/DefaultWebBrowserChecks.mm:
(WebKit::shouldEvaluateJavaScriptWithoutTransientActivation):

* Source/WebKit/UIProcess/API/C/WKPage.cpp:
(WKPageRunJavaScriptInMainFrame):

* Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _evaluateJavaScript:asAsyncFunction:withSourceURL:withArguments:forceUserGesture:inFrame:inWorld:completionHandler:]):

Originally-landed-as: 259548.854@safari-7615-branch (465b681). rdar://113286760
Canonical link: https://commits.webkit.org/266613@main
  • Loading branch information
beidson authored and JonWBedard committed Aug 5, 2023
1 parent 473600f commit dbfde83
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 25 deletions.
19 changes: 14 additions & 5 deletions Source/WebCore/bindings/js/RunJavaScriptParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,38 @@ namespace WebCore {

enum class RunAsAsyncFunction : bool { No, Yes };
enum class ForceUserGesture : bool { No, Yes };
enum class RemoveTransientActivation : bool { No, Yes };

using ArgumentWireBytesMap = HashMap<String, Vector<uint8_t>>;

struct RunJavaScriptParameters {
RunJavaScriptParameters(String&& source, URL&& sourceURL, RunAsAsyncFunction runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, ForceUserGesture forceUserGesture)
RunJavaScriptParameters(String&& source, URL&& sourceURL, RunAsAsyncFunction runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, ForceUserGesture forceUserGesture, RemoveTransientActivation removeTransientActivation)
: source(WTFMove(source))
, sourceURL(WTFMove(sourceURL))
, runAsAsyncFunction(runAsAsyncFunction)
, arguments(WTFMove(arguments))
, forceUserGesture(forceUserGesture)
, removeTransientActivation(removeTransientActivation)
{
}

RunJavaScriptParameters(const String& source, URL&& sourceURL, bool runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture)
RunJavaScriptParameters(const String& source, URL&& sourceURL, bool runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture, RemoveTransientActivation removeTransientActivation)
: source(source)
, sourceURL(WTFMove(sourceURL))
, runAsAsyncFunction(runAsAsyncFunction ? RunAsAsyncFunction::Yes : RunAsAsyncFunction::No)
, arguments(WTFMove(arguments))
, forceUserGesture(forceUserGesture ? ForceUserGesture::Yes : ForceUserGesture::No)
, removeTransientActivation(removeTransientActivation)
{
}

RunJavaScriptParameters(String&& source, URL&& sourceURL, bool runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture)
RunJavaScriptParameters(String&& source, URL&& sourceURL, bool runAsAsyncFunction, std::optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture, RemoveTransientActivation removeTransientActivation)
: source(WTFMove(source))
, sourceURL(WTFMove(sourceURL))
, runAsAsyncFunction(runAsAsyncFunction ? RunAsAsyncFunction::Yes : RunAsAsyncFunction::No)
, arguments(WTFMove(arguments))
, forceUserGesture(forceUserGesture ? ForceUserGesture::Yes : ForceUserGesture::No)
, removeTransientActivation(removeTransientActivation)
{
}

Expand All @@ -70,10 +74,11 @@ struct RunJavaScriptParameters {
RunAsAsyncFunction runAsAsyncFunction;
std::optional<ArgumentWireBytesMap> arguments;
ForceUserGesture forceUserGesture;
RemoveTransientActivation removeTransientActivation;

template<typename Encoder> void encode(Encoder& encoder) const
{
encoder << source << sourceURL << runAsAsyncFunction << arguments << forceUserGesture;
encoder << source << sourceURL << runAsAsyncFunction << arguments << forceUserGesture << removeTransientActivation;
}

template<typename Decoder> static std::optional<RunJavaScriptParameters> decode(Decoder& decoder)
Expand All @@ -98,7 +103,11 @@ struct RunJavaScriptParameters {
if (!decoder.decode(forceUserGesture))
return std::nullopt;

return { RunJavaScriptParameters { WTFMove(source), WTFMove(sourceURL), runAsAsyncFunction, WTFMove(arguments), forceUserGesture } };
RemoveTransientActivation removeTransientActivation;
if (!decoder.decode(removeTransientActivation))
return std::nullopt;

return { RunJavaScriptParameters { WTFMove(source), WTFMove(sourceURL), runAsAsyncFunction, WTFMove(arguments), forceUserGesture, removeTransientActivation } };
}
};

Expand Down
21 changes: 8 additions & 13 deletions Source/WebCore/bindings/js/ScriptController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ JSC::JSValue ScriptController::executeScriptIgnoringException(const String& scri

JSC::JSValue ScriptController::executeScriptInWorldIgnoringException(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
{
auto result = executeScriptInWorld(world, { script, URL { }, false, std::nullopt, forceUserGesture });
auto result = executeScriptInWorld(world, { script, URL { }, false, std::nullopt, forceUserGesture, RemoveTransientActivation::No });
return result ? result.value() : JSC::JSValue { };
}

Expand All @@ -594,19 +594,14 @@ ValueOrException ScriptController::executeScriptInWorld(DOMWrapperWorld& world,

UserGestureIndicator gestureIndicator(parameters.forceUserGesture == ForceUserGesture::Yes ? std::optional<ProcessingUserGestureState>(ProcessingUserGesture) : std::nullopt, m_frame.document());

#if PLATFORM(COCOA)
if (linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::EvaluateJavaScriptWithoutTransientActivation)) {
// Script executed by the user agent under simulated user gesture should not leave behind transient activation
if (parameters.forceUserGesture == ForceUserGesture::Yes && UserGestureIndicator::currentUserGesture()) {
UserGestureIndicator::currentUserGesture()->addDestructionObserver([](UserGestureToken& token) {
token.forEachImpactedDocument([](Document& document) {
if (auto* window = document.domWindow())
window->consumeLastActivationIfNecessary();
});
if (parameters.forceUserGesture == ForceUserGesture::Yes && UserGestureIndicator::currentUserGesture() && parameters.removeTransientActivation == RemoveTransientActivation::Yes) {
UserGestureIndicator::currentUserGesture()->addDestructionObserver([](UserGestureToken& token) {
token.forEachImpactedDocument([](Document& document) {
if (auto* window = document.domWindow())
window->consumeTransientActivation();
});
}
});
}
#endif

if (!canExecuteScripts(ReasonForCallingCanExecuteScripts::AboutToExecuteScript) || isPaused())
return makeUnexpected(ExceptionDetails { "Cannot execute JavaScript in this document"_s });
Expand Down Expand Up @@ -719,7 +714,7 @@ JSC::JSValue ScriptController::executeUserAgentScriptInWorldIgnoringException(DO
}
ValueOrException ScriptController::executeUserAgentScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
{
return executeScriptInWorld(world, { script, URL { }, false, std::nullopt, forceUserGesture });
return executeScriptInWorld(world, { script, URL { }, false, std::nullopt, forceUserGesture, RemoveTransientActivation::No });
}

void ScriptController::executeAsynchronousUserAgentScriptInWorld(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters, ResolveFunction&& resolveCompletionHandler)
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/Shared/Cocoa/DefaultWebBrowserChecks.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ bool isRunningTest(const String& bundleID);
void determineTrackingPreventionState();
bool doesAppHaveTrackingPreventionEnabled();
bool doesParentProcessHaveTrackingPreventionEnabled(AuxiliaryProcess&, bool hasRequestedCrossWebsiteTrackingPermission);
bool shouldEvaluateJavaScriptWithoutTransientActivation();
bool isFullWebBrowserOrRunningTest();
bool isParentProcessAFullWebBrowser(AuxiliaryProcess&);

Expand Down
12 changes: 12 additions & 0 deletions Source/WebKit/Shared/Cocoa/DefaultWebBrowserChecks.mm
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,18 @@ bool isFullWebBrowserOrRunningTest(const String& bundleIdentifier)
return fullWebBrowser || isRunningTest(bundleIdentifier);
}

bool shouldEvaluateJavaScriptWithoutTransientActivation()
{
static bool staticShouldEvaluateJavaScriptWithoutTransientActivation = [] {
if (linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::EvaluateJavaScriptWithoutTransientActivation))
return true;

return isFullWebBrowserOrRunningTest();
}();

return staticShouldEvaluateJavaScriptWithoutTransientActivation;
}

bool isFullWebBrowserOrRunningTest()
{
ASSERT(!isInWebKitChildProcess());
Expand Down
9 changes: 8 additions & 1 deletion Source/WebKit/UIProcess/API/C/WKPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
#endif

#if PLATFORM(COCOA)
#include "DefaultWebBrowserChecks.h"
#include <wtf/cocoa/RuntimeApplicationChecksCocoa.h>
#endif

Expand Down Expand Up @@ -2593,7 +2594,13 @@ void WKPageSetPageStateClient(WKPageRef pageRef, WKPageStateClientBase* client)
void WKPageRunJavaScriptInMainFrame(WKPageRef pageRef, WKStringRef scriptRef, void* context, WKPageRunJavaScriptFunction callback)
{
CRASH_IF_SUSPENDED;
toImpl(pageRef)->runJavaScriptInMainFrame({ toImpl(scriptRef)->string(), URL { }, false, std::nullopt, true }, [context, callback] (auto&& result) {
#if PLATFORM(COCOA)
auto removeTransientActivation = shouldEvaluateJavaScriptWithoutTransientActivation() ? RemoveTransientActivation::Yes : RemoveTransientActivation::No;
#else
auto removeTransientActivation = RemoveTransientActivation::No;
#endif

toImpl(pageRef)->runJavaScriptInMainFrame({ toImpl(scriptRef)->string(), URL { }, false, std::nullopt, true, removeTransientActivation }, [context, callback] (auto&& result) {
if (result.has_value())
callback(toAPI(result.value().get()), nullptr, context);
else
Expand Down
4 changes: 3 additions & 1 deletion Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#import "CocoaImage.h"
#import "CompletionHandlerCallChecker.h"
#import "ContentAsStringIncludesChildFrames.h"
#import "DefaultWebBrowserChecks.h"
#import "DiagnosticLoggingClient.h"
#import "FindClient.h"
#import "FullscreenClient.h"
Expand Down Expand Up @@ -1178,7 +1179,8 @@ - (void)_evaluateJavaScript:(NSString *)javaScriptString asAsyncFunction:(BOOL)a
frameID = frame._handle->_frameHandle->frameID();
}

_page->runJavaScriptInFrameInScriptWorld({ javaScriptString, sourceURL, !!asAsyncFunction, WTFMove(argumentsMap), !!forceUserGesture }, frameID, *world->_contentWorld.get(), [handler] (auto&& result) {
auto removeTransientActivation = WebKit::shouldEvaluateJavaScriptWithoutTransientActivation() ? WebCore::RemoveTransientActivation::Yes : WebCore::RemoveTransientActivation::No;
_page->runJavaScriptInFrameInScriptWorld({ javaScriptString, sourceURL, !!asAsyncFunction, WTFMove(argumentsMap), !!forceUserGesture, removeTransientActivation }, frameID, *world->_contentWorld.get(), [handler] (auto&& result) {
if (!handler)
return;

Expand Down
8 changes: 4 additions & 4 deletions Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4029,7 +4029,7 @@ void webkitWebViewRunJavascriptWithoutForcedUserGestures(WebKitWebView* webView,
g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
g_return_if_fail(script);

RunJavaScriptParameters params = { String::fromUTF8(script), URL { }, RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::No };
RunJavaScriptParameters params = { String::fromUTF8(script), URL { }, RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::No, RemoveTransientActivation::No };
webkitWebViewRunJavaScriptWithParams(webView, WTFMove(params), nullptr, RunJavascriptReturnType::JSCValue, adoptGRef(g_task_new(webView, cancellable, callback, userData)));
}

Expand All @@ -4038,7 +4038,7 @@ static void webkitWebViewEvaluateJavascriptInternal(WebKitWebView* webView, cons
g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
g_return_if_fail(script);

RunJavaScriptParameters params = { String::fromUTF8(script, length < 0 ? strlen(script) : length), URL({ }, String::fromUTF8(sourceURI)), RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::Yes };
RunJavaScriptParameters params = { String::fromUTF8(script, length < 0 ? strlen(script) : length), URL({ }, String::fromUTF8(sourceURI)), RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::Yes, RemoveTransientActivation::No };
webkitWebViewRunJavaScriptWithParams(webView, WTFMove(params), worldName, returnType, adoptGRef(g_task_new(webView, cancellable, callback, userData)));
}

Expand Down Expand Up @@ -4174,7 +4174,7 @@ static void webkitWebViewCallAsyncJavascriptFunctionInternal(WebKitWebView* webV
return;
}

RunJavaScriptParameters params = { String::fromUTF8(body, length < 0 ? strlen(body) : length), URL({ }, String::fromUTF8(sourceURI)), RunAsAsyncFunction::Yes, WTFMove(argumentsMap), ForceUserGesture::Yes };
RunJavaScriptParameters params = { String::fromUTF8(body, length < 0 ? strlen(body) : length), URL({ }, String::fromUTF8(sourceURI)), RunAsAsyncFunction::Yes, WTFMove(argumentsMap), ForceUserGesture::Yes, RemoveTransientActivation::No };
webkitWebViewRunJavaScriptWithParams(webView, WTFMove(params), worldName, returnType, adoptGRef(g_task_new(webView, cancellable, callback, userData)));
}

Expand Down Expand Up @@ -4504,7 +4504,7 @@ static void resourcesStreamReadCallback(GObject* object, GAsyncResult* result, g

WebKitWebView* webView = WEBKIT_WEB_VIEW(g_task_get_source_object(task.get()));
gpointer outputStreamData = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(object));
RunJavaScriptParameters params = { String::fromUTF8(reinterpret_cast<const gchar*>(outputStreamData)), URL { }, RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::Yes };
RunJavaScriptParameters params = { String::fromUTF8(reinterpret_cast<const gchar*>(outputStreamData)), URL { }, RunAsAsyncFunction::No, std::nullopt, ForceUserGesture::Yes, RemoveTransientActivation::No };
webkitWebViewRunJavaScriptWithParams(webView, WTFMove(params), nullptr, RunJavascriptReturnType::WebKitJavascriptResult, WTFMove(task));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ void RemoteInspectorProtocolHandler::inspect(const String& hostAndPort, Connecti

void RemoteInspectorProtocolHandler::runScript(const String& script)
{
m_page.runJavaScriptInMainFrame({ script, URL { }, false, std::nullopt, false },
m_page.runJavaScriptInMainFrame({ script, URL { }, false, std::nullopt, false, RemoveTransientActivation::No },
[] (auto&& result) {
if (!result.has_value())
LOG_ERROR("Exception running script \"%s\"", result.error().message.utf8().data());
Expand Down

0 comments on commit dbfde83

Please sign in to comment.