Bug 1803245 - Add Timeout nsiTimer onto the Document to track active IdentityCredential requests, r=timhuang

This doesn't actually move the timer onto the document.
Instead it is the correct manual juggling of pointers via `.forget()` and `NS_RELEASE()` and some additional testing to make sure it works properly.
This passes tests where we resolve, reject immediately, and reject on timeout and has no leaks in all of those cases.
Putting the timer on the document also required putting the pending promise onto the document with it and that had further wrinkles.
I call that good enough.

Differential Revision: https://phabricator.services.mozilla.com/D164260
This commit is contained in:
Benjamin VanderSloot 2022-12-14 14:02:57 +00:00
Родитель de079e859b
Коммит c823b35008
5 изменённых файлов: 102 добавлений и 3 удалений

Просмотреть файл

@ -84,6 +84,44 @@ IdentityCredential::DiscoverFromExternalSource(
RefPtr<IdentityCredential::GetIdentityCredentialPromise::Private> result =
new IdentityCredential::GetIdentityCredentialPromise::Private(__func__);
if (StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_enabled()) {
// This is used to give the promise the appropriate lifetime so it is not
// freed before the callback below is called. This reference is taken as an
// argument to that callback.
RefPtr<IdentityCredential::GetIdentityCredentialPromise::Private>
forCallbackResult = result;
RefPtr<nsITimer> timeout;
nsresult rv = NS_NewTimerWithFuncCallback(
getter_AddRefs(timeout),
[](nsITimer* aTimer, void* aClosure) -> void {
auto* promise = static_cast<
IdentityCredential::GetIdentityCredentialPromise::Private*>(
aClosure);
if (!promise->IsResolved()) {
promise->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
}
// This releases the promise we forgot when we returned from
// this function and the timer we forgot after we built this
// callback.
NS_RELEASE(promise);
NS_RELEASE(aTimer);
},
do_AddRef(forCallbackResult).take(),
StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_duration_ms(),
nsITimer::TYPE_ONE_SHOT, "IdentityCredentialTimeoutCallback");
if (NS_WARN_IF(NS_FAILED(rv))) {
result->Reject(NS_ERROR_FAILURE, __func__);
return result.forget();
}
// Do not clean this timer when we return form this function. This will be
// done at the end of the callback above.
Unused << timeout.forget();
}
// Kick the request off to the main process and translate the result to the
// expected type when we get a result.
MOZ_ASSERT(aOptions.mIdentity.WasPassed());
@ -101,13 +139,20 @@ IdentityCredential::DiscoverFromExternalSource(
if (aResult.isSome()) {
credential->CopyValuesFrom(aResult.value());
result->Resolve(credential, __func__);
} else {
} else if (
!StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_enabled()) {
result->Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}
},
[result](const WindowGlobalChild::
DiscoverIdentityCredentialFromExternalSourcePromise::
RejectValueType& aResult) { return; });
RejectValueType& aResult) {
if (!StaticPrefs::
dom_security_credentialmanagement_identity_reject_delay_enabled()) {
result->Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}
});
return result.forget();
}

Просмотреть файл

@ -2,6 +2,7 @@
prefs =
dom.security.credentialmanagement.identity.enabled=true
dom.security.credentialmanagement.identity.select_first_in_ui_lists=true
dom.security.credentialmanagement.identity.reject_delay.enabled=false
privacy.antitracking.enableWebcompat=false # disables opener heuristic
scheme = https
skip-if = xorigin
@ -44,3 +45,4 @@ support-files =
[test_get_without_providers.html]
[test_empty_provider_list.html]
[test_two_providers.html]
[test_delay_reject.html]

Просмотреть файл

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Delay Reject</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="head.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
<script>
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ set: [
["dom.security.credentialmanagement.identity.reject_delay.enabled", "true" ],
["dom.security.credentialmanagement.identity.reject_delay.duration_ms", "1000" ],
] })
.then(() => {setupTest("delay_reject")})
.then(
function () {
return navigator.credentials.get({
identity: {
providers: []
}
});
}
).then((cred) => {
ok(false, "incorrectly got a credential");
}).catch((err) => {
ok(true, "correctly got an error");
}).finally(() => {
SimpleTest.finish();
})
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">This test verifies that our rejections are delayed, checking for >500ms.</div>
<pre id="test"></pre>
</body>
</html>

Просмотреть файл

@ -8,7 +8,8 @@
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
<script>
SimpleTest.waitForExplicitFinish();
setupTest("empty_provider_list").then(
setupTest("empty_provider_list")
.then(
function () {
return navigator.credentials.get({
identity: {

Просмотреть файл

@ -3732,6 +3732,18 @@
value: false
mirror: always
# pref controls whether we should delay identity credential rejections at all
- name: dom.security.credentialmanagement.identity.reject_delay.enabled
type: bool
value: true
mirror: always
# pref controls how long we should delay identity credential rejections if enabled
- name: dom.security.credentialmanagement.identity.reject_delay.duration_ms
type: uint32_t
value: 120000
mirror: always
# Whether or not selection events on text controls are enabled.
- name: dom.select_events.textcontrols.selectionchange.enabled
type: bool