Diff
Modified: trunk/Source/WebCore/ChangeLog (235407 => 235408)
--- trunk/Source/WebCore/ChangeLog 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebCore/ChangeLog 2018-08-27 23:31:15 UTC (rev 235408)
@@ -1,3 +1,14 @@
+2018-08-27 Simon Fraser <simon.fra...@apple.com>
+
+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
+ https://bugs.webkit.org/show_bug.cgi?id=188994
+
+ Reviewed by Tim Horton.
+
+ Export Document::postTask() for use by WTR's injected bundle.
+
+ * dom/Document.h:
+
2018-08-27 Aditya Keerthi <akeer...@apple.com>
Consolidate ENABLE_INPUT_TYPE_COLOR and ENABLE_INPUT_TYPE_COLOR_POPOVER
Modified: trunk/Source/WebCore/dom/Document.h (235407 => 235408)
--- trunk/Source/WebCore/dom/Document.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebCore/dom/Document.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -1030,7 +1030,7 @@
bool isDNSPrefetchEnabled() const { return m_isDNSPrefetchEnabled; }
void parseDNSPrefetchControlHeader(const String&);
- void postTask(Task&&) final; // Executes the task on context's thread asynchronously.
+ WEBCORE_EXPORT void postTask(Task&&) final; // Executes the task on context's thread asynchronously.
ScriptedAnimationController* scriptedAnimationController() { return m_scriptedAnimationController.get(); }
void suspendScriptedAnimationControllerCallbacks();
Modified: trunk/Source/WebKit/ChangeLog (235407 => 235408)
--- trunk/Source/WebKit/ChangeLog 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/ChangeLog 2018-08-27 23:31:15 UTC (rev 235408)
@@ -1,3 +1,46 @@
+2018-08-27 Simon Fraser <simon.fra...@apple.com>
+
+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
+ https://bugs.webkit.org/show_bug.cgi?id=188994
+
+ Reviewed by Tim Horton.
+
+ This patch adds the notion of a "control command" in the protocol between webkitpy and
+ WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
+ that is checked for before trying to parse the input as test URL. For now, just one
+ commmand is supported, which is "#CHECK FOR WORLD LEAKS".
+
+ In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
+ with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.
+
+ DumpRenderTree stubs out the command, returning an empty block.
+
+ WebKitTestRunner responds to the command by dumping the list of live documents, if it was
+ run with the --check-for-world-leaks option.
+
+ When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
+ WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
+ that leaked a document), and keeps them in a map of document identifier to test and live document URL.
+ Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
+ clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
+ after which it requests the list of live documents for a final time, excluding any that are loaded
+ in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
+ list are therefore leaked (or abandoned).
+
+ Future patches will hook up webkitpy reporting for leaked documents.
+
+ * WebProcess/InjectedBundle/API/c/WKBundle.cpp:
+ (WKBundleGetLiveDocumentURLs):
+ (WKBundleClearPageCache):
+ (WKBundleClearMemoryCache):
+ * WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:
+ (WKBundlePagePostTask):
+ * WebProcess/InjectedBundle/API/c/WKBundlePage.h:
+ * WebProcess/InjectedBundle/API/c/WKBundlePrivate.h:
+ * WebProcess/InjectedBundle/InjectedBundle.cpp:
+ (WebKit::InjectedBundle::liveDocumentURLs):
+ * WebProcess/InjectedBundle/InjectedBundle.h:
+
2018-08-27 Alex Christensen <achristen...@webkit.org>
Fix plug-ins after r235398
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -34,11 +34,18 @@
#include "WKAPICast.h"
#include "WKBundleAPICast.h"
#include "WKBundlePrivate.h"
+#include "WKMutableArray.h"
+#include "WKMutableDictionary.h"
+#include "WKNumber.h"
+#include "WKRetainPtr.h"
+#include "WKString.h"
#include "WebConnection.h"
#include "WebFrame.h"
#include "WebPage.h"
#include "WebPageGroupProxy.h"
#include <WebCore/DatabaseTracker.h>
+#include <WebCore/MemoryCache.h>
+#include <WebCore/PageCache.h>
#include <WebCore/ResourceLoadObserver.h>
#include <WebCore/ServiceWorkerThreadProxy.h>
@@ -213,6 +220,30 @@
toImpl(bundleRef)->setAsynchronousSpellCheckingEnabled(toImpl(pageGroupRef), enabled);
}
+WKArrayRef WKBundleGetLiveDocumentURLs(WKBundleRef bundleRef, WKBundlePageGroupRef pageGroupRef, bool excludeDocumentsInPageGroupPages)
+{
+ auto liveDocuments = toImpl(bundleRef)->liveDocumentURLs(toImpl(pageGroupRef), excludeDocumentsInPageGroupPages);
+
+ auto liveURLs = adoptWK(WKMutableArrayCreate());
+
+ for (const auto& it : liveDocuments) {
+ auto urlInfo = adoptWK(WKMutableDictionaryCreate());
+
+ auto documentIDKey = adoptWK(WKStringCreateWithUTF8CString("id"));
+ auto documentURLKey = adoptWK(WKStringCreateWithUTF8CString("url"));
+
+ auto documentIDValue = adoptWK(WKUInt64Create(it.key));
+ auto documentURLValue = adoptWK(toCopiedAPI(it.value));
+
+ WKDictionarySetItem(urlInfo.get(), documentIDKey.get(), documentIDValue.get());
+ WKDictionarySetItem(urlInfo.get(), documentURLKey.get(), documentURLValue.get());
+
+ WKArrayAppendItem(liveURLs.get(), urlInfo.get());
+ }
+
+ return liveURLs.leakRef();
+}
+
void WKBundleReportException(JSContextRef context, JSValueRef exception)
{
InjectedBundle::reportException(context, exception);
@@ -229,6 +260,16 @@
DatabaseTracker::singleton().setQuota(*SecurityOriginData::fromDatabaseIdentifier("file__0"), quota);
}
+void WKBundleClearPageCache(WKBundleRef bundle)
+{
+ PageCache::singleton().pruneToSizeNow(0, PruningReason::MemoryPressure);
+}
+
+void WKBundleClearMemoryCache(WKBundleRef bundle)
+{
+ MemoryCache::singleton().evictResources();
+}
+
WKDataRef WKBundleCreateWKDataFromUInt8Array(WKBundleRef bundle, JSContextRef context, JSValueRef data)
{
return toAPI(&toImpl(bundle)->createWebDataFromUint8Array(context, data).leakRef());
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -64,6 +64,7 @@
#include <WebCore/PageOverlay.h>
#include <WebCore/PageOverlayController.h>
#include <WebCore/RenderLayerCompositor.h>
+#include <WebCore/ScriptExecutionContext.h>
#include <WebCore/SecurityOriginData.h>
#include <WebCore/URL.h>
#include <WebCore/WheelEventTestTrigger.h>
@@ -619,6 +620,25 @@
});
}
+void WKBundlePagePostTask(WKBundlePageRef pageRef, WKBundlePageTestNotificationCallback callback, void* context)
+{
+ if (!callback)
+ return;
+
+ WebKit::WebPage* webPage = toImpl(pageRef);
+ WebCore::Page* page = webPage ? webPage->corePage() : nullptr;
+ if (!page)
+ return;
+
+ WebCore::Document* document = page->mainFrame().document();
+ if (!document)
+ return;
+
+ document->postTask([=] (WebCore::ScriptExecutionContext&) {
+ callback(context);
+ });
+}
+
void WKBundlePagePostMessage(WKBundlePageRef pageRef, WKStringRef messageNameRef, WKTypeRef messageBodyRef)
{
toImpl(pageRef)->postMessage(toWTFString(messageNameRef), toImpl(messageBodyRef));
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -117,6 +117,9 @@
typedef void (*WKBundlePageTestNotificationCallback)(void* context);
WK_EXPORT void WKBundlePageRegisterScrollOperationCompletionCallback(WKBundlePageRef, WKBundlePageTestNotificationCallback, void* context);
+// Posts a task in the ScriptExecutionContext of the main frame. Used to do work after other tasks have completed.
+WK_EXPORT void WKBundlePagePostTask(WKBundlePageRef, WKBundlePageTestNotificationCallback, void* context);
+
WK_EXPORT void WKBundlePagePostMessage(WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody);
// Switches a connection into a fully synchronous mode, so all messages become synchronous until we get a response.
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -68,6 +68,8 @@
WK_EXPORT uint64_t WKBundleGetWebNotificationID(WKBundleRef bundle, JSContextRef context, JSValueRef notification);
WK_EXPORT WKDataRef WKBundleCreateWKDataFromUInt8Array(WKBundleRef bundle, JSContextRef context, JSValueRef data);
WK_EXPORT void WKBundleSetAsynchronousSpellCheckingEnabled(WKBundleRef bundleRef, WKBundlePageGroupRef pageGroupRef, bool enabled);
+// Returns array of dictionaries. Dictionary keys are document identifiers, values are document URLs.
+WK_EXPORT WKArrayRef WKBundleGetLiveDocumentURLs(WKBundleRef bundle, WKBundlePageGroupRef pageGroup, bool excludeDocumentsInPageGroupPages);
// UserContent API
WK_EXPORT void WKBundleAddUserScript(WKBundleRef bundle, WKBundlePageGroupRef pageGroup, WKBundleScriptWorldRef scriptWorld, WKStringRef source, WKURLRef url, WKArrayRef whitelist, WKArrayRef blacklist, _WKUserScriptInjectionTime injectionTime, WKUserContentInjectedFrames injectedFrames);
@@ -96,6 +98,9 @@
WK_EXPORT void WKBundleExtendClassesForParameterCoder(WKBundleRef bundle, WKArrayRef classes);
+WK_EXPORT void WKBundleClearPageCache(WKBundleRef bundle);
+WK_EXPORT void WKBundleClearMemoryCache(WKBundleRef bundle);
+
#ifdef __cplusplus
}
#endif
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -55,6 +55,7 @@
#include <WebCore/ApplicationCache.h>
#include <WebCore/ApplicationCacheStorage.h>
#include <WebCore/CommonVM.h>
+#include <WebCore/Document.h>
#include <WebCore/Frame.h>
#include <WebCore/FrameLoader.h>
#include <WebCore/FrameView.h>
@@ -604,6 +605,26 @@
return API::Data::create(static_cast<unsigned char*>(arrayData->baseAddress()), arrayData->byteLength());
}
+InjectedBundle::DocumentIDToURLMap InjectedBundle::liveDocumentURLs(WebPageGroupProxy* pageGroup, bool excludeDocumentsInPageGroupPages)
+{
+ DocumentIDToURLMap result;
+
+ for (const auto* document : Document::allDocuments())
+ result.add(document->identifier().toUInt64(), document->url().string());
+
+ if (excludeDocumentsInPageGroupPages) {
+ for (const auto* page : PageGroup::pageGroup(pageGroup->identifier())->pages()) {
+ for (const auto* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
+ if (!frame->document())
+ continue;
+ result.remove(frame->document()->identifier().toUInt64());
+ }
+ }
+ }
+
+ return result;
+}
+
void InjectedBundle::setTabKeyCyclesThroughElements(WebPage* page, bool enabled)
{
page->corePage()->setTabKeyCyclesThroughElements(enabled);
Modified: trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h (235407 => 235408)
--- trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -122,6 +122,9 @@
void removeAllWebNotificationPermissions(WebPage*);
uint64_t webNotificationID(JSContextRef, JSValueRef);
Ref<API::Data> createWebDataFromUint8Array(JSContextRef, JSValueRef);
+
+ typedef HashMap<uint64_t, String> DocumentIDToURLMap;
+ DocumentIDToURLMap liveDocumentURLs(WebPageGroupProxy*, bool excludeDocumentsInPageGroupPages);
// UserContent API
void addUserScript(WebPageGroupProxy*, InjectedBundleScriptWorld*, String&& source, String&& url, API::Array* whitelist, API::Array* blacklist, WebCore::UserScriptInjectionTime, WebCore::UserContentInjectedFrames);
Modified: trunk/Tools/ChangeLog (235407 => 235408)
--- trunk/Tools/ChangeLog 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/ChangeLog 2018-08-27 23:31:15 UTC (rev 235408)
@@ -1,3 +1,76 @@
+2018-08-27 Simon Fraser <simon.fra...@apple.com>
+
+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
+ https://bugs.webkit.org/show_bug.cgi?id=188994
+
+ Reviewed by Tim Horton.
+
+ This patch adds the notion of a "control command" in the protocol between webkitpy and
+ WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
+ that is checked for before trying to parse the input as test URL. For now, just one
+ commmand is supported, which is "#CHECK FOR WORLD LEAKS".
+
+ In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
+ with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.
+
+ DumpRenderTree stubs out the command, returning an empty block.
+
+ WebKitTestRunner responds to the command by dumping the list of live documents, if it was
+ run with the --check-for-world-leaks option.
+
+ When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
+ WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
+ that leaked a document), and keeps them in a map of document identifier to test and live document URL.
+ Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
+ clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
+ after which it requests the list of live documents for a final time, excluding any that are loaded
+ in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
+ list are therefore leaked (or abandoned).
+
+ Future patches will hook up webkitpy reporting for leaked documents.
+
+ * DumpRenderTree/mac/DumpRenderTree.mm:
+ (initializeGlobalsFromCommandLineOptions):
+ (handleControlCommand):
+ (runTestingServerLoop):
+ * DumpRenderTree/win/DumpRenderTree.cpp:
+ (handleControlCommand):
+ (main):
+ * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
+ (WTR::postGCTask):
+ (WTR::InjectedBundle::reportLiveDocuments):
+ (WTR::InjectedBundle::didReceiveMessageToPage):
+ * WebKitTestRunner/InjectedBundle/InjectedBundle.h:
+ * WebKitTestRunner/Options.cpp:
+ (WTR::handleOptionCheckForWorldLeaks):
+ (WTR::OptionsHandler::OptionsHandler):
+ * WebKitTestRunner/Options.h:
+ * WebKitTestRunner/TestController.cpp:
+ (WTR::AsyncTask::run):
+ (WTR::AsyncTask::currentTask):
+ (WTR::TestController::initialize):
+ (WTR::TestController::ensureViewSupportsOptionsForTest):
+ (WTR::TestController::resetStateToConsistentValues):
+ (WTR::TestController::updateLiveDocumentsAfterTest):
+ (WTR::TestController::checkForWorldLeaks):
+ (WTR::TestController::findAndDumpWorldLeaks):
+ (WTR::TestController::willDestroyWebView):
+ (WTR::parseInputLine):
+ (WTR::TestController::waitForCompletion):
+ (WTR::TestController::handleControlCommand):
+ (WTR::TestController::runTestingServerLoop):
+ (WTR::TestController::run):
+ (WTR::TestController::didReceiveLiveDocumentsList):
+ (WTR::TestController::didReceiveMessageFromInjectedBundle):
+ * WebKitTestRunner/TestController.h:
+ (WTR::AsyncTask::AsyncTask):
+ (WTR::AsyncTask::taskComplete):
+ (WTR::TestController::AbandonedDocumentInfo::AbandonedDocumentInfo):
+ * WebKitTestRunner/TestInvocation.cpp:
+ (WTR::TestInvocation::invoke):
+ * WebKitTestRunner/TestOptions.h:
+ (WTR::TestOptions::hasSameInitializationOptions const):
+
2018-08-27 Alex Christensen <achristen...@webkit.org>
Fix API test after r235398
Modified: trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm (235407 => 235408)
--- trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm 2018-08-27 23:31:15 UTC (rev 235408)
@@ -223,6 +223,7 @@
static int allowAnyHTTPSCertificateForAllowedHosts;
static int showWebView;
static int printTestCount;
+static int checkForWorldLeaks;
static BOOL printSeparators;
static RetainPtr<CFStringRef> persistentUserStyleSheetLocation;
static std::set<std::string> allowedHosts;
@@ -1120,6 +1121,7 @@
{"allow-any-certificate-for-allowed-hosts", no_argument, &allowAnyHTTPSCertificateForAllowedHosts, YES},
{"show-webview", no_argument, &showWebView, YES},
{"print-test-count", no_argument, &printTestCount, YES},
+ {"check-for-world-leaks", no_argument, &checkForWorldLeaks, NO},
{nullptr, 0, nullptr, 0}
};
@@ -1152,6 +1154,23 @@
return (argc == optind+1 && strcmp(argv[optind], "-") == 0);
}
+static bool handleControlCommand(const char* command)
+{
+ if (!strcmp("#CHECK FOR WORLD LEAKS", command)) {
+ // DumpRenderTree does not support checking for world leaks.
+ WTF::String result("\n");
+ printf("Content-Type: text/plain\n");
+ printf("Content-Length: %u\n", result.length());
+ fwrite(result.utf8().data(), 1, result.length(), stdout);
+ printf("#EOF\n");
+ fprintf(stderr, "#EOF\n");
+ fflush(stdout);
+ fflush(stderr);
+ return true;
+ }
+ return false;
+}
+
static void runTestingServerLoop()
{
// When DumpRenderTree run in server mode, we just wait around for file names
@@ -1166,6 +1185,9 @@
if (strlen(filenameBuffer) == 0)
continue;
+ if (handleControlCommand(filenameBuffer))
+ continue;
+
runTest(filenameBuffer);
if (printTestCount) {
Modified: trunk/Tools/DumpRenderTree/win/DumpRenderTree.cpp (235407 => 235408)
--- trunk/Tools/DumpRenderTree/win/DumpRenderTree.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/DumpRenderTree/win/DumpRenderTree.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -1112,6 +1112,22 @@
::setPersistentUserStyleSheetLocation(nullptr);
}
+static bool handleControlCommand(const char* command)
+{
+ if (!strcmp("#CHECK FOR ABANDONED DOCUMENTS", command)) {
+ // DumpRenderTree does not support checking for abandonded documents.
+ String result("\n");
+ printf("Content-Type: text/plain\n");
+ printf("Content-Length: %u\n", result.length());
+ fwrite(result.utf8().data(), 1, result.length(), stdout);
+ printf("#EOF\n");
+ fprintf(stderr, "#EOF\n");
+ fflush(stdout);
+ fflush(stderr);
+ return true;
+ }
+ return false;
+}
static void runTest(const string& inputLine)
{
@@ -1616,6 +1632,9 @@
if (strlen(filenameBuffer) == 0)
continue;
+ if (handleControlCommand(filenameBuffer))
+ continue;
+
runTest(filenameBuffer);
}
} else {
Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -180,6 +180,21 @@
WKBundlePostMessage(m_bundle, errorMessageName.get(), errorMessageBody.get());
}
+static void postGCTask(void* context)
+{
+ WKBundlePageRef page = reinterpret_cast<WKBundlePageRef>(context);
+ InjectedBundle::singleton().reportLiveDocuments(page);
+ WKRelease(page);
+}
+
+void InjectedBundle::reportLiveDocuments(WKBundlePageRef page)
+{
+ const bool excludeDocumentsInPageGroup = true;
+ auto documentURLs = adoptWK(WKBundleGetLiveDocumentURLs(m_bundle, m_pageGroup, excludeDocumentsInPageGroup));
+ auto ackMessageName = adoptWK(WKStringCreateWithUTF8CString("LiveDocuments"));
+ WKBundlePagePostMessage(page, ackMessageName.get(), documentURLs.get());
+}
+
void InjectedBundle::didReceiveMessageToPage(WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody)
{
if (WKStringIsEqualToUTF8CString(messageName, "BeginTest")) {
@@ -244,10 +259,27 @@
TestRunner::removeAllWebNotificationPermissions();
InjectedBundle::page()->resetAfterTest();
+ return;
+ }
+ if (WKStringIsEqualToUTF8CString(messageName, "GetLiveDocuments")) {
+ const bool excludeDocumentsInPageGroup = false;
+ auto documentURLs = adoptWK(WKBundleGetLiveDocumentURLs(m_bundle, m_pageGroup, excludeDocumentsInPageGroup));
+ auto ackMessageName = adoptWK(WKStringCreateWithUTF8CString("LiveDocuments"));
+ WKBundlePagePostMessage(page, ackMessageName.get(), documentURLs.get());
return;
}
+ if (WKStringIsEqualToUTF8CString(messageName, "CheckForWorldLeaks")) {
+ WKBundleClearPageCache(m_bundle);
+ WKBundleClearMemoryCache(m_bundle);
+ WKBundleGarbageCollectJavaScriptObjects(m_bundle);
+
+ WKRetain(page); // Balanced by the release in postGCTask.
+ WKBundlePagePostTask(page, postGCTask, (void*)page);
+ return;
+ }
+
if (WKStringIsEqualToUTF8CString(messageName, "CallAddChromeInputFieldCallback")) {
m_testRunner->callAddChromeInputFieldCallback();
return;
Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -140,6 +140,8 @@
void textFieldDidBeginEditing();
void textFieldDidEndEditing();
+ void reportLiveDocuments(WKBundlePageRef);
+
void resetUserScriptInjectedCount() { m_userScriptInjectedCount = 0; }
void increaseUserScriptInjectedCount() { ++m_userScriptInjectedCount; }
size_t userScriptInjectedCount() const { return m_userScriptInjectedCount; }
Modified: trunk/Tools/WebKitTestRunner/Options.cpp (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/Options.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/Options.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -93,6 +93,12 @@
return true;
}
+static bool handleOptionCheckForWorldLeaks(Options& options, const char*, const char*)
+{
+ options.checkForWorldLeaks = true;
+ return true;
+}
+
static bool handleOptionAllowAnyHTTPSCertificateForAllowedHosts(Options& options, const char*, const char*)
{
options.allowAnyHTTPSCertificateForAllowedHosts = true;
@@ -129,6 +135,7 @@
optionList.append(Option("--allow-any-certificate-for-allowed-hosts", "Allows any HTTPS certificate for an allowed host.", handleOptionAllowAnyHTTPSCertificateForAllowedHosts));
optionList.append(Option("--show-webview", "Show the WebView during test runs (for debugging)", handleOptionShowWebView));
optionList.append(Option("--show-touches", "Show the touches during test runs (for debugging)", handleOptionShowTouches));
+ optionList.append(Option("--check-for-world-leaks", "Check for leaks of world objects (currently, documents)", handleOptionCheckForWorldLeaks));
optionList.append(Option(0, 0, handleOptionUnmatched));
}
Modified: trunk/Tools/WebKitTestRunner/Options.h (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/Options.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/Options.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -48,6 +48,7 @@
bool shouldUseRemoteLayerTree { false };
bool shouldShowWebView { false };
bool shouldShowTouches { false };
+ bool checkForWorldLeaks { false };
bool allowAnyHTTPSCertificateForAllowedHosts { false };
std::vector<std::string> paths;
std::set<std::string> allowedHosts;
Modified: trunk/Tools/WebKitTestRunner/TestController.cpp (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/TestController.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/TestController.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -115,6 +115,22 @@
return WKStringCreateWithUTF8CString("MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue%2BPtwBRE6XfV%0AWtKQbsshxk5ZhcUwcwyvcnIq9b82QhJdoACdD34rqfCAIND46fXKQUnb0mvKzQID%0AAQABFhFNb3ppbGxhSXNNeUZyaWVuZDANBgkqhkiG9w0BAQQFAANBAAKv2Eex2n%2FS%0Ar%2F7iJNroWlSzSMtTiQTEB%2BADWHGj9u1xrUrOilq%2Fo2cuQxIfZcNZkYAkWP4DubqW%0Ai0%2F%2FrgBvmco%3D");
}
+AsyncTask* AsyncTask::m_currentTask;
+
+bool AsyncTask::run()
+{
+ m_currentTask = this;
+ m_task();
+ TestController::singleton().runUntil(m_taskDone, m_timeout);
+ m_currentTask = nullptr;
+ return m_taskDone;
+}
+
+AsyncTask* AsyncTask::currentTask()
+{
+ return m_currentTask;
+}
+
static TestController* controller;
TestController& TestController::singleton()
@@ -384,6 +400,7 @@
m_allowedHosts = options.allowedHosts;
m_shouldShowWebView = options.shouldShowWebView;
m_shouldShowTouches = options.shouldShowTouches;
+ m_checkForWorldLeaks = options.checkForWorldLeaks;
m_allowAnyHTTPSCertificateForAllowedHosts = options.allowAnyHTTPSCertificateForAllowedHosts;
if (options.printSupportedFeatures) {
@@ -660,6 +677,8 @@
if (m_mainWebView->viewSupportsOptions(options))
return;
+ willDestroyWebView();
+
WKPageSetPageUIClient(m_mainWebView->page(), nullptr);
WKPageSetPageNavigationClient(m_mainWebView->page(), nullptr);
WKPageClose(m_mainWebView->page());
@@ -669,7 +688,7 @@
createWebViewWithOptions(options);
- if (!resetStateToConsistentValues(options))
+ if (!resetStateToConsistentValues(options, ResetStage::BeforeTest))
TestInvocation::dumpWebProcessUnresponsiveness("<unknown> - TestController::run - Failed to reset state to consistent values\n");
}
@@ -783,7 +802,7 @@
platformResetPreferencesToConsistentValues();
}
-bool TestController::resetStateToConsistentValues(const TestOptions& options)
+bool TestController::resetStateToConsistentValues(const TestOptions& options, ResetStage resetStage)
{
SetForScope<State> changeState(m_state, Resetting);
m_beforeUnloadReturnValue = true;
@@ -911,9 +930,70 @@
m_doneResetting = false;
WKPageLoadURL(m_mainWebView->page(), blankURL());
runUntil(m_doneResetting, m_currentInvocation->shortTimeout());
+ if (!m_doneResetting)
+ return false;
+
+ if (resetStage == ResetStage::AfterTest && m_checkForWorldLeaks)
+ updateLiveDocumentsAfterTest();
+
return m_doneResetting;
}
+void TestController::updateLiveDocumentsAfterTest()
+{
+ AsyncTask([]() {
+ // After each test, we update the list of live documents so that we can detect when an abandoned document first showed up.
+ WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("GetLiveDocuments"));
+ WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), nullptr);
+ }, 5_s).run();
+}
+
+void TestController::checkForWorldLeaks()
+{
+ AsyncTask([]() {
+ // This runs at the end of a series of tests. It clears caches, runs a GC and then fetches the list of documents.
+ WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("CheckForWorldLeaks"));
+ WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), nullptr);
+ }, 20_s).run();
+}
+
+void TestController::findAndDumpWorldLeaks()
+{
+ checkForWorldLeaks();
+
+ StringBuilder builder;
+
+ if (m_abandonedDocumentInfo.size()) {
+ for (const auto& it : m_abandonedDocumentInfo) {
+ auto documentURL = it.value.abandonedDocumentURL;
+ if (documentURL.isEmpty())
+ documentURL = "(no url)";
+ builder.append("TEST: ");
+ builder.append(it.value.testURL);
+ builder.append('\n');
+ builder.append("ABANDONED DOCUMENT: ");
+ builder.append(documentURL);
+ builder.append('\n');
+ }
+ } else
+ builder.append("no abandoned documents");
+
+ String result = builder.toString();
+ printf("Content-Type: text/plain\n");
+ printf("Content-Length: %u\n", result.length());
+ fwrite(result.utf8().data(), 1, result.length(), stdout);
+ printf("#EOF\n");
+ fprintf(stderr, "#EOF\n");
+ fflush(stdout);
+ fflush(stderr);
+}
+
+void TestController::willDestroyWebView()
+{
+ // Before we kill the web view, look for abandoned documents before that web process goes away.
+ checkForWorldLeaks();
+}
+
void TestController::terminateWebContentProcess()
{
WKPageTerminate(m_mainWebView->page());
@@ -1238,7 +1318,7 @@
exit(1);
}
-TestCommand parseInputLine(const std::string& inputLine)
+static TestCommand parseInputLine(const std::string& inputLine)
{
TestCommand result;
CommandTokenizer tokenizer(inputLine);
@@ -1296,6 +1376,23 @@
return true;
}
+bool TestController::waitForCompletion(const WTF::Function<void ()>& function, WTF::Seconds timeout)
+{
+ m_doneResetting = false;
+ function();
+ runUntil(m_doneResetting, timeout);
+ return !m_doneResetting;
+}
+
+bool TestController::handleControlCommand(const char* command)
+{
+ if (!strcmp("#CHECK FOR WORLD LEAKS", command)) {
+ findAndDumpWorldLeaks();
+ return true;
+ }
+ return false;
+}
+
void TestController::runTestingServerLoop()
{
char filenameBuffer[2048];
@@ -1307,6 +1404,9 @@
if (strlen(filenameBuffer) == 0)
continue;
+ if (handleControlCommand(filenameBuffer))
+ continue;
+
if (!runTest(filenameBuffer))
break;
}
@@ -1321,6 +1421,7 @@
if (!runTest(m_paths[i].c_str()))
break;
}
+ findAndDumpWorldLeaks();
}
}
@@ -1385,8 +1486,51 @@
m_eventSenderProxy->keyDown(key, modifiers, location);
}
+void TestController::didReceiveLiveDocumentsList(WKArrayRef liveDocumentList)
+{
+ auto numDocuments = WKArrayGetSize(liveDocumentList);
+
+ HashMap<uint64_t, String> documentInfo;
+ for (size_t i = 0; i < numDocuments; ++i) {
+ WKTypeRef item = WKArrayGetItemAtIndex(liveDocumentList, i);
+ if (item && WKGetTypeID(item) == WKDictionaryGetTypeID()) {
+ WKDictionaryRef liveDocumentItem = static_cast<WKDictionaryRef>(item);
+
+ WKRetainPtr<WKStringRef> idKey(AdoptWK, WKStringCreateWithUTF8CString("id"));
+ WKUInt64Ref documentID = static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(liveDocumentItem, idKey.get()));
+
+ WKRetainPtr<WKStringRef> urlKey(AdoptWK, WKStringCreateWithUTF8CString("url"));
+ WKStringRef documentURL = static_cast<WKStringRef>(WKDictionaryGetItemForKey(liveDocumentItem, urlKey.get()));
+
+ documentInfo.add(WKUInt64GetValue(documentID), toWTFString(documentURL));
+ }
+ }
+
+ if (!documentInfo.size()) {
+ m_abandonedDocumentInfo.clear();
+ return;
+ }
+
+ // Remove any documents which are no longer live.
+ m_abandonedDocumentInfo.removeIf([&](auto& keyAndValue) {
+ return !documentInfo.contains(keyAndValue.key);
+ });
+
+ // Add newly abandoned documents.
+ String currentTestURL = m_currentInvocation ? toWTFString(adoptWK(WKURLCopyString(m_currentInvocation->url()))) : "no test";
+ for (const auto& it : documentInfo)
+ m_abandonedDocumentInfo.add(it.key, AbandonedDocumentInfo(currentTestURL, it.value));
+}
+
void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
{
+ if (WKStringIsEqualToUTF8CString(messageName, "LiveDocuments")) {
+ ASSERT(WKGetTypeID(messageBody) == WKArrayGetTypeID());
+ didReceiveLiveDocumentsList(static_cast<WKArrayRef>(messageBody));
+ AsyncTask::currentTask()->taskComplete();
+ return;
+ }
+
if (WKStringIsEqualToUTF8CString(messageName, "EventSender")) {
if (m_state != RunningTest)
return;
Modified: trunk/Tools/WebKitTestRunner/TestController.h (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/TestController.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/TestController.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -48,6 +48,33 @@
struct TestCommand;
struct TestOptions;
+class AsyncTask {
+public:
+ AsyncTask(WTF::Function<void ()>&& task, WTF::Seconds timeout)
+ : m_task(WTFMove(task))
+ , m_timeout(timeout)
+ {
+ ASSERT(!currentTask());
+ }
+
+ // Returns false on timeout.
+ bool run();
+
+ void taskComplete()
+ {
+ m_taskDone = true;
+ }
+
+ static AsyncTask* currentTask();
+
+private:
+ static AsyncTask* m_currentTask;
+
+ WTF::Function<void ()> m_task;
+ WTF::Seconds m_timeout;
+ bool m_taskDone { false };
+};
+
// FIXME: Rename this TestRunner?
class TestController {
public:
@@ -121,9 +148,12 @@
unsigned imageCountInGeneralPasteboard() const;
- bool resetStateToConsistentValues(const TestOptions&);
+ enum class ResetStage { BeforeTest, AfterTest };
+ bool resetStateToConsistentValues(const TestOptions&, ResetStage);
void resetPreferencesToConsistentValues(const TestOptions&);
+ void willDestroyWebView();
+
void terminateWebContentProcess();
void reattachPageToWebProcess();
@@ -231,7 +261,12 @@
void runTestingServerLoop();
bool runTest(const char* pathOrURL);
+
+ // Returns false if timed out.
+ bool waitForCompletion(const WTF::Function<void ()>&, WTF::Seconds timeout);
+ bool handleControlCommand(const char* command);
+
void platformInitialize();
void platformDestroy();
WKContextRef platformAdjustContext(WKContextRef, WKContextConfigurationRef);
@@ -261,6 +296,12 @@
void updateWebViewSizeForTest(const TestInvocation&);
void updateWindowScaleForTest(PlatformWebView*, const TestInvocation&);
+ void updateLiveDocumentsAfterTest();
+ void checkForWorldLeaks();
+
+ void didReceiveLiveDocumentsList(WKArrayRef);
+ void findAndDumpWorldLeaks();
+
void decidePolicyForGeolocationPermissionRequestIfPossible();
void decidePolicyForUserMediaPermissionRequestIfPossible();
@@ -431,6 +472,7 @@
bool m_shouldShowWebView { false };
bool m_shouldShowTouches { false };
+ bool m_checkForWorldLeaks { false };
bool m_allowAnyHTTPSCertificateForAllowedHosts { false };
@@ -444,6 +486,18 @@
std::unique_ptr<EventSenderProxy> m_eventSenderProxy;
WorkQueueManager m_workQueueManager;
+
+ struct AbandonedDocumentInfo {
+ String testURL;
+ String abandonedDocumentURL;
+
+ AbandonedDocumentInfo() = default;
+ AbandonedDocumentInfo(String inTestURL, String inAbandonedDocumentURL)
+ : testURL(inTestURL)
+ , abandonedDocumentURL(inAbandonedDocumentURL)
+ { }
+ };
+ HashMap<uint64_t, AbandonedDocumentInfo> m_abandonedDocumentInfo;
};
struct TestCommand {
Modified: trunk/Tools/WebKitTestRunner/TestInvocation.cpp (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/TestInvocation.cpp 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/TestInvocation.cpp 2018-08-27 23:31:15 UTC (rev 235408)
@@ -187,7 +187,7 @@
WKInspectorClose(WKPageGetInspector(TestController::singleton().mainWebView()->page()));
#endif // !PLATFORM(IOS)
- if (TestController::singleton().resetStateToConsistentValues(m_options))
+ if (TestController::singleton().resetStateToConsistentValues(m_options, TestController::ResetStage::AfterTest))
return;
// The process is unresponsive, so let's start a new one.
Modified: trunk/Tools/WebKitTestRunner/TestOptions.h (235407 => 235408)
--- trunk/Tools/WebKitTestRunner/TestOptions.h 2018-08-27 23:29:42 UTC (rev 235407)
+++ trunk/Tools/WebKitTestRunner/TestOptions.h 2018-08-27 23:31:15 UTC (rev 235408)
@@ -60,6 +60,7 @@
bool enableColorFilter { false };
bool punchOutWhiteBackgroundsInDarkMode { false };
bool runSingly { false };
+ bool checkForWorldLeaks { false };
float deviceScaleFactor { 1 };
Vector<String> overrideLanguages;
@@ -97,7 +98,8 @@
|| enableColorFilter != options.enableColorFilter
|| punchOutWhiteBackgroundsInDarkMode != options.punchOutWhiteBackgroundsInDarkMode
|| jscOptions != options.jscOptions
- || runSingly != options.runSingly)
+ || runSingly != options.runSingly
+ || checkForWorldLeaks != options.checkForWorldLeaks)
return false;
return true;