Bug 1669552 - Add TestUtils support for WPT r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D135942
This commit is contained in:
Kagami Sascha Rosylight 2022-01-14 18:36:59 +00:00
parent af8f04a00d
commit 624a3640bf
17 changed files with 185 additions and 15 deletions

58
dom/base/TestUtils.cpp Normal file
View File

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestUtils.h"
#include "mozilla/dom/TestUtilsBinding.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "nsJSEnvironment.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestUtils, mOwner)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestUtils)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestUtils)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestUtils)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject* TestUtils::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return TestUtils_Binding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise> TestUtils::Gc(ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
// TODO(krosylight): Ideally we could use nsJSContext::IncrementalGC to make
// it actually async, but that's not required right now.
NS_DispatchToCurrentThread(
NS_NewCancelableRunnableFunction("TestUtils::Gc", [promise] {
if (NS_IsMainThread()) {
nsJSContext::GarbageCollectNow(JS::GCReason::DOM_TESTUTILS,
nsJSContext::NonIncrementalGC,
nsJSContext::NonShrinkingGC);
nsJSContext::CycleCollectNow(CCReason::API);
} else {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
false /* shrinking */,
false /* collect children */);
workerPrivate->CycleCollectInternal(false);
}
promise->MaybeResolveWithUndefined();
}));
return promise.forget();
}
} // namespace mozilla::dom

43
dom/base/TestUtils.h Normal file
View File

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DOM_TESTING_TESTUTILS_H_
#define DOM_TESTING_TESTUTILS_H_
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
class nsIGlobalObject;
namespace mozilla::dom {
class TestUtils final : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestUtils)
explicit TestUtils(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {}
nsIGlobalObject* GetParentObject() const { return mOwner; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<Promise> Gc(ErrorResult& aRv);
void Shutdown();
private:
~TestUtils() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
};
} // namespace mozilla::dom
#endif // DOM_TESTING_TESTUTILS_H_

View File

@ -272,6 +272,7 @@ EXPORTS.mozilla.dom += [
"StyleSheetList.h",
"SubtleCrypto.h",
"SyncMessageSender.h",
"TestUtils.h",
"Text.h",
"Timeout.h",
"TimeoutHandler.h",
@ -446,6 +447,7 @@ UNIFIED_SOURCES += [
"StyledRange.cpp",
"StyleSheetList.cpp",
"SubtleCrypto.cpp",
"TestUtils.cpp",
"Text.cpp",
"TextInputProcessor.cpp",
"ThirdPartyUtil.cpp",

View File

@ -164,6 +164,7 @@
#include "mozilla/dom/StorageNotifierService.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/TabMessageTypes.h"
#include "mozilla/dom/TestUtils.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
@ -1429,6 +1430,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTestUtils)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDebuggerNotificationManager)
@ -1535,6 +1537,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTestUtils)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDebuggerNotificationManager)
@ -7479,6 +7482,15 @@ void nsGlobalWindowInner::StructuredClone(
nsContentUtils::StructuredClone(aCx, this, aValue, aOptions, aRetval, aError);
}
already_AddRefed<mozilla::dom::TestUtils> nsGlobalWindowInner::TestUtils() {
if (!mTestUtils) {
mTestUtils = new class TestUtils(AsGlobal());
}
RefPtr<class TestUtils> ref = mTestUtils;
return ref.forget();
}
nsresult nsGlobalWindowInner::Dispatch(
TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());

View File

@ -129,6 +129,7 @@ class SharedWorker;
class Selection;
class SpeechSynthesis;
class Timeout;
class TestUtils;
class U2F;
class VisualViewport;
class VRDisplay;
@ -914,6 +915,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
JS::MutableHandle<JS::Value> aRetval,
mozilla::ErrorResult& aError);
already_AddRefed<mozilla::dom::TestUtils> TestUtils();
// ChromeWindow bits. Do NOT call these unless your window is in
// fact chrome.
uint16_t WindowState();
@ -1440,6 +1443,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
RefPtr<mozilla::dom::VisualViewport> mVisualViewport;
RefPtr<mozilla::dom::TestUtils> mTestUtils;
// The document's principals and CSP are only stored if
// FreeInnerObjects has been called.
nsCOMPtr<nsIPrincipal> mDocumentPrincipal;

View File

@ -0,0 +1,14 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://testutils.spec.whatwg.org/#the-testutils-object
*/
[Exposed=(Window,Worker),
Pref="dom.testing.testutils.enabled"]
interface TestUtils {
[NewObject, Throws] Promise<void> gc();
};

View File

@ -79,3 +79,9 @@ partial interface mixin WindowOrWorkerGlobalScope {
[Throws, Pref="dom.caches.enabled", SameObject]
readonly attribute CacheStorage caches;
};
// https://testutils.spec.whatwg.org/#the-testutils-object
partial interface mixin WindowOrWorkerGlobalScope {
[Pref="dom.testing.testutils.enabled", SameObject]
readonly attribute TestUtils testUtils;
};

View File

@ -933,6 +933,7 @@ WEBIDL_FILES = [
"TCPSocket.webidl",
"TCPSocketErrorEvent.webidl",
"TCPSocketEvent.webidl",
"TestUtils.webidl",
"Text.webidl",
"TextClause.webidl",
"TextDecoder.webidl",

View File

@ -76,6 +76,7 @@
#include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
#include "mozilla/dom/SimpleGlobalObject.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TestUtils.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
#include "mozilla/dom/WorkerGlobalScopeBinding.h"
@ -363,6 +364,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScope,
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTestUtils)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDebuggerNotificationManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@ -374,6 +376,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScope,
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTestUtils)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDebuggerNotificationManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -404,6 +407,15 @@ already_AddRefed<CacheStorage> WorkerGlobalScope::GetCaches(ErrorResult& aRv) {
return ref.forget();
}
already_AddRefed<mozilla::dom::TestUtils> WorkerGlobalScope::TestUtils() {
if (!mTestUtils) {
mTestUtils = new class TestUtils(this);
}
RefPtr<class TestUtils> ref = mTestUtils;
return ref.forget();
}
bool WorkerGlobalScope::IsSecureContext() const {
bool globalSecure = JS::GetIsSecureContext(
js::GetNonCCWObjectRealm(GetWrapperPreserveColor()));

View File

@ -71,6 +71,7 @@ class ServiceWorkerDescriptor;
class ServiceWorkerRegistration;
class ServiceWorkerRegistrationDescriptor;
struct StructuredSerializeOptions;
class TestUtils;
class WorkerDocumentListener;
class WorkerLocation;
class WorkerNavigator;
@ -298,6 +299,8 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase,
already_AddRefed<cache::CacheStorage> GetCaches(ErrorResult& aRv);
already_AddRefed<mozilla::dom::TestUtils> TestUtils();
bool WindowInteractionAllowed() const;
void AllowWindowInteraction();
@ -332,6 +335,7 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase,
RefPtr<Performance> mPerformance;
RefPtr<IDBFactory> mIndexedDB;
RefPtr<cache::CacheStorage> mCacheStorage;
RefPtr<class TestUtils> mTestUtils;
RefPtr<DebuggerNotificationManager> mDebuggerNotificationManager;
uint32_t mWindowInteractionsAllowed = 0;
};

View File

@ -586,6 +586,7 @@ namespace JS {
D(XPCONNECT_SHUTDOWN, 53) \
D(DOCSHELL, 54) \
D(HTML_PARSER, 55) \
D(DOM_TESTUTILS, 56) \
\
/* Reasons reserved for embeddings. */ \
D(RESERVED1, FIRST_RESERVED_REASON) \

View File

@ -3532,6 +3532,12 @@
value: false
mirror: always
# To enable TestUtils interface on WPT
- name: dom.testing.testutils.enabled
type: RelaxedAtomicBool
value: false
mirror: always
- name: dom.textMetrics.actualBoundingBox.enabled
type: bool
value: true

View File

@ -11,6 +11,8 @@ user_pref("browser.sessionstore.resume_from_crash", false);
// Don't show the Bookmarks Toolbar on any tab (the above pref that
// disables the New Tab Page ends up showing the toolbar on about:blank).
user_pref("browser.toolbars.bookmarks.visibility", "never");
// Expose TestUtils interface
user_pref("dom.testing.testutils.enabled", true);
// Only install add-ons from the profile and the application scope
// Also ensure that those are not getting disabled.
// see: https://developer.mozilla.org/en/Installing_extensions

View File

@ -7,8 +7,9 @@
// if perform_gc is true.
async function read_and_gc(reader, perform_gc) {
const read_promise = reader.read();
if (perform_gc)
garbageCollect();
if (perform_gc) {
await garbageCollect();
}
return read_promise;
}
@ -65,7 +66,7 @@ promise_test(async() => {
let blob = new Blob([typed_arr]);
const stream = blob.stream();
blob = null;
garbageCollect();
await garbageCollect();
const chunks = await read_all_chunks(stream, /*perform_gc=*/true);
assert_array_equals(chunks, input_arr);
}, "Blob.stream() garbage collection of blob shouldn't break stream" +

View File

@ -11,8 +11,8 @@
* @return {undefined}
*/
async function maybeGarbageCollectAsync() {
if (typeof TestUtils !== 'undefined' && TestUtils.gc) {
await TestUtils.gc();
if (typeof testUtils !== 'undefined' && testUtils.gc) {
await testUtils.gc();
} else if (self.gc) {
// Use --expose_gc for V8 (and Node.js)
// to pass this flag at chrome launch use: --js-flags="--expose-gc"

View File

@ -2,7 +2,7 @@
// META: script=../resources/test-utils.js
'use strict';
promise_test(() => {
promise_test(async () => {
let controller;
new ReadableStream({
@ -11,7 +11,7 @@ promise_test(() => {
}
});
garbageCollect();
await garbageCollect();
return delay(50).then(() => {
controller.close();
@ -22,7 +22,7 @@ promise_test(() => {
}, 'ReadableStreamController methods should continue working properly when scripts lose their reference to the ' +
'readable stream');
promise_test(() => {
promise_test(async () => {
let controller;
@ -32,13 +32,13 @@ promise_test(() => {
}
}).getReader().closed;
garbageCollect();
await garbageCollect();
return delay(50).then(() => controller.close()).then(() => closedPromise);
}, 'ReadableStream closed promise should fulfill even if the stream and reader JS references are lost');
promise_test(t => {
promise_test(async t => {
const theError = new Error('boo');
let controller;
@ -49,20 +49,20 @@ promise_test(t => {
}
}).getReader().closed;
garbageCollect();
await garbageCollect();
return delay(50).then(() => controller.error(theError))
.then(() => promise_rejects_exactly(t, theError, closedPromise));
}, 'ReadableStream closed promise should reject even if stream and reader JS references are lost');
promise_test(() => {
promise_test(async () => {
const rs = new ReadableStream({});
rs.getReader();
garbageCollect();
await garbageCollect();
return delay(50).then(() => assert_throws_js(TypeError, () => rs.getReader(),
'old reader should still be locking the stream even after garbage collection'));

View File

@ -47,8 +47,11 @@ self.constructorThrowsForAll = (constructor, firstArgs) => {
'constructor should throw a TypeError'));
};
self.garbageCollect = () => {
if (self.gc) {
self.garbageCollect = async () => {
if (self.testUtils?.gc) {
// https://testutils.spec.whatwg.org/#the-testutils-object
await testUtils.gc();
} else if (self.gc) {
// Use --expose_gc for V8 (and Node.js)
// to pass this flag at chrome launch use: --js-flags="--expose-gc"
// Exposed in SpiderMonkey shell as well