blob: 022be4391358b8d09a8f7e9d70e79deeb6488075 [file] [log] [blame]
/*
* Copyright (C) 2010-2020 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.
*/
#include "config.h"
#include "InjectedBundle.h"
#include "ActivateFonts.h"
#include "DictionaryFunctions.h"
#include "InjectedBundlePage.h"
#include "WebCoreTestSupport.h"
#include <JavaScriptCore/Options.h>
#include <WebKit/WKBundle.h>
#include <WebKit/WKBundlePage.h>
#include <WebKit/WKBundlePagePrivate.h>
#include <WebKit/WKBundlePrivate.h>
#include <WebKit/WKRetainPtr.h>
#include <WebKit/WebKit2_C.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/Vector.h>
namespace WTR {
static void handleTextDidChangeInTextField(WKBundlePageRef, WKBundleNodeHandleRef, WKBundleFrameRef, const void* context)
{
static_cast<InjectedBundle*>(const_cast<void*>(context))->textDidChangeInTextField();
}
static void handleTextFieldDidBeginEditing(WKBundlePageRef, WKBundleNodeHandleRef, WKBundleFrameRef, const void* context)
{
static_cast<InjectedBundle*>(const_cast<void*>(context))->textFieldDidBeginEditing();
}
static void handleTextFieldDidEndEditing(WKBundlePageRef, WKBundleNodeHandleRef, WKBundleFrameRef, const void* context)
{
static_cast<InjectedBundle*>(const_cast<void*>(context))->textFieldDidEndEditing();
}
InjectedBundle& InjectedBundle::singleton()
{
static InjectedBundle& shared = *new InjectedBundle;
return shared;
}
void InjectedBundle::didCreatePage(WKBundleRef bundle, WKBundlePageRef page, const void* clientInfo)
{
static_cast<InjectedBundle*>(const_cast<void*>(clientInfo))->didCreatePage(page);
}
void InjectedBundle::willDestroyPage(WKBundleRef bundle, WKBundlePageRef page, const void* clientInfo)
{
static_cast<InjectedBundle*>(const_cast<void*>(clientInfo))->willDestroyPage(page);
}
void InjectedBundle::didReceiveMessage(WKBundleRef bundle, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
{
static_cast<InjectedBundle*>(const_cast<void*>(clientInfo))->didReceiveMessage(messageName, messageBody);
}
void InjectedBundle::didReceiveMessageToPage(WKBundleRef bundle, WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
{
static_cast<InjectedBundle*>(const_cast<void*>(clientInfo))->didReceiveMessageToPage(page, messageName, messageBody);
}
void InjectedBundle::initialize(WKBundleRef bundle, WKTypeRef initializationUserData)
{
m_bundle = bundle;
WKBundleClientV1 client = {
{ 1, this },
didCreatePage,
willDestroyPage,
nullptr,
didReceiveMessage,
didReceiveMessageToPage
};
WKBundleSetClient(m_bundle.get(), &client.base);
WKBundleSetServiceWorkerProxyCreationCallback(m_bundle.get(), WebCoreTestSupport::setupNewlyCreatedServiceWorker);
platformInitialize(initializationUserData);
WebCoreTestSupport::populateJITOperations();
activateFonts();
}
void InjectedBundle::didCreatePage(WKBundlePageRef page)
{
bool isMainPage = m_pages.isEmpty();
m_pages.append(makeUnique<InjectedBundlePage>(page));
setUpInjectedBundleClients(page);
if (!isMainPage)
return;
WKTypeRef result = nullptr;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
WKBundlePostSynchronousMessage(m_bundle.get(), toWK("Initialization").get(), nullptr, &result);
ALLOW_DEPRECATED_DECLARATIONS_END
auto initializationDictionary = adoptWK(dictionaryValue(result));
if (booleanValue(initializationDictionary.get(), "ResumeTesting"))
beginTesting(initializationDictionary.get(), BegingTestingMode::Resume);
}
void InjectedBundle::willDestroyPage(WKBundlePageRef page)
{
m_pages.removeFirstMatching([page](auto& current) {
return current->page() == page;
});
}
void InjectedBundle::setUpInjectedBundleClients(WKBundlePageRef page)
{
WKBundlePageFormClientV2 formClient = {
{ 2, this },
handleTextFieldDidBeginEditing,
handleTextFieldDidEndEditing,
handleTextDidChangeInTextField,
0, // textDidChangeInTextArea
0, // shouldPerformActionInTextField
0, // willSubmitForm
0, // willSendSubmitEvent
0, // didFocusTextField
0, // shouldNotifyOnFormChanges
0, // didAssociateFormControls
};
WKBundlePageSetFormClient(page, &formClient.base);
}
InjectedBundlePage* InjectedBundle::page() const
{
// It might be better to have the UI process send over a reference to the main
// page instead of just assuming it's the first non-suspended one.
for (auto& page : m_pages) {
if (!WKBundlePageIsSuspended(page->page()))
return page.get();
}
return m_pages[0].get();
}
WKBundlePageRef InjectedBundle::pageRef() const
{
auto page = this->page();
return page ? page->page() : nullptr;
}
void InjectedBundle::didReceiveMessage(WKStringRef, WKTypeRef)
{
WKBundlePostMessage(m_bundle.get(), toWK("Error").get(), toWK("Unknown").get());
}
static void postGCTask(void* context)
{
auto page = reinterpret_cast<WKBundlePageRef>(context);
InjectedBundle::singleton().reportLiveDocuments(page);
WKRelease(page);
}
void InjectedBundle::reportLiveDocuments(WKBundlePageRef page)
{
// Release memory again, after the GC and timer fire. This is necessary to clear entries from CachedResourceLoader's m_documentResources in some scenarios.
WKBundleReleaseMemory(m_bundle.get());
const bool excludeDocumentsInPageGroup = true;
WKBundlePagePostMessage(page, toWK("LiveDocuments").get(), adoptWK(WKBundleGetLiveDocumentURLsForTesting(m_bundle.get(), excludeDocumentsInPageGroup)).get());
}
void InjectedBundle::didReceiveMessageToPage(WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody)
{
if (WKStringIsEqualToUTF8CString(messageName, "BeginTest")) {
ASSERT(messageBody);
auto messageBodyDictionary = dictionaryValue(messageBody);
WKBundlePagePostMessage(page, toWK("Ack").get(), toWK("BeginTest").get());
beginTesting(messageBodyDictionary, BegingTestingMode::New);
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "Reset")) {
ASSERT(messageBody);
auto messageBodyDictionary = dictionaryValue(messageBody);
if (auto options = stringValue(messageBodyDictionary, "JSCOptions"))
JSC::Options::setOptions(toWTFString(options).utf8().data());
if (booleanValue(messageBodyDictionary, "ShouldGC"))
WKBundleGarbageCollectJavaScriptObjects(m_bundle.get());
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
m_accessibilityIsolatedTreeMode = booleanValue(messageBodyDictionary, "AccessibilityIsolatedTree");
#endif
auto allowedHostsValue = value(messageBodyDictionary, "AllowedHosts");
if (allowedHostsValue && WKGetTypeID(allowedHostsValue) == WKArrayGetTypeID()) {
m_allowedHosts.clear();
auto array = static_cast<WKArrayRef>(allowedHostsValue);
for (size_t i = 0, size = WKArrayGetSize(array); i < size; ++i)
m_allowedHosts.append(toWTFString(WKArrayGetItemAtIndex(array, i)));
}
m_state = Idle;
m_dumpPixels = false;
m_pixelResultIsPending = false;
setlocale(LC_ALL, "");
TestRunner::removeAllWebNotificationPermissions();
InjectedBundle::page()->resetAfterTest();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "GetLiveDocuments")) {
const bool excludeDocumentsInPageGroup = false;
postPageMessage("LiveDocuments", adoptWK(WKBundleGetLiveDocumentURLsForTesting(m_bundle.get(), excludeDocumentsInPageGroup)));
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CheckForWorldLeaks")) {
WKBundleReleaseMemory(m_bundle.get());
WKRetain(page); // Balanced by the release in postGCTask.
WKBundlePageCallAfterTasksAndTimers(page, postGCTask, (void*)page);
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallAddChromeInputFieldCallback")) {
m_testRunner->callAddChromeInputFieldCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallRemoveChromeInputFieldCallback")) {
m_testRunner->callRemoveChromeInputFieldCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallSetTextInChromeInputFieldCallback")) {
m_testRunner->callSetTextInChromeInputFieldCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallSelectChromeInputFieldCallback")) {
m_testRunner->callSelectChromeInputFieldCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallGetSelectedTextInChromeInputFieldCallback")) {
ASSERT(messageBody);
ASSERT(WKGetTypeID(messageBody) == WKStringGetTypeID());
auto jsString = toJS(static_cast<WKStringRef>(messageBody));
m_testRunner->callGetSelectedTextInChromeInputFieldCallback(jsString.get());
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallFocusWebViewCallback")) {
m_testRunner->callFocusWebViewCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallSetBackingScaleFactorCallback")) {
m_testRunner->callSetBackingScaleFactorCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidBeginSwipeCallback")) {
m_testRunner->callDidBeginSwipeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallWillEndSwipeCallback")) {
m_testRunner->callWillEndSwipeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidEndSwipeCallback")) {
m_testRunner->callDidEndSwipeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidRemoveSwipeSnapshotCallback")) {
m_testRunner->callDidRemoveSwipeSnapshotCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidClearStatisticsInMemoryAndPersistentStore")) {
m_testRunner->statisticsCallClearInMemoryAndPersistentStoreCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidClearStatisticsThroughWebsiteDataRemoval")) {
m_testRunner->statisticsCallClearThroughWebsiteDataRemovalCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetShouldDowngradeReferrer")) {
m_testRunner->statisticsCallDidSetShouldDowngradeReferrerCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetShouldBlockThirdPartyCookies")) {
m_testRunner->statisticsCallDidSetShouldBlockThirdPartyCookiesCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetFirstPartyWebsiteDataRemovalMode")) {
m_testRunner->statisticsCallDidSetFirstPartyWebsiteDataRemovalModeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetToSameSiteStrictCookies")) {
m_testRunner->statisticsCallDidSetToSameSiteStrictCookiesCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetFirstPartyHostCNAMEDomain")) {
m_testRunner->statisticsCallDidSetFirstPartyHostCNAMEDomainCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetThirdPartyCNAMEDomain")) {
m_testRunner->statisticsCallDidSetThirdPartyCNAMEDomainCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidResetStatisticsToConsistentState")) {
m_testRunner->statisticsCallDidResetToConsistentStateCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetBlockCookiesForHost")) {
m_testRunner->statisticsCallDidSetBlockCookiesForHostCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetStatisticsDebugMode")) {
m_testRunner->statisticsCallDidSetDebugModeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetPrevalentResourceForDebugMode")) {
m_testRunner->statisticsCallDidSetPrevalentResourceForDebugModeCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetLastSeen")) {
m_testRunner->statisticsCallDidSetLastSeenCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidMergeStatistic")) {
m_testRunner->statisticsCallDidSetMergeStatisticCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetExpiredStatistic")) {
m_testRunner->statisticsCallDidSetExpiredStatisticCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetPrevalentResource")) {
m_testRunner->statisticsCallDidSetPrevalentResourceCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetVeryPrevalentResource")) {
m_testRunner->statisticsCallDidSetVeryPrevalentResourceCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetHasHadUserInteraction")) {
m_testRunner->statisticsCallDidSetHasHadUserInteractionCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidReceiveAllStorageAccessEntries")) {
ASSERT(messageBody);
ASSERT(WKGetTypeID(messageBody) == WKArrayGetTypeID());
WKArrayRef domainsArray = static_cast<WKArrayRef>(messageBody);
auto size = WKArrayGetSize(domainsArray);
Vector<String> domains;
domains.reserveInitialCapacity(size);
for (size_t i = 0; i < size; ++i) {
auto item = WKArrayGetItemAtIndex(domainsArray, i);
if (item && WKGetTypeID(item) == WKStringGetTypeID())
domains.append(toWTFString(static_cast<WKStringRef>(item)));
}
m_testRunner->callDidReceiveAllStorageAccessEntriesCallback(domains);
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidReceiveLoadedSubresourceDomains")) {
ASSERT(messageBody);
ASSERT(WKGetTypeID(messageBody) == WKArrayGetTypeID());
WKArrayRef domainsArray = static_cast<WKArrayRef>(messageBody);
auto size = WKArrayGetSize(domainsArray);
Vector<String> domains;
domains.reserveInitialCapacity(size);
for (size_t i = 0; i < size; ++i) {
auto item = WKArrayGetItemAtIndex(domainsArray, i);
if (item && WKGetTypeID(item) == WKStringGetTypeID())
domains.uncheckedAppend(toWTFString(static_cast<WKStringRef>(item)));
}
m_testRunner->callDidReceiveLoadedSubresourceDomainsCallback(WTFMove(domains));
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidRemoveAllSessionCredentialsCallback")) {
m_testRunner->callDidRemoveAllSessionCredentialsCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "NotifyDownloadDone")) {
if (m_testRunner->shouldFinishAfterDownload())
m_testRunner->notifyDone();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallUISideScriptCallback")) {
auto messageBodyDictionary = dictionaryValue(messageBody);
auto callbackID = uint64Value(messageBodyDictionary, "CallbackID");
auto resultString = stringValue(messageBodyDictionary, "Result");
m_testRunner->runUIScriptCallback(callbackID, toJS(resultString).get());
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "WorkQueueProcessedCallback")) {
if (!topLoadingFrame() && !m_testRunner->shouldWaitUntilDone())
InjectedBundle::page()->dump();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "WebsiteDataDeletionForRegistrableDomainsFinished")) {
m_testRunner->statisticsDidModifyDataRecordsCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "WebsiteDataScanForRegistrableDomainsFinished")) {
m_testRunner->statisticsDidScanDataRecordsCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "DidGetApplicationManifest")) {
m_testRunner->didGetApplicationManifest();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "PerformCustomMenuAction")) {
m_testRunner->performCustomMenuAction();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "CallDidSetAppBoundDomains")) {
m_testRunner->didSetAppBoundDomainsCallback();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "ForceImmediateCompletion")) {
m_testRunner->forceImmediateCompletion();
return;
}
if (WKStringIsEqualToUTF8CString(messageName, "ViewPortSnapshotTaken")) {
ASSERT(messageBody);
ASSERT(WKGetTypeID(messageBody) == WKStringGetTypeID());
m_testRunner->viewPortSnapshotTaken(static_cast<WKStringRef>(messageBody));
return;
}
postPageMessage("Error", "Unknown");
}
void InjectedBundle::beginTesting(WKDictionaryRef settings, BegingTestingMode testingMode)
{
m_state = Testing;
m_dumpPixels = booleanValue(settings, "DumpPixels");
m_timeout = Seconds::fromMilliseconds(uint64Value(settings, "Timeout"));
m_dumpJSConsoleLogInStdErr = booleanValue(settings, "DumpJSConsoleLogInStdErr");
m_pixelResult.clear();
m_repaintRects.clear();
m_testRunner = TestRunner::create();
m_gcController = GCController::create();
m_eventSendingController = EventSendingController::create();
m_textInputController = TextInputController::create();
#if HAVE(ACCESSIBILITY)
m_accessibilityController = AccessibilityController::create();
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
m_accessibilityController->setIsolatedTreeMode(m_accessibilityIsolatedTreeMode);
#endif
#endif
#if PLATFORM(IOS_FAMILY)
WKBundlePageSetUseTestingViewportConfiguration(page()->page(), !booleanValue(settings, "UseFlexibleViewport"));
#endif
#if PLATFORM(COCOA)
WebCoreTestSupport::setAdditionalSupportedImageTypesForTesting(toWTFString(stringValue(settings, "additionalSupportedImageTypes")));
#endif
m_testRunner->setUserStyleSheetEnabled(false);
m_testRunner->setAcceptsEditing(true);
m_testRunner->setTabKeyCyclesThroughElements(true);
m_testRunner->clearTestRunnerCallbacks();
if (m_timeout > 0_s)
m_testRunner->setCustomTimeout(m_timeout);
page()->prepare();
if (testingMode != BegingTestingMode::New)
return;
WKBundleClearAllDatabases(m_bundle.get());
WKBundlePageClearApplicationCache(page()->page());
WKBundleResetOriginAccessAllowLists(m_bundle.get());
WKBundleClearResourceLoadStatistics(m_bundle.get());
// [WK2] REGRESSION(r128623): It made layout tests extremely slow
// https://bugs.webkit.org/show_bug.cgi?id=96862
// WKBundleSetDatabaseQuota(m_bundle.get(), 5 * 1024 * 1024);
}
void InjectedBundle::done()
{
m_state = Stopping;
m_useWorkQueue = false;
page()->stopLoading();
setTopLoadingFrame(0);
#if HAVE(ACCESSIBILITY)
m_accessibilityController->resetToConsistentState();
#endif
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "PixelResultIsPending", m_pixelResultIsPending);
if (!m_pixelResultIsPending)
setValue(body, "PixelResult", m_pixelResult);
setValue(body, "RepaintRects", m_repaintRects);
setValue(body, "AudioResult", m_audioResult);
WKBundlePagePostMessageIgnoringFullySynchronousMode(page()->page(), toWK("Done").get(), body.get());
m_state = Idle;
}
void InjectedBundle::dumpBackForwardListsForAllPages(StringBuilder& stringBuilder)
{
size_t size = m_pages.size();
for (size_t i = 0; i < size; ++i)
stringBuilder.append(m_pages[i]->dumpHistory());
}
void InjectedBundle::dumpToStdErr(const String& output)
{
if (m_state != Testing)
return;
if (output.isEmpty())
return;
// FIXME: Do we really have to convert to UTF-8 instead of using toWK?
auto string = output.tryGetUtf8();
postPageMessage("DumpToStdErr", string ? string->data() : "Out of memory\n");
}
void InjectedBundle::outputText(const String& output)
{
if (m_state != Testing)
return;
if (output.isEmpty())
return;
// FIXME: Do we really have to convert to UTF-8 instead of using toWK?
auto string = output.tryGetUtf8();
// We use WKBundlePagePostMessageIgnoringFullySynchronousMode() instead of WKBundlePagePostMessage() to make sure that all text output
// is done via asynchronous IPC, even if the connection is in fully synchronous mode due to a WKBundlePagePostSynchronousMessageForTesting()
// call. Otherwise, messages logged via sync and async IPC may end up out of order and cause flakiness.
WKBundlePagePostMessageIgnoringFullySynchronousMode(page()->page(), toWK("TextOutput").get(), toWK(string ? string->data() : "Out of memory\n").get());
}
void InjectedBundle::postNewBeforeUnloadReturnValue(bool value)
{
postPageMessage("BeforeUnloadReturnValue", value);
}
void InjectedBundle::postAddChromeInputField()
{
postPageMessage("AddChromeInputField");
}
void InjectedBundle::postRemoveChromeInputField()
{
postPageMessage("RemoveChromeInputField");
}
void InjectedBundle::postSetTextInChromeInputField(const String& text)
{
auto wkText = toWK(text);
postPageMessage("SetTextInChromeInputField", wkText.get());
}
void InjectedBundle::postSelectChromeInputField()
{
postPageMessage("SelectChromeInputField");
}
void InjectedBundle::postGetSelectedTextInChromeInputField()
{
postPageMessage("GetSelectedTextInChromeInputField");
}
void InjectedBundle::postFocusWebView()
{
postPageMessage("FocusWebView");
}
void InjectedBundle::postSetBackingScaleFactor(double backingScaleFactor)
{
postPageMessage("SetBackingScaleFactor", adoptWK(WKDoubleCreate(backingScaleFactor)));
}
void InjectedBundle::postSetWindowIsKey(bool isKey)
{
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("SetWindowIsKey").get(), adoptWK(WKBooleanCreate(isKey)).get(), 0);
}
void InjectedBundle::postSetViewSize(double width, double height)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "width", width);
setValue(body, "height", height);
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("SetViewSize").get(), body.get(), 0);
}
void InjectedBundle::postSimulateWebNotificationClick(uint64_t notificationID)
{
postPageMessage("SimulateWebNotificationClick", adoptWK(WKUInt64Create(notificationID)));
}
void InjectedBundle::postSetAddsVisitedLinks(bool addsVisitedLinks)
{
postPageMessage("SetAddsVisitedLinks", adoptWK(WKBooleanCreate(addsVisitedLinks)));
}
void InjectedBundle::setGeolocationPermission(bool enabled)
{
postPageMessage("SetGeolocationPermission", adoptWK(WKBooleanCreate(enabled)));
}
void InjectedBundle::setMockGeolocationPosition(double latitude, double longitude, double accuracy, std::optional<double> altitude, std::optional<double> altitudeAccuracy, std::optional<double> heading, std::optional<double> speed, std::optional<double> floorLevel)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "latitude", latitude);
setValue(body, "longitude", longitude);
setValue(body, "accuracy", accuracy);
setValue(body, "altitude", altitude);
setValue(body, "altitudeAccuracy", altitudeAccuracy);
setValue(body, "heading", heading);
setValue(body, "speed", speed);
setValue(body, "floorLevel", floorLevel);
postPageMessage("SetMockGeolocationPosition", body);
}
void InjectedBundle::setMockGeolocationPositionUnavailableError(WKStringRef errorMessage)
{
postPageMessage("SetMockGeolocationPositionUnavailableError", errorMessage);
}
bool InjectedBundle::isGeolocationProviderActive() const
{
WKTypeRef result = nullptr;
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("IsGeolocationClientActive").get(), 0, &result);
return booleanValue(adoptWK(result).get());
}
unsigned InjectedBundle::imageCountInGeneralPasteboard() const
{
WKTypeRef result = nullptr;
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("ImageCountInGeneralPasteboard").get(), 0, &result);
return uint64Value(adoptWK(result).get());
}
void InjectedBundle::setUserMediaPermission(bool enabled)
{
postPageMessage("SetUserMediaPermission", adoptWK(WKBooleanCreate(enabled)));
}
void InjectedBundle::resetUserMediaPermission()
{
postPageMessage("ResetUserMediaPermission");
}
void InjectedBundle::setUserMediaPersistentPermissionForOrigin(bool permission, WKStringRef origin, WKStringRef parentOrigin)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "permission", permission);
setValue(body, "origin", origin);
setValue(body, "parentOrigin", parentOrigin);
postPageMessage("SetUserMediaPersistentPermissionForOrigin", body);
}
unsigned InjectedBundle::userMediaPermissionRequestCountForOrigin(WKStringRef origin, WKStringRef parentOrigin) const
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "origin", origin);
setValue(body, "parentOrigin", parentOrigin);
WKTypeRef result = nullptr;
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("UserMediaPermissionRequestCountForOrigin").get(), body.get(), &result);
return uint64Value(adoptWK(result).get());
}
void InjectedBundle::resetUserMediaPermissionRequestCountForOrigin(WKStringRef origin, WKStringRef parentOrigin)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "origin", origin);
setValue(body, "parentOrigin", parentOrigin);
postPageMessage("ResetUserMediaPermissionRequestCountForOrigin", body);
}
void InjectedBundle::setCustomPolicyDelegate(bool enabled, bool permissive)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "enabled", enabled);
setValue(body, "permissive", permissive);
postPageMessage("SetCustomPolicyDelegate", body);
}
void InjectedBundle::setHidden(bool hidden)
{
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "hidden", hidden);
postPageMessage("SetHidden", body);
}
void InjectedBundle::setCacheModel(int model)
{
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("SetCacheModel").get(), adoptWK(WKUInt64Create(model)).get(), nullptr);
}
bool InjectedBundle::shouldProcessWorkQueue() const
{
if (!m_useWorkQueue)
return false;
WKTypeRef result = nullptr;
WKBundlePagePostSynchronousMessageForTesting(page()->page(), toWK("IsWorkQueueEmpty").get(), 0, &result);
// The IPC failed. This happens when swapping processes on navigation because the WebPageProxy unregisters itself
// as a MessageReceiver from the old WebProcessProxy and register itself with the new WebProcessProxy instead.
if (!result)
return false;
auto isEmpty = booleanValue(adoptWK(result).get());
return !isEmpty;
}
void InjectedBundle::processWorkQueue()
{
postPageMessage("ProcessWorkQueue");
}
void InjectedBundle::queueBackNavigation(unsigned howFarBackward)
{
m_useWorkQueue = true;
postPageMessage("QueueBackNavigation", adoptWK(WKUInt64Create(howFarBackward)));
}
void InjectedBundle::queueForwardNavigation(unsigned howFarForward)
{
m_useWorkQueue = true;
postPageMessage("QueueForwardNavigation", adoptWK(WKUInt64Create(howFarForward)));
}
void InjectedBundle::queueLoad(WKStringRef url, WKStringRef target, bool shouldOpenExternalURLs)
{
m_useWorkQueue = true;
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "url", url);
setValue(body, "target", target);
setValue(body, "shouldOpenExternalURLs", shouldOpenExternalURLs);
postPageMessage("QueueLoad", body);
}
void InjectedBundle::queueLoadHTMLString(WKStringRef content, WKStringRef baseURL, WKStringRef unreachableURL)
{
m_useWorkQueue = true;
auto body = adoptWK(WKMutableDictionaryCreate());
setValue(body, "content", content);
if (baseURL)
setValue(body, "baseURL", baseURL);
if (unreachableURL)
setValue(body, "unreachableURL", unreachableURL);
postPageMessage("QueueLoadHTMLString", body);
}
void InjectedBundle::queueReload()
{
m_useWorkQueue = true;
postPageMessage("QueueReload");
}
void InjectedBundle::queueLoadingScript(WKStringRef script)
{
m_useWorkQueue = true;
postPageMessage("QueueLoadingScript", script);
}
void InjectedBundle::queueNonLoadingScript(WKStringRef script)
{
m_useWorkQueue = true;
postPageMessage("QueueNonLoadingScript", script);
}
bool InjectedBundle::isAllowedHost(WKStringRef host)
{
if (m_allowedHosts.isEmpty())
return false;
return m_allowedHosts.contains(toWTFString(host));
}
void InjectedBundle::setAllowsAnySSLCertificate(bool allowsAnySSLCertificate)
{
WebCoreTestSupport::setAllowsAnySSLCertificate(allowsAnySSLCertificate);
}
bool InjectedBundle::statisticsNotifyObserver()
{
return WKBundleResourceLoadStatisticsNotifyObserver(m_bundle.get());
}
void InjectedBundle::textDidChangeInTextField()
{
m_testRunner->textDidChangeInTextFieldCallback();
}
void InjectedBundle::textFieldDidBeginEditing()
{
m_testRunner->textFieldDidBeginEditingCallback();
}
void InjectedBundle::textFieldDidEndEditing()
{
m_testRunner->textFieldDidEndEditingCallback();
}
void postMessage(const char* name)
{
postMessage(name, WKRetainPtr<WKTypeRef> { });
}
void postMessage(const char* name, bool value)
{
postMessage(name, adoptWK(WKBooleanCreate(value)));
}
void postMessage(const char* name, unsigned value)
{
postMessage(name, adoptWK(WKUInt64Create(value)));
}
void postMessage(const char* name, JSStringRef value)
{
postMessage(name, toWK(value));
}
void postSynchronousMessage(const char* name)
{
postSynchronousMessage(name, WKRetainPtr<WKTypeRef> { });
}
void postSynchronousMessage(const char* name, bool value)
{
postSynchronousMessage(name, adoptWK(WKBooleanCreate(value)));
}
void postSynchronousMessage(const char* name, uint64_t value)
{
postSynchronousMessage(name, adoptWK(WKUInt64Create(value)));
}
void postSynchronousMessage(const char* name, unsigned value)
{
uint64_t promotedValue = value;
postSynchronousMessage(name, promotedValue);
}
void postSynchronousMessage(const char* name, double value)
{
postSynchronousMessage(name, adoptWK(WKDoubleCreate(value)));
}
void postPageMessage(const char* name)
{
postPageMessage(name, WKRetainPtr<WKTypeRef> { });
}
void postPageMessage(const char* name, bool value)
{
postPageMessage(name, adoptWK(WKBooleanCreate(value)));
}
void postPageMessage(const char* name, const char* value)
{
postPageMessage(name, toWK(value));
}
void postPageMessage(const char* name, WKStringRef value)
{
if (auto page = InjectedBundle::singleton().pageRef())
WKBundlePagePostMessage(page, toWK(name).get(), value);
}
void postSynchronousPageMessage(const char* name)
{
postSynchronousPageMessage(name, WKRetainPtr<WKTypeRef> { });
}
void postSynchronousPageMessage(const char* name, bool value)
{
postSynchronousPageMessage(name, adoptWK(WKBooleanCreate(value)));
}
} // namespace WTR