WebKit Bugzilla
Attachment 348176 Details for
Bug 188994
: Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-188994-20180827112908.patch (text/plain), 40.19 KB, created by
Simon Fraser (smfr)
on 2018-08-27 11:29:11 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Simon Fraser (smfr)
Created:
2018-08-27 11:29:11 PDT
Size:
40.19 KB
patch
obsolete
>Subversion Revision: 235338 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 302c942a8cb7e43d5dfc46d5cbb1895e79b4b176..097b609867b3421dac8c4bd58fea2af8069df0a3 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,14 @@ >+2018-08-27 Simon Fraser <simon.fraser@apple.com> >+ >+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks >+ https://bugs.webkit.org/show_bug.cgi?id=188994 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Export Document::postTask() for use by WTR's injected bundle. >+ >+ * dom/Document.h: >+ > 2018-08-24 Ryosuke Niwa <rniwa@webkit.org> > > Click event from click() is not composed >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index 509808abae463146e3d25282b6bfcdc32e997e5d..8f82d0fd5c23e2e484bbaeea17caeaf8bd9ad321 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,46 @@ >+2018-08-27 Simon Fraser <simon.fraser@apple.com> >+ >+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks >+ https://bugs.webkit.org/show_bug.cgi?id=188994 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ 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-23 Jeff Miller <jeffm@apple.com> > > Remove -[WKNavigationDelegate _webView:decidePolicyForPluginLoadWithCurrentPolicy:pluginInfo:unavailabilityDescription:] >diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h >index 0a1d903417d4ca9dc0e30bbffac41d188f31bdbc..a71da3f0ee1a97fb65000fc1a5587904f792a68e 100644 >--- a/Source/WebCore/dom/Document.h >+++ b/Source/WebCore/dom/Document.h >@@ -1030,7 +1030,7 @@ public: > 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(); >diff --git a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp >index d1c7b0fe92e2c6e0576b89a8ba8a1c8035dacf8e..1a32c605f5f2b713b6430309e1dee833318311ee 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp >+++ b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp >@@ -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 @@ void WKBundleSetAsynchronousSpellCheckingEnabled(WKBundleRef bundleRef, WKBundle > 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 @@ void WKBundleSetDatabaseQuota(WKBundleRef bundleRef, uint64_t quota) > 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()); >diff --git a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp >index b84d5ab3a1a20b57fa2fb0ad10d6064da8843b2c..24d4f0d449d4071988661dd0c66be00f1073819f 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp >+++ b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp >@@ -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 WKBundlePageRegisterScrollOperationCompletionCallback(WKBundlePageRef pageR > }); > } > >+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)); >diff --git a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h >index 3051866c92f6212d52c930b7dd0b749ca4b2ab11..276589125ee8e2bf5e7fe259fd2cc77693e3b120 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h >+++ b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h >@@ -117,6 +117,9 @@ WK_EXPORT WKStringRef WKBundlePageCopyGroupIdentifier(WKBundlePageRef page); > 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. >diff --git a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h >index f4b0943ac6bea35d1d215d98704225c83f4b4fbb..3949f77b6c91fca4a027a44b7b43277a2cef371b 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h >+++ b/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h >@@ -68,6 +68,8 @@ WK_EXPORT void WKBundleRemoveAllWebNotificationPermissions(WKBundleRef bundle, W > 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 WKBundleResourceLoadStatisticsNotifyObserver(WKBundleRef); > > WK_EXPORT void WKBundleExtendClassesForParameterCoder(WKBundleRef bundle, WKArrayRef classes); > >+WK_EXPORT void WKBundleClearPageCache(WKBundleRef bundle); >+WK_EXPORT void WKBundleClearMemoryCache(WKBundleRef bundle); >+ > #ifdef __cplusplus > } > #endif >diff --git a/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp b/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp >index f450c1ea263e8bea6afae466cad27b9cb8185aa6..d43114c20646b4743bad7caabced8ff186c31437 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp >+++ b/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp >@@ -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 @@ Ref<API::Data> InjectedBundle::createWebDataFromUint8Array(JSContextRef context, > 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); >diff --git a/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h b/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h >index 16c7c2f2bd2ca867260ede0634ec812595daa17b..257ebb782b02240bba14225348d9d69eb44eab07 100644 >--- a/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h >+++ b/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h >@@ -122,6 +122,9 @@ public: > 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); >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index 1d4830e8aa64fc2afda3afb0a943395991f652a4..55035ab602f8a17ab67549ae3f93be34e7ed403a 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,76 @@ >+2018-08-27 Simon Fraser <simon.fraser@apple.com> >+ >+ Teach WebKitTestRunner and DumpRenderTree about detecting world leaks >+ https://bugs.webkit.org/show_bug.cgi?id=188994 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ 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 Simon Fraser <simon.fraser@apple.com> > > Convert timeout values in WebKitTestRunner to WTF::Seconds >diff --git a/Tools/DumpRenderTree/mac/DumpRenderTree.mm b/Tools/DumpRenderTree/mac/DumpRenderTree.mm >index 43069ccdf23d1bd437bc3f0be260e1497433a550..2ad03a018457e540eed866b72f74656f4b9f6948 100644 >--- a/Tools/DumpRenderTree/mac/DumpRenderTree.mm >+++ b/Tools/DumpRenderTree/mac/DumpRenderTree.mm >@@ -223,6 +223,7 @@ static int gcBetweenTests; > 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 @@ static void initializeGlobalsFromCommandLineOptions(int argc, const char *argv[] > {"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 @@ static bool useLongRunningServerMode(int argc, const char *argv[]) > 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 @@ static void runTestingServerLoop() > if (strlen(filenameBuffer) == 0) > continue; > >+ if (handleControlCommand(filenameBuffer)) >+ continue; >+ > runTest(filenameBuffer); > > if (printTestCount) { >diff --git a/Tools/DumpRenderTree/win/DumpRenderTree.cpp b/Tools/DumpRenderTree/win/DumpRenderTree.cpp >index 54d06ea83220cd6803e36f48a26ef21b00049fd9..70465cf031bd4635e331695bb38850a0df550b22 100644 >--- a/Tools/DumpRenderTree/win/DumpRenderTree.cpp >+++ b/Tools/DumpRenderTree/win/DumpRenderTree.cpp >@@ -1112,6 +1112,22 @@ static void removeFontFallbackIfPresent(const String& fontFallbackPath) > ::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 @@ int main(int argc, const char* argv[]) > if (strlen(filenameBuffer) == 0) > continue; > >+ if (handleControlCommand(filenameBuffer)) >+ continue; >+ > runTest(filenameBuffer); > } > } else { >diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp >index 5168ea7d221896624e7d20f729b287d1f9b8917f..6113b76e1b650ef009773b0b005797b2e23ad7db 100644 >--- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp >+++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp >@@ -180,6 +180,21 @@ void InjectedBundle::didReceiveMessage(WKStringRef messageName, WKTypeRef messag > 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,7 +259,24 @@ void InjectedBundle::didReceiveMessageToPage(WKBundlePageRef page, WKStringRef m > 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; > } > >diff --git a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h >index 0ad75e70dd37bab1bc822b55be955cdfd5d42734..e387e3d2cc2f3c7505f40c6b968b91a50fcf74ef 100644 >--- a/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h >+++ b/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h >@@ -140,6 +140,8 @@ public: > void textFieldDidBeginEditing(); > void textFieldDidEndEditing(); > >+ void reportLiveDocuments(WKBundlePageRef); >+ > void resetUserScriptInjectedCount() { m_userScriptInjectedCount = 0; } > void increaseUserScriptInjectedCount() { ++m_userScriptInjectedCount; } > size_t userScriptInjectedCount() const { return m_userScriptInjectedCount; } >diff --git a/Tools/WebKitTestRunner/Options.cpp b/Tools/WebKitTestRunner/Options.cpp >index a89e0db4e2495aae50e33a44d39d70402345917b..a51440b918776b58ad68d8dd5f58956cc2765313 100644 >--- a/Tools/WebKitTestRunner/Options.cpp >+++ b/Tools/WebKitTestRunner/Options.cpp >@@ -93,6 +93,12 @@ static bool handleOptionShowTouches(Options& options, const char*, const char*) > 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 @@ OptionsHandler::OptionsHandler(Options& o) > 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)); > } >diff --git a/Tools/WebKitTestRunner/Options.h b/Tools/WebKitTestRunner/Options.h >index f30c495d9930766e3083fd84ccf0567681510792..a89926dbc057fa630e54a4887ee9b17fcd22dd57 100644 >--- a/Tools/WebKitTestRunner/Options.h >+++ b/Tools/WebKitTestRunner/Options.h >@@ -48,6 +48,7 @@ struct Options { > 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; >diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp >index fedca39267662d2493b7753a6d539f11cac81771..68a4d12f50692d0050e735b5467fc09b0c0d3b31 100644 >--- a/Tools/WebKitTestRunner/TestController.cpp >+++ b/Tools/WebKitTestRunner/TestController.cpp >@@ -115,6 +115,22 @@ static WKStringRef copySignedPublicKeyAndChallengeString(WKPageRef, const void*) > 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 @@ void TestController::initialize(int argc, const char* argv[]) > 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 @@ void TestController::ensureViewSupportsOptionsForTest(const TestInvocation& test > 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 @@ void TestController::ensureViewSupportsOptionsForTest(const TestInvocation& test > > 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 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio > 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 @@ bool TestController::resetStateToConsistentValues(const TestOptions& options) > 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 @@ NO_RETURN static void die(const std::string& inputLine) > exit(1); > } > >-TestCommand parseInputLine(const std::string& inputLine) >+static TestCommand parseInputLine(const std::string& inputLine) > { > TestCommand result; > CommandTokenizer tokenizer(inputLine); >@@ -1296,6 +1376,23 @@ bool TestController::runTest(const char* inputLine) > 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 @@ void TestController::runTestingServerLoop() > if (strlen(filenameBuffer) == 0) > continue; > >+ if (handleControlCommand(filenameBuffer)) >+ continue; >+ > if (!runTest(filenameBuffer)) > break; > } >@@ -1321,6 +1421,7 @@ void TestController::run() > if (!runTest(m_paths[i].c_str())) > break; > } >+ findAndDumpWorldLeaks(); > } > } > >@@ -1385,8 +1486,51 @@ void TestController::didReceiveKeyDownMessageFromInjectedBundle(WKDictionaryRef > 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; >diff --git a/Tools/WebKitTestRunner/TestController.h b/Tools/WebKitTestRunner/TestController.h >index 16225b50fa9cc5cd6360602ae75eb66a1b765b16..cf75d6676bfb46135c640618306419de17c895ca 100644 >--- a/Tools/WebKitTestRunner/TestController.h >+++ b/Tools/WebKitTestRunner/TestController.h >@@ -48,6 +48,33 @@ class EventSenderProxy; > 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 @@ public: > > 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,6 +261,11 @@ private: > > 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(); >@@ -261,6 +296,12 @@ private: > 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 @@ private: > bool m_shouldShowWebView { false }; > > bool m_shouldShowTouches { false }; >+ bool m_checkForWorldLeaks { false }; > > bool m_allowAnyHTTPSCertificateForAllowedHosts { false }; > >@@ -444,6 +486,18 @@ private: > 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 { >diff --git a/Tools/WebKitTestRunner/TestInvocation.cpp b/Tools/WebKitTestRunner/TestInvocation.cpp >index bc1eaf63e40079aa334c1fa5574907a6573bb7ed..13b21bedd5174071bda7b61b390efd788533c409 100644 >--- a/Tools/WebKitTestRunner/TestInvocation.cpp >+++ b/Tools/WebKitTestRunner/TestInvocation.cpp >@@ -187,7 +187,7 @@ end: > 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. >diff --git a/Tools/WebKitTestRunner/TestOptions.h b/Tools/WebKitTestRunner/TestOptions.h >index 290b3bbd7417ed82e9d73b3788a6361488c8a016..dd66ec8a755746ec9cb2ba89c6b897d70cc77f69 100644 >--- a/Tools/WebKitTestRunner/TestOptions.h >+++ b/Tools/WebKitTestRunner/TestOptions.h >@@ -60,6 +60,7 @@ struct TestOptions { > bool enableColorFilter { false }; > bool punchOutWhiteBackgroundsInDarkMode { false }; > bool runSingly { false }; >+ bool checkForWorldLeaks { false }; > > float deviceScaleFactor { 1 }; > Vector<String> overrideLanguages; >@@ -97,7 +98,8 @@ struct TestOptions { > || enableColorFilter != options.enableColorFilter > || punchOutWhiteBackgroundsInDarkMode != options.punchOutWhiteBackgroundsInDarkMode > || jscOptions != options.jscOptions >- || runSingly != options.runSingly) >+ || runSingly != options.runSingly >+ || checkForWorldLeaks != options.checkForWorldLeaks) > return false; > > return true;
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 188994
:
348176
|
348201