Bug 1684139 - Adding mozilla specific wpt for timer nesting level in workers r=dom-worker-reviewers,asuth

This patch is developed from D104136#3396152.

This patch creates WorkerTestUtils.webidl under dom/webidl for testing workers with internal APIs. These APIs are exposed to workers only and controlled by dom.workers.testing.enabled pref.

This patch creates a Mozilla-specific web-platform test, testing/web-platform/mozilla/test/workers/worker_timer_nesting_level.html, to test the timer nesting level implementation for workers.

To simplify the test implementation, this patch does not implement the webidl under dom/chrome-webidl/ suggested by D104136#3396152.

Depends on D104136

Differential Revision: https://phabricator.services.mozilla.com/D105332
This commit is contained in:
Eden Chuang 2021-03-10 12:35:46 +00:00
parent 9d5c5e771a
commit 53093e432c
10 changed files with 239 additions and 0 deletions

View File

@ -0,0 +1,10 @@
/* 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/. */
[Exposed=Worker, Pref="dom.workers.testing.enabled"]
namespace WorkerTestUtils {
[Throws]
unsigned long currentTimerNestingLevel();
};

View File

@ -977,6 +977,7 @@ WEBIDL_FILES = [
"WorkerGlobalScope.webidl",
"WorkerLocation.webidl",
"WorkerNavigator.webidl",
"WorkerTestUtils.webidl",
"Worklet.webidl",
"WorkletGlobalScope.webidl",
"XMLDocument.webidl",

View File

@ -971,6 +971,11 @@ class WorkerPrivate : public RelativeTimeline {
void SetCCCollectedAnything(bool collectedAnything);
uint32_t GetCurrentTimerNestingLevel() const {
auto data = mWorkerThreadAccessible.Access();
return data->mCurrentTimerNestingLevel;
}
private:
WorkerPrivate(
WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,

View File

@ -0,0 +1,23 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/ErrorResult.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerTestUtils.h"
namespace mozilla {
namespace dom {
uint32_t WorkerTestUtils::CurrentTimerNestingLevel(const GlobalObject& aGlobal,
ErrorResult& aErr) {
MOZ_ASSERT(!NS_IsMainThread());
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
return worker->GetCurrentTimerNestingLevel();
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_WorkerTestUtils__
#define mozilla_dom_WorkerTestUtils__
namespace mozilla {
class ErrorResult;
namespace dom {
/**
* dom/webidl/WorkerTestUtils.webidl defines APIs to expose worker's internal
* status for glass-box testing. The APIs are only exposed to Workers with prefs
* dom.workers.testing.enabled.
*
* WorkerTestUtils is the implementation of dom/webidl/WorkerTestUtils.webidl
*/
class WorkerTestUtils final {
public:
/**
* Expose the worker's current timer nesting level.
*
* The worker's current timer nesting level means the executing timer
* handler's timer nesting level. When there is no executing timer handler, 0
* should be returned by this API. The maximum timer nesting level is 5.
*
* https://html.spec.whatwg.org/#timer-initialisation-steps
*/
static uint32_t CurrentTimerNestingLevel(const GlobalObject&,
ErrorResult& aErr);
};
} // namespace dom
} // namespace mozilla
#endif

View File

@ -26,6 +26,7 @@ EXPORTS.mozilla.dom += [
"WorkerRunnable.h",
"WorkerScope.h",
"WorkerStatus.h",
"WorkerTestUtils.h",
]
# Private stuff.
@ -65,6 +66,7 @@ UNIFIED_SOURCES += [
"WorkerRef.cpp",
"WorkerRunnable.cpp",
"WorkerScope.cpp",
"WorkerTestUtils.cpp",
"WorkerThread.cpp",
]

View File

@ -3483,6 +3483,11 @@
type: RelaxedAtomicBool
value: true
mirror: always
- name: dom.workers.testing.enabled
type: RelaxedAtomicBool
value: false
mirror: always
- name: dom.worklet.enabled
type: bool

View File

@ -0,0 +1 @@
prefs: [dom.workers.testing.enabled:true]

View File

@ -0,0 +1,100 @@
const maxNestingLevel = 5;
let expectedNestingLevel = 1;
let timer;
let isInterval = false;
let stopTimer = false;
let lastCall = 0;
let testStage = "ScriptLoaded";
let timerCallback = async () => {
let now = Date.now();
if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
postMessage({
stage: testStage,
status: "FAIL",
msg: `current timer nesting level is ${WorkerTestUtils.currentTimerNestingLevel()}, expected ${expectedNestingLevel}`,
});
if (isInterval) {
clearInterval(timer);
}
return;
}
if (!stopTimer) {
if (expectedNestingLevel === maxNestingLevel) {
stopTimer = true;
} else {
expectedNestingLevel = expectedNestingLevel + 1;
}
if (!isInterval) {
setTimeout(timerCallback, 0);
}
lastCall = now;
return;
}
await Promise.resolve(true).then(() => {
if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
postMessage({
stage: testStage,
status: "FAIL",
msg: `Timer nesting level should be in effect for immediately resolved micro-tasks`,
});
}
});
if (now - lastCall < 3) {
postMessage({
stage: testStage,
status: "FAIL",
msg: `timer nesting level reaches the max nesting level(${maxNestingLevel}), interval time should be clamped at least 3, but got ${now -
lastCall}`,
});
} else {
postMessage({ stage: testStage, status: "PASS", msg: "" });
}
stopTimer = false;
if (isInterval) {
clearInterval(timer);
}
};
onmessage = async e => {
testStage = e.data;
switch (e.data) {
case "CheckInitialValue":
if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
postMessage({ stage: testStage, status: "PASS", msg: "" });
} else {
postMessage({
stage: testStage,
status: "FAIL",
msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) after top level script loaded.`,
});
}
break;
case "TestSetInterval":
expectedNestingLevel = 1;
isInterval = true;
timer = setInterval(timerCallback, 0);
break;
case "TestSetTimeout":
expectedNestingLevel = 1;
isInterval = false;
setTimeout(timerCallback, 0);
break;
case "CheckNoTimer":
if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
postMessage({ stage: testStage, status: "PASS", msg: "" });
} else {
postMessage({
stage: testStage,
status: "FAIL",
msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) when there is no timer in queue.`,
});
}
break;
}
};
postMessage({ stage: testStage, status: "PASS" });

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<title>Worker: Timer Nesting Level</title>
<Script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict'
/**
* This test includes following four test stages.
* 1. CheckInitialValue: Checking the initial value of worker's current timer
* nesting level after the worker's top level script is loaded. The result
* is expected as 0.
* 2. TestSetInterval: Checking the worker's current timer nesting level with
* setInterval with following steps
* 1. call setInterval(callback, 0) to create a repeating timer.
* 2. checking the current timer nesting level in the callback. The value
* should increase every time executing the callback until it reaches the
* maximun nesting level(5).
* 3. Checking the worker's current timer nesting level with immediately
* resolved promise.
* 4. Checking the the time duration between two callback launching.
* 3. TestSetTimeout: Checking the worker's current timer nesting level with
* setTimeout. This stage has similar test steps with TestSetInterval.
* The difference is this stage using the recursive setTimeout to accumulate
* the timer nesting level.
* 4. CheckNoTimer: Checking the situation which the worker has no pending
* timer. The result is expected as 0.
*/
let testStages = ["CheckInitialValue",
"TestSetInterval",
"TestSetTimeout",
"CheckNoTimer"];
promise_test(async function(t) {
let result = await new Promise( (resolve, reject) => {
let worker = new Worker("resources/worker.js");
worker.onmessage = (e) => {
if (e.data.status === "FAIL") {
resolve(e.data);
return;
}
if (testStages.length !== 0) {
worker.postMessage(testStages.shift());
} else {
resolve({status: "PASS", msg: "Timer nesting level for workers"});
}
};
});
assert_true(result.status === "PASS", result.msg);
}, 'Worker timer nesting level ');
</script>