Backed out 5 changesets (bug 1945576, bug 1945573, bug 1945577) for causing junit failures @ PromptDelegateTest CLOSED TREE

Backed out changeset bad19f7c69d8 (bug 1945577)
Backed out changeset f691730e3dac (bug 1945573)
Backed out changeset 194f70082e01 (bug 1945573)
Backed out changeset 8ce3493a6152 (bug 1945576)
Backed out changeset b34ad4456d7c (bug 1945576)
This commit is contained in:
Alexandru Marc
2025-03-25 18:10:49 +02:00
parent ec0102d276
commit 15953c627f
36 changed files with 667 additions and 875 deletions

View File

@@ -52,7 +52,6 @@
#include "mozilla/dom/LockManager.h"
#include "mozilla/dom/MIDIAccessManager.h"
#include "mozilla/dom/MIDIOptionsBinding.h"
#include "mozilla/dom/NavigatorLogin.h"
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/StorageManager.h"
@@ -165,7 +164,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddonManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebGpu)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLogin)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrivateAttribution)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUserActivation)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
@@ -256,8 +254,6 @@ void Navigator::Invalidate() {
mLocks = nullptr;
}
mLogin = nullptr;
mPrivateAttribution = nullptr;
mUserActivation = nullptr;
@@ -2274,13 +2270,6 @@ dom::LockManager* Navigator::Locks() {
return mLocks;
}
NavigatorLogin* Navigator::Login() {
if (!mLogin) {
mLogin = new NavigatorLogin(GetWindow()->AsGlobal());
}
return mLogin;
}
dom::PrivateAttribution* Navigator::PrivateAttribution() {
if (!mPrivateAttribution) {
mPrivateAttribution = new dom::PrivateAttribution(GetWindow()->AsGlobal());

View File

@@ -42,7 +42,6 @@ class ServiceWorkerContainer;
class CredentialsContainer;
class Clipboard;
class LockManager;
class NavigatorLogin;
class PrivateAttribution;
class HTMLMediaElement;
class AudioContext;
@@ -211,7 +210,6 @@ class Navigator final : public nsISupports, public nsWrapperCache {
dom::Clipboard* Clipboard();
webgpu::Instance* Gpu();
dom::LockManager* Locks();
NavigatorLogin* Login();
dom::PrivateAttribution* PrivateAttribution();
static bool Webdriver();
@@ -309,7 +307,6 @@ class Navigator final : public nsISupports, public nsWrapperCache {
RefPtr<webgpu::Instance> mWebGpu;
RefPtr<Promise> mSharePromise; // Web Share API related
RefPtr<LockManager> mLocks;
RefPtr<NavigatorLogin> mLogin;
RefPtr<dom::PrivateAttribution> mPrivateAttribution;
RefPtr<dom::UserActivation> mUserActivation;
RefPtr<dom::WakeLockJS> mWakeLock;

View File

@@ -98,9 +98,7 @@ static bool ConsumeUserActivation(nsPIDOMWindowInner* aParent) {
return doc->ConsumeTransientUserGestureActivation();
}
// static
bool CredentialsContainer::IsSameOriginWithAncestors(
nsPIDOMWindowInner* aParent) {
static bool IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent) {
// This method returns true if aParent is either not in a frame / iframe, or
// is in a frame or iframe and all ancestors for aParent are the same origin.
// This is useful for Credential Management because we need to prohibit

View File

@@ -38,8 +38,6 @@ class CredentialsContainer final : public nsISupports, public nsWrapperCache {
already_AddRefed<Promise> PreventSilentAccess(ErrorResult& aRv);
static bool IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent);
private:
~CredentialsContainer();

View File

@@ -1346,6 +1346,73 @@ RefPtr<IdentityCredential::GetTokenPromise> IdentityCredential::FetchToken(
});
}
// static
RefPtr<IdentityCredential::GetMetadataPromise>
IdentityCredential::FetchMetadata(nsIPrincipal* aPrincipal,
const IdentityProviderConfig& aProvider,
const IdentityProviderAPIConfig& aManifest) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aPrincipal);
// Build the URL
nsCOMPtr<nsIURI> baseURI;
nsCString baseURIString = aProvider.mConfigURL.Value();
nsresult rv = NS_NewURI(getter_AddRefs(baseURI), baseURIString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
__func__);
}
nsCOMPtr<nsIURI> idpURI;
nsCString metadataSpec = aManifest.mClient_metadata_endpoint;
rv = NS_NewURI(getter_AddRefs(idpURI), metadataSpec.get(), baseURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
__func__);
}
nsCString configLocation;
rv = idpURI->GetSpec(configLocation);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
__func__);
}
// Create the global
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
nsCOMPtr<nsIGlobalObject> global;
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> sandbox(cx);
rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
if (NS_WARN_IF(NS_FAILED(rv))) {
return IdentityCredential::GetMetadataPromise::CreateAndReject(rv,
__func__);
}
MOZ_ASSERT(JS_IsGlobalObject(sandbox));
global = xpc::NativeGlobal(sandbox);
if (NS_WARN_IF(!global)) {
return IdentityCredential::GetMetadataPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
// Create a new request
constexpr auto fragment = ""_ns;
auto internalRequest =
MakeSafeRefPtr<InternalRequest>(configLocation, fragment);
internalRequest->SetRedirectMode(RequestRedirect::Error);
internalRequest->SetCredentialsMode(RequestCredentials::Omit);
internalRequest->SetReferrerPolicy(ReferrerPolicy::No_referrer);
internalRequest->SetMode(RequestMode::Cors);
internalRequest->SetCacheMode(RequestCache::No_cache);
internalRequest->SetHeaders(new InternalHeaders(HeadersGuardEnum::Request));
internalRequest->OverrideContentPolicyType(
nsContentPolicyType::TYPE_WEB_IDENTITY);
RefPtr<Request> request =
new Request(global, std::move(internalRequest), nullptr);
return IdentityNetworkHelpers::FetchJSONStructure<
IdentityProviderClientMetadata>(request);
}
// static
already_AddRefed<Promise> IdentityCredential::Disconnect(
const GlobalObject& aGlobal,
@@ -1741,11 +1808,107 @@ IdentityCredential::PromptUserWithPolicy(
__func__);
}
// Mark as logged in and return
icStorageService->SetState(aPrincipal, idpPrincipal,
NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
return IdentityCredential::GetAccountPromise::CreateAndResolve(
std::make_tuple(aManifest, aAccount), __func__);
// if registered, mark as logged in and return
if (registered) {
icStorageService->SetState(aPrincipal, idpPrincipal,
NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
return IdentityCredential::GetAccountPromise::CreateAndResolve(
std::make_tuple(aManifest, aAccount), __func__);
}
// otherwise, fetch ->Then display ->Then return ->Catch reject
RefPtr<BrowsingContext> browsingContext(aBrowsingContext);
nsCOMPtr<nsIPrincipal> argumentPrincipal(aPrincipal);
return FetchMetadata(aPrincipal, aProvider, aManifest)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aAccount, aManifest, aProvider, argumentPrincipal, browsingContext,
icStorageService,
idpPrincipal](const IdentityProviderClientMetadata& metadata)
-> RefPtr<GenericPromise> {
nsresult error;
nsCOMPtr<nsIIdentityCredentialPromptService> icPromptService =
mozilla::components::IdentityCredentialPromptService::Service(
&error);
if (NS_WARN_IF(!icPromptService)) {
return GenericPromise::CreateAndReject(error, __func__);
}
nsCOMPtr<nsIXPConnectWrappedJS> wrapped =
do_QueryInterface(icPromptService);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(wrapped->GetJSObjectGlobal()))) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
JS::Rooted<JS::Value> providerJS(jsapi.cx());
bool success = ToJSValue(jsapi.cx(), aProvider, &providerJS);
if (NS_WARN_IF(!success)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
JS::Rooted<JS::Value> metadataJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), metadata, &metadataJS);
if (NS_WARN_IF(!success)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
JS::Rooted<JS::Value> manifestJS(jsapi.cx());
success = ToJSValue(jsapi.cx(), aManifest, &manifestJS);
if (NS_WARN_IF(!success)) {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
RefPtr<Promise> showPromptPromise;
icPromptService->ShowPolicyPrompt(
browsingContext, providerJS, manifestJS, metadataJS,
getter_AddRefs(showPromptPromise));
RefPtr<GenericPromise::Private> resultPromise =
new GenericPromise::Private(__func__);
showPromptPromise->AddCallbacksWithCycleCollectedArgs(
[aAccount, argumentPrincipal, idpPrincipal, resultPromise,
icStorageService](JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult&) {
bool isBool = aValue.isBoolean();
if (!isBool) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
icStorageService->SetState(
argumentPrincipal, idpPrincipal,
NS_ConvertUTF16toUTF8(aAccount.mId), true, true);
resultPromise->Resolve(aValue.toBoolean(), __func__);
},
[resultPromise](JSContext*, JS::Handle<JS::Value> aValue,
ErrorResult&) {
resultPromise->Reject(
Promise::TryExtractNSResultFromRejectionValue(aValue),
__func__);
});
// Working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85883
showPromptPromise->AppendNativeHandler(
new MozPromiseRejectOnDestruction{resultPromise, __func__});
return resultPromise;
},
[](nsresult error) {
return GenericPromise::CreateAndReject(error, __func__);
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[aManifest, aAccount](bool success) {
if (success) {
return IdentityCredential::GetAccountPromise::CreateAndResolve(
std::make_tuple(aManifest, aAccount), __func__);
}
return IdentityCredential::GetAccountPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
},
[](nsresult error) {
return IdentityCredential::GetAccountPromise::CreateAndReject(
error, __func__);
});
}
// static

View File

@@ -11,7 +11,6 @@
#include "mozilla/dom/IdentityCredential.h"
#include "mozilla/dom/IdentityCredentialBinding.h"
#include "mozilla/dom/CredentialManagementBinding.h"
#include "mozilla/dom/LoginStatusBinding.h"
namespace IPC {
@@ -82,10 +81,6 @@ struct ParamTraits<mozilla::dom::IdentityCredentialRequestOptions> {
}
};
template <>
struct ParamTraits<mozilla::dom::LoginStatus>
: public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::LoginStatus> {};
} // namespace IPC
#endif // mozilla_dom_identitycredentialserializationhelpers_h__

View File

@@ -38,6 +38,99 @@ class IdentityNetworkHelpers {
static RefPtr<MozPromise<DisconnectedAccount, nsresult, true>>
FetchDisconnectHelper(nsIURI* aAccountsEndpoint, const nsCString& aBody,
nsIPrincipal* aTriggeringPrincipal);
// Helper to get a JSON structure via a Fetch.
// The Request must already be built and T should be a webidl type with
// annotation GenerateConversionToJS so it has an Init method.
template <typename T, typename TPromise = MozPromise<T, nsresult, true>>
static RefPtr<TPromise> FetchJSONStructure(Request* aRequest) {
MOZ_ASSERT(XRE_IsParentProcess());
// Create the returned Promise
RefPtr<typename TPromise::Private> resultPromise =
new typename TPromise::Private(__func__);
// Fetch the provided request
RequestOrUTF8String fetchInput;
fetchInput.SetAsRequest() = aRequest;
RootedDictionary<RequestInit> requestInit(RootingCx());
IgnoredErrorResult error;
RefPtr<Promise> fetchPromise =
FetchRequest(aRequest->GetParentObject(), fetchInput, requestInit,
CallerType::System, error);
if (NS_WARN_IF(error.Failed())) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return resultPromise;
}
// Working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85883
RefPtr<PromiseNativeHandler> reject =
new MozPromiseRejectOnDestruction{resultPromise, __func__};
// Handle the response
fetchPromise->AddCallbacksWithCycleCollectedArgs(
[resultPromise, reject](JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult&) {
// Get the Response object from the argument to the callback
if (NS_WARN_IF(!aValue.isObject())) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
MOZ_ASSERT(obj);
Response* response = nullptr;
if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Response, &obj, response)))) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
// Make sure the request was a success
if (!response->Ok()) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
// Parse the body into JSON, which must be done async
IgnoredErrorResult error;
RefPtr<Promise> jsonPromise = response->ConsumeBody(
aCx, BodyConsumer::ConsumeType::JSON, error);
if (NS_WARN_IF(error.Failed())) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
// Handle the parsed JSON from the Response body
jsonPromise->AddCallbacksWithCycleCollectedArgs(
[resultPromise](JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult&) {
// Parse the JSON into the correct type, validating fields and
// types
T result;
bool success = result.Init(aCx, aValue);
if (!success) {
resultPromise->Reject(NS_ERROR_FAILURE, __func__);
return;
}
resultPromise->Resolve(result, __func__);
},
[resultPromise](JSContext*, JS::Handle<JS::Value> aValue,
ErrorResult&) {
resultPromise->Reject(
Promise::TryExtractNSResultFromRejectionValue(aValue),
__func__);
});
jsonPromise->AppendNativeHandler(reject);
},
[resultPromise](JSContext*, JS::Handle<JS::Value> aValue,
ErrorResult&) {
resultPromise->Reject(
Promise::TryExtractNSResultFromRejectionValue(aValue), __func__);
});
fetchPromise->AppendNativeHandler(reject);
return resultPromise;
}
};
} // namespace mozilla::dom

View File

@@ -1,194 +0,0 @@
/* -*- 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/Components.h"
#include "mozilla/dom/CredentialsContainer.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/NavigatorLogin.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Maybe.h"
#include "mozilla/net/SFVService.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIGlobalObject.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
static constexpr nsLiteralCString kLoginStatusPermission =
"self-reported-logged-in"_ns;
uint32_t ConvertStatusToPermission(LoginStatus aStatus) {
if (aStatus == LoginStatus::Logged_in) {
return nsIPermissionManager::ALLOW_ACTION;
} else if (aStatus == LoginStatus::Logged_out) {
return nsIPermissionManager::DENY_ACTION;
} else {
// This should be unreachable, but let's return a real value
return nsIPermissionManager::UNKNOWN_ACTION;
}
}
Maybe<LoginStatus> PermissionToStatus(uint32_t aPermission) {
if (aPermission == nsIPermissionManager::ALLOW_ACTION) {
return Some(LoginStatus::Logged_in);
} else if (aPermission == nsIPermissionManager::DENY_ACTION) {
return Some(LoginStatus::Logged_out);
} else {
if (aPermission == nsIPermissionManager::UNKNOWN_ACTION) {
MOZ_ASSERT(false, "Unexpected permission action from login status");
}
return Nothing();
}
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(NavigatorLogin, mOwner)
NavigatorLogin::~NavigatorLogin() = default;
JSObject* NavigatorLogin::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return NavigatorLogin_Binding::Wrap(aCx, this, aGivenProto);
}
NavigatorLogin::NavigatorLogin(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
MOZ_ASSERT(mOwner);
};
already_AddRefed<mozilla::dom::Promise> NavigatorLogin::SetStatus(
LoginStatus aStatus, mozilla::ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
if (aRv.Failed()) {
return nullptr;
}
nsPIDOMWindowInner* window = mOwner->GetAsInnerWindow();
if (!window) {
promise->MaybeRejectWithUnknownError(
"navigator.login.setStatus called on unavailable window"_ns);
return promise.forget();
}
if (!CredentialsContainer::IsSameOriginWithAncestors(window)) {
promise->MaybeRejectWithSecurityError(
"navigator.login.setStatus must be called in a frame that is same-origin with its ancestors"_ns);
return promise.forget();
}
WindowGlobalChild* wgc = window->GetWindowGlobalChild();
if (!wgc) {
promise->MaybeRejectWithUnknownError(
"navigator.login.setStatus called while window already destroyed"_ns);
return promise.forget();
}
wgc->SendSetLoginStatus(aStatus)->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](
const WindowGlobalChild::SetLoginStatusPromise::ResolveValueType&
aResult) {
if (NS_SUCCEEDED(aResult)) {
promise->MaybeResolveWithUndefined();
} else {
promise->MaybeRejectWithUnknownError(
"navigator.login.setStatus had an unexpected internal error");
}
},
[promise](const WindowGlobalChild::SetLoginStatusPromise::RejectValueType&
aResult) {
promise->MaybeRejectWithUnknownError(
"navigator.login.setStatus had an unexpected internal error");
});
return promise.forget();
}
// static
nsresult NavigatorLogin::SetLoginStatus(nsIPrincipal* aPrincipal,
LoginStatus aStatus) {
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIPermissionManager> permMgr =
components::PermissionManager::Service();
if (!permMgr) {
return NS_ERROR_NOT_AVAILABLE;
}
return permMgr->AddFromPrincipal(aPrincipal, kLoginStatusPermission,
ConvertStatusToPermission(aStatus),
nsIPermissionManager::EXPIRE_NEVER, 0);
}
// static
nsresult NavigatorLogin::SetLoginStatus(nsIPrincipal* aPrincipal,
const nsACString& aStatus) {
LoginStatus parsedStatus;
nsresult rv = ParseLoginStatusHeader(aStatus, parsedStatus);
NS_ENSURE_SUCCESS(rv, rv);
return SetLoginStatus(aPrincipal, parsedStatus);
}
// static
nsresult NavigatorLogin::ParseLoginStatusHeader(const nsACString& aStatus,
LoginStatus& aResult) {
nsCOMPtr<nsISFVService> sfv = net::GetSFVService();
nsCOMPtr<nsISFVItem> item;
nsresult rv = sfv->ParseItem(aStatus, getter_AddRefs(item));
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsISFVBareItem> value;
rv = item->GetValue(getter_AddRefs(value));
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
if (!token) {
return NS_ERROR_UNEXPECTED;
}
nsAutoCString parsedStatus;
rv = token->GetValue(parsedStatus);
if (NS_FAILED(rv)) {
return rv;
}
if (parsedStatus == "logged-in") {
aResult = LoginStatus::Logged_in;
return NS_OK;
}
if (parsedStatus == "logged-out") {
aResult = LoginStatus::Logged_out;
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
// static
nsresult GetLoginStatus(nsIPrincipal* aPrincipal, Maybe<LoginStatus>& aStatus) {
nsCOMPtr<nsIPermissionManager> permMgr =
components::PermissionManager::Service();
if (!permMgr) {
aStatus = Nothing();
return NS_ERROR_SERVICE_NOT_AVAILABLE;
}
uint32_t action;
nsresult rv = permMgr->TestPermissionFromPrincipal(
aPrincipal, kLoginStatusPermission, &action);
if (NS_FAILED(rv)) {
aStatus = Nothing();
return rv;
}
aStatus = PermissionToStatus(action);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

View File

@@ -1,49 +0,0 @@
/* -*- 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_NavigatorLogin_h
#define mozilla_dom_NavigatorLogin_h
#include "ErrorList.h"
#include "mozilla/dom/LoginStatusBinding.h"
#include "mozilla/Maybe.h"
#include "nsIGlobalObject.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
namespace mozilla::dom {
class NavigatorLogin : public nsWrapperCache {
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(NavigatorLogin)
NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(NavigatorLogin)
explicit NavigatorLogin(nsIGlobalObject* aGlobal);
nsIGlobalObject* GetParentObject() const { return mOwner; }
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<mozilla::dom::Promise> SetStatus(LoginStatus aStatus,
mozilla::ErrorResult& aRv);
static Maybe<LoginStatus> GetLoginStatus(nsIPrincipal* aPrincipal);
static nsresult SetLoginStatus(nsIPrincipal* aPrincipal, LoginStatus aStatus);
static nsresult SetLoginStatus(nsIPrincipal* aPrincipal,
const nsACString& aStatus);
static nsresult ParseLoginStatusHeader(const nsACString& aStatus,
LoginStatus& aResult);
protected:
virtual ~NavigatorLogin();
private:
nsCOMPtr<nsIGlobalObject> mOwner;
};
} // namespace mozilla::dom
#endif // mozilla_dom_NavigatorLogin_h

View File

@@ -12,18 +12,13 @@ EXPORTS.mozilla.dom += [
"IdentityCredential.h",
"IdentityCredentialSerializationHelpers.h",
"IdentityNetworkHelpers.h",
"NavigatorLogin.h",
]
IPDL_SOURCES += [
"IPCIdentityCredential.ipdlh",
]
UNIFIED_SOURCES += [
"IdentityCredential.cpp",
"IdentityNetworkHelpers.cpp",
"NavigatorLogin.cpp",
]
UNIFIED_SOURCES += ["IdentityCredential.cpp", "IdentityNetworkHelpers.cpp"]
include("/ipc/chromium/chromium-config.mozbuild")

View File

@@ -13,17 +13,16 @@ support-files = [
"server_disconnect.json^headers^",
"server_idtoken.json",
"server_idtoken.json^headers^",
"server_loginStatus.sjs",
"server_manifest_disconnect_failure.json",
"server_manifest_disconnect_failure.json^headers^",
"server_manifest.json",
"server_manifest.json^headers^",
"server_metadata.json",
"server_metadata.json^headers^",
]
["browser_close_prompt_on_timeout.js"]
["browser_disconnect.js"]
["browser_loginStatus.js"]
["browser_single_concurrent_identity_request.js"]

View File

@@ -1,499 +0,0 @@
/* 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/. */
"use strict";
XPCOMUtils.defineLazyServiceGetter(
this,
"IdentityCredentialStorageService",
"@mozilla.org/browser/identity-credential-storage-service;1",
"nsIIdentityCredentialStorageService"
);
const TEST_URL = "https://example.com/";
const TEST_XORIGIN_URL = "https://example.net/";
const TEST_LOGIN_STATUS_BASE =
TEST_URL +
"browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status=";
const TEST_LOGIN_STATUS_XORIGIN_BASE =
TEST_XORIGIN_URL +
"browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status=";
/**
* Perform a test with a function that should change a page's login status.
*
* This function opens a new foreground tab, then calls stepFn with two arguments:
* the Browser of the new tab and the value that should be set as the login status.
* This repeats for various values of the header, making sure the status is correct
* after each instance.
*
* @param {Function(Browser, string) => Promise} stepFn - The function to update login status,
* @param {string} - An optional description describing the test case, used in assertion descriptions.
*/
async function login_logout_sequence(stepFn, desc = "") {
// Open a test page, get its principal
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
const principal = gBrowser.contentPrincipal;
// Make sure we don't have a starting permission
let permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.UNKNOWN_ACTION,
"Permission correctly not initialized in test of " + desc
);
// Try using a bad value for the argument
await stepFn(tab.linkedBrowser, "should-reject");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.UNKNOWN_ACTION,
"Permission not altered by bad enum value in test of " + desc
);
// Try logging in
await stepFn(tab.linkedBrowser, "logged-in");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.ALLOW_ACTION,
"Permission stored correcty for `logged-in` in test of " + desc
);
// Try logging out
await stepFn(tab.linkedBrowser, "logged-out");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.DENY_ACTION,
"Permission stored correcty for `logged-out` in test of " + desc
);
// Try using a bad value for the argument, after it's already been set
await stepFn(tab.linkedBrowser, "should-reject");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.DENY_ACTION,
"Permission not altered by bad enum value in test of " + desc
);
// Try logging in again
await stepFn(tab.linkedBrowser, "logged-in");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.ALLOW_ACTION,
"Permission stored correcty for `logged-in` in test of " + desc
);
// Make sure we don't have any extra permissinons laying about
let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]);
Assert.equal(
permissions.length,
1,
"One permission must be left after all modifications in test of " + desc
);
// Clear the permission
Services.perms.removeByType("self-reported-logged-in");
// Close tabs.
await BrowserTestUtils.removeTab(tab);
}
/**
* Perform a test with a function that should NOT change a page's login status.
*
* This function opens a new foreground tab, then calls stepFn with two arguments:
* the Browser of the new tab and the value that should be set as the login status.
* Then it makes sure that no permission has been set for the login status.
*
* @param {Function(Browser, string) => Promise} stepFn - The function to update login status,
* @param {string} - An optional description describing the test case, used in assertion descriptions.
*/
async function login_doesnt_work(stepFn, desc = "") {
// Open a test page, get its principal
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
const principal = gBrowser.contentPrincipal;
// Make sure we don't have a starting permission
let permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.UNKNOWN_ACTION,
"Permission correctly not initialized in test of " + desc
);
// Try logging in
await stepFn(tab.linkedBrowser, "logged-in");
permission = Services.perms.testPermissionFromPrincipal(
principal,
"self-reported-logged-in"
);
Assert.equal(
permission,
Services.perms.UNKNOWN_ACTION,
"Permission not set for `logged-in` in test of " + desc
);
// Make sure we don't have any extra permissinons laying about
let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]);
Assert.equal(permissions.length, 0, "No permission set in test of " + desc);
// Clear the permission
Services.perms.removeByType("self-reported-logged-in");
// Close tabs.
await BrowserTestUtils.removeTab(tab);
}
// Function that we can use to set the status and return with a string for the different possible outcomes
async function setStatusInContent(status) {
try {
let result = await content.navigator.login.setStatus(status);
if (result === undefined) {
return "resolved with undefined";
}
return "resolved with defined";
} catch (err) {
if (err.name == "TypeError") {
return "rejected with TypeError";
}
if (err.name == "SecurityError") {
return "rejected with SecurityError";
}
return "rejected with other error";
}
}
add_task(async function test_logiStatus_js() {
let setLoginStatusInJavascript = async function (browser, value) {
let loginResult = await SpecialPowers.spawn(
browser,
[value],
setStatusInContent
);
if (value == "logged-in" || value == "logged-out") {
Assert.equal(
"resolved with undefined",
loginResult,
"Successful call resolves with `undefined`"
);
} else {
Assert.equal(
loginResult,
"rejected with TypeError",
"Unsuccessful JS call rejects with TypeError"
);
}
};
await login_logout_sequence(setLoginStatusInJavascript, "javascript API");
});
add_task(async function test_loginStatus_js_frame() {
let setLoginStatusInSubframeJavascript = async function (browser, value) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
let loginResult = await SpecialPowers.spawn(
iframeBC,
[value],
setStatusInContent
);
if (value == "logged-in" || value == "logged-out") {
Assert.equal(
"resolved with undefined",
loginResult,
"Successful call resolves with `undefined`"
);
} else {
Assert.equal(
loginResult,
"rejected with TypeError",
"Unsuccessful JS call rejects with TypeError"
);
}
};
await login_logout_sequence(
setLoginStatusInSubframeJavascript,
"javascript API"
);
});
add_task(async function test_loginStatus_js_xorigin_frame() {
let setLoginStatusInSubframeJavascript = async function (browser, value) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_XORIGIN_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
let loginResult = await SpecialPowers.spawn(
iframeBC,
[value],
setStatusInContent
);
if (value == "logged-in" || value == "logged-out") {
Assert.equal(
loginResult,
"rejected with SecurityError",
"Cross origin JS call with correct enum rejects with SecurityError"
);
} else {
Assert.equal(
loginResult,
"rejected with TypeError",
"Unsuccessful JS call rejects with TypeError"
);
}
};
await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API");
});
add_task(async function test_loginStatus_js_xorigin_ancestor_frame() {
let setLoginStatusInSubframeJavascript = async function (browser, value) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_XORIGIN_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
const innerIframeBC = await SpecialPowers.spawn(
iframeBC,
[TEST_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
let loginResult = await SpecialPowers.spawn(
innerIframeBC,
[value],
setStatusInContent
);
if (value == "logged-in" || value == "logged-out") {
Assert.equal(
loginResult,
"rejected with SecurityError",
"Cross origin JS call with correct enum rejects with SecurityError"
);
} else {
Assert.equal(
loginResult,
"rejected with TypeError",
"Unsuccessful JS call rejects with TypeError"
);
}
};
await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API");
});
add_task(async function test_login_logout_document_headers() {
let setLoginStatusInDocumentHeader = async function (browser, value) {
let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_URL);
BrowserTestUtils.startLoadingURIString(
browser,
TEST_LOGIN_STATUS_BASE + value
);
await loaded;
};
await login_logout_sequence(
setLoginStatusInDocumentHeader,
"document redirect"
);
});
add_task(async function test_loginStatus_subresource_headers() {
let setLoginStatusViaHeader = async function (browser, value) {
await SpecialPowers.spawn(
browser,
[TEST_LOGIN_STATUS_BASE + value],
async function (url) {
await content.fetch(url);
}
);
};
await login_logout_sequence(setLoginStatusViaHeader, "subresource header");
});
add_task(async function test_loginStatus_xorigin_subresource_headers() {
let setLoginStatusViaHeader = async function (browser, value) {
await SpecialPowers.spawn(
browser,
[TEST_LOGIN_STATUS_XORIGIN_BASE + value],
async function (url) {
await content.fetch(url, { mode: "no-cors" });
}
);
};
await login_doesnt_work(
setLoginStatusViaHeader,
"xorigin subresource header"
);
});
add_task(
async function test_loginStatus_xorigin_subdocument_subresource_headers() {
let setLoginStatusViaSubdocumentSubresource = async function (
browser,
value
) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_XORIGIN_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
await SpecialPowers.spawn(
iframeBC,
[TEST_LOGIN_STATUS_BASE + value],
async function (url) {
await content.fetch(url, { mode: "no-cors" });
}
);
};
await login_doesnt_work(
setLoginStatusViaSubdocumentSubresource,
"xorigin subresource header in xorigin frame"
);
}
);
add_task(
async function test_loginStatus_xorigin_subdocument_xorigin_subresource_headers() {
let setLoginStatusViaSubdocumentSubresource = async function (
browser,
value
) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_XORIGIN_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
await SpecialPowers.spawn(
iframeBC,
[TEST_LOGIN_STATUS_XORIGIN_BASE + value],
async function (url) {
await content.fetch(url, { mode: "no-cors" });
}
);
};
await login_doesnt_work(
setLoginStatusViaSubdocumentSubresource,
"xorigin subresource header in xorigin frame"
);
}
);
add_task(async function test_loginStatus_subdocument_subresource_headers() {
let setLoginStatusViaSubdocumentSubresource = async function (
browser,
value
) {
const iframeBC = await SpecialPowers.spawn(
browser,
[TEST_URL],
async url => {
const iframe = content.document.createElement("iframe");
await new Promise(resolve => {
iframe.addEventListener("load", resolve, { once: true });
iframe.src = url;
content.document.body.appendChild(iframe);
});
return iframe.browsingContext;
}
);
await SpecialPowers.spawn(
iframeBC,
[TEST_LOGIN_STATUS_BASE + value],
async function (url) {
await content.fetch(url, { mode: "no-cors" });
}
);
};
await login_logout_sequence(
setLoginStatusViaSubdocumentSubresource,
"subresource header in frame"
);
});

View File

@@ -1,16 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
function handleRequest(request, response) {
let params = new URLSearchParams(request.queryString);
if (params.has("status")) {
response.setHeader("Set-Login", params.get("status"), false);
}
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Location", "/", false);
response.setStatusLine(request.httpVersion, "307", "Temporary Redirect");
response.write("<!DOCTYPE html>");
}

View File

@@ -1,5 +1,6 @@
{
"accounts_endpoint": "https://example.net/browser/dom/credentialmanagement/identity/tests/browser/server_accounts.json",
"client_metadata_endpoint": "https://example.net/browser/dom/credentialmanagement/identity/tests/browser/server_metadata.json",
"id_assertion_endpoint": "https://example.net/browser/dom/credentialmanagement/identity/tests/browser/server_idtoken.json",
"disconnect_endpoint": "https://example.net/browser/dom/credentialmanagement/identity/tests/browser/server_disconnect.json"
}

View File

@@ -1,5 +1,6 @@
{
"accounts_endpoint": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_accounts.json",
"client_metadata_endpoint": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json",
"id_assertion_endpoint": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_idtoken.json",
"disconnect_endpoint": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_disconnect_intentionally_missing.json"
}

View File

@@ -0,0 +1,4 @@
{
"privacy_policy_url": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt",
"terms_of_service_url": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt"
}

View File

@@ -0,0 +1,2 @@
Content-Type: application/json
Access-Control-Allow-Origin: *

View File

@@ -22,6 +22,8 @@ support-files = [
"/.well-known/web-identity^headers^",
"server_manifest.sjs",
"server_manifest_wrong_provider_in_manifest.sjs",
"server_metadata.json",
"server_metadata.json^headers^",
"server_simple_accounts.sjs",
"server_simple_idtoken.sjs",
"server_no_accounts_accounts.sjs",

View File

@@ -32,6 +32,8 @@ function handleRequest(request, response) {
let content = {
accounts_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_TESTNAME_accounts.sjs",
client_metadata_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_metadata.json",
id_assertion_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_TESTNAME_idtoken.sjs",
};

View File

@@ -8,6 +8,8 @@ function handleRequest(request, response) {
let content = {
accounts_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_accounts.sjs",
client_metadata_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_metadata.sjs",
id_assertion_endpoint:
"https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/server_simple_idtoken.sjs",
};

View File

@@ -0,0 +1,4 @@
{
"privacy_policy_url": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt",
"terms_of_service_url": "https://example.net/tests/dom/credentialmanagement/identity/tests/mochitest/null.txt"
}

View File

@@ -0,0 +1,2 @@
Content-Type: application/json
Access-Control-Allow-Origin: *

View File

@@ -40,7 +40,6 @@ using mozilla::dom::IdentityCredentialDisconnectOptions from "mozilla/dom/Identi
using mozilla::dom::IdentityCredentialInit from "mozilla/dom/IdentityCredentialBinding.h";
using mozilla::dom::IdentityCredentialRequestOptions from "mozilla/dom/IdentityCredentialBinding.h";
using mozilla::dom::IdentityLoginTargetType from "mozilla/dom/IdentityCredentialBinding.h";
using mozilla::dom::LoginStatus from "mozilla/dom/LoginStatusBinding.h";
namespace mozilla {
namespace dom {
@@ -219,9 +218,6 @@ parent:
async DisconnectIdentityCredential(IdentityCredentialDisconnectOptions aOptions) returns (nsresult rv);
async PreventSilentAccess() returns (nsresult rv);
// Indicate that the page has reported itself as logged in
async SetLoginStatus(LoginStatus foo) returns (nsresult rv);
async GetStorageAccessPermission(bool aIncludeIdentityCredential) returns(uint32_t permission_action);

View File

@@ -26,7 +26,6 @@
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/IdentityCredential.h"
#include "mozilla/dom/MediaController.h"
#include "mozilla/dom/NavigatorLogin.h"
#include "mozilla/dom/WebAuthnTransactionParent.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/ChromeUtils.h"
@@ -1504,18 +1503,6 @@ IPCResult WindowGlobalParent::RecvPreventSilentAccess(
return IPC_OK();
}
mozilla::ipc::IPCResult WindowGlobalParent::RecvSetLoginStatus(
LoginStatus aStatus, const SetLoginStatusResolver& aResolver) {
nsIPrincipal* principal = DocumentPrincipal();
if (!principal) {
aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return IPC_OK();
}
nsresult rv = NavigatorLogin::SetLoginStatus(principal, aStatus);
aResolver(rv);
return IPC_OK();
}
IPCResult WindowGlobalParent::RecvGetStorageAccessPermission(
bool aIncludeIdentityCredential,
GetStorageAccessPermissionResolver&& aResolve) {

View File

@@ -328,9 +328,6 @@ class WindowGlobalParent final : public WindowContext,
mozilla::ipc::IPCResult RecvDisconnectIdentityCredential(
const IdentityCredentialDisconnectOptions& aOptions,
const DisconnectIdentityCredentialResolver& aResolver);
mozilla::ipc::IPCResult RecvSetLoginStatus(
LoginStatus aStatus, const SetLoginStatusResolver& aResolver);
mozilla::ipc::IPCResult RecvPreventSilentAccess(
const PreventSilentAccessResolver& aResolver);

View File

@@ -937,8 +937,6 @@ let interfaceNamesInGlobalScope = [
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "Navigator", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
"NavigatorLogin",
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "NetworkInformation", insecureContext: true, disabled: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "Node", insecureContext: true },

View File

@@ -91,9 +91,7 @@ dictionary IdentityProviderBranding {
[GenerateInit, GenerateConversionToJS]
dictionary IdentityProviderAPIConfig {
required UTF8String accounts_endpoint;
// We do not want to gather consent for identity providers, so we
// omit this requirement and its use: https://github.com/w3c-fedid/FedCM/issues/703
// required UTF8String client_metadata_endpoint;
required UTF8String client_metadata_endpoint;
required UTF8String id_assertion_endpoint;
UTF8String disconnect_endpoint;
IdentityProviderBranding branding;

View File

@@ -1,19 +0,0 @@
/* -*- 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://w3c-fedid.github.io/login-status/#login-status-javascript
*/
enum LoginStatus {
"logged-in",
"logged-out",
};
[Exposed=Window, SecureContext]
interface NavigatorLogin {
[NewObject]
Promise<undefined> setStatus(LoginStatus status);
};

View File

@@ -398,9 +398,3 @@ partial interface Navigator {
[SameObject, Trial="PrivateAttributionV2"]
readonly attribute PrivateAttribution privateAttribution;
};
// https://w3c-fedid.github.io/login-status/#login-status-javascript
[SecureContext]
partial interface Navigator {
[SameObject] readonly attribute NavigatorLogin login;
};

View File

@@ -725,7 +725,6 @@ WEBIDL_FILES = [
"Location.webidl",
"Lock.webidl",
"LockManager.webidl",
"LoginStatus.webidl",
"MathMLElement.webidl",
"MediaCapabilities.webidl",
"MediaDebugInfo.webidl",

View File

@@ -90,7 +90,6 @@ HTTP_ATOM(Server, "Server")
HTTP_ATOM(Server_Timing, "Server-Timing")
HTTP_ATOM(Service_Worker_Allowed, "Service-Worker-Allowed")
HTTP_ATOM(Set_Cookie, "Set-Cookie")
HTTP_ATOM(Set_Login, "Set-Login")
HTTP_ATOM(Status_URI, "Status-URI")
HTTP_ATOM(Strict_Transport_Security, "Strict-Transport-Security")
HTTP_ATOM(TE, "TE")

View File

@@ -12,7 +12,6 @@
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/NavigatorLogin.h"
#include "mozilla/glean/AntitrackingMetrics.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
@@ -5995,32 +5994,6 @@ nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
}
}
// if we have a Set-Login header, we should try to handle it here
nsAutoCString setLogin;
if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Login, setLogin))) {
bool isDocument = mLoadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_DOCUMENT;
if (isDocument) {
auto ssm = nsContentUtils::GetSecurityManager();
if (ssm) {
nsCOMPtr<nsIPrincipal> documentPrincipal;
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
this, getter_AddRefs(documentPrincipal));
dom::NavigatorLogin::SetLoginStatus(documentPrincipal, setLogin);
}
} else {
bool inThirdPartyContext = mLoadInfo->GetIsInThirdPartyContext();
nsIPrincipal* loadingPrincipal = mLoadInfo->GetLoadingPrincipal();
if (loadingPrincipal) {
bool isSameOriginToLoadingPrincipal =
loadingPrincipal->IsSameOrigin(mURI);
if (!inThirdPartyContext && isSameOriginToLoadingPrincipal) {
dom::NavigatorLogin::SetLoginStatus(loadingPrincipal, setLogin);
}
}
}
}
if (NS_WARN_IF(!mRedirectURI)) {
LOG(("Invalid redirect URI after performaing query string stripping"));
return NS_ERROR_FAILURE;

View File

@@ -311,6 +311,178 @@ export class IdentityCredentialPromptService {
});
}
/**
* Ask the user, using a PopupNotification, to approve or disapprove of the policies of the Identity Provider.
* @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()
* @param {IdentityProviderConfig} identityProvider - The Identity Provider that the user has selected to use
* @param {IdentityProviderAPIConfig} identityManifest - The Identity Provider that the user has selected to use's manifest
* @param {IdentityCredentialMetadata} identityCredentialMetadata - The metadata displayed to the user
* @returns {Promise<bool>} A boolean representing the user's acceptance of the metadata.
*/
async showPolicyPrompt(
browsingContext,
identityProvider,
identityManifest,
identityCredentialMetadata
) {
// For testing only.
if (lazy.SELECT_FIRST_IN_UI_LISTS) {
return Promise.resolve(true);
}
if (
!identityCredentialMetadata ||
!identityCredentialMetadata.privacy_policy_url ||
!identityCredentialMetadata.terms_of_service_url
) {
return Promise.resolve(true);
}
let iconResult = await this.loadIconFromManifest(
identityManifest,
BEST_HEADER_ICON_SIZE,
"chrome://global/skin/icons/defaultFavicon.svg"
);
const providerName = identityManifest?.branding?.name;
return new Promise(function (resolve, reject) {
let browser = browsingContext.top.embedderElement;
if (!browser) {
reject();
return;
}
let providerURL = new URL(identityProvider.configURL);
let providerDisplayDomain = lazy.IDNService.convertToDisplayIDN(
providerURL.host
);
let currentBaseDomain =
browsingContext.currentWindowContext.documentPrincipal.baseDomain;
if (AppConstants.platform === "android") {
lazy.GeckoViewIdentityCredential.onShowPolicyPrompt(
browsingContext,
identityCredentialMetadata.privacy_policy_url,
identityCredentialMetadata.terms_of_service_url,
providerDisplayDomain,
currentBaseDomain,
iconResult,
resolve,
reject
);
} else {
// Localize the description
// Bug 1797154 - Convert localization calls to use the async formatValues.
let localization = new Localization(
["browser/identityCredentialNotification.ftl"],
true
);
let [accept, cancel] = localization.formatMessagesSync([
{ id: "identity-credential-accept-button" },
{ id: "identity-credential-cancel-button" },
]);
let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
let cancelKey = cancel.attributes.find(
x => x.name == "accesskey"
).value;
let acceptLabel = accept.attributes.find(x => x.name == "label").value;
let acceptKey = accept.attributes.find(
x => x.name == "accesskey"
).value;
let title = localization.formatValueSync(
"identity-credential-policy-title",
{
provider: providerName || providerDisplayDomain,
}
);
if (iconResult) {
let headerIcon = browser.ownerDocument.getElementsByClassName(
"identity-credential-header-icon"
)[0];
headerIcon.setAttribute("src", iconResult);
}
const headerText = browser.ownerDocument.getElementById(
"identity-credential-header-text"
);
headerText.textContent = title;
let privacyPolicyAnchor = browser.ownerDocument.getElementById(
"identity-credential-privacy-policy"
);
privacyPolicyAnchor.href =
identityCredentialMetadata.privacy_policy_url;
let termsOfServiceAnchor = browser.ownerDocument.getElementById(
"identity-credential-terms-of-service"
);
termsOfServiceAnchor.href =
identityCredentialMetadata.terms_of_service_url;
// Populate the content of the policy panel
let description = browser.ownerDocument.getElementById(
"identity-credential-policy-explanation"
);
browser.ownerDocument.l10n.setAttributes(
description,
"identity-credential-policy-description",
{
host: currentBaseDomain,
provider: providerDisplayDomain,
}
);
// Construct the necessary arguments for notification behavior
let options = {
hideClose: true,
eventCallback: (topic, nextRemovalReason, isCancel) => {
if (topic == "removed" && isCancel) {
reject();
}
},
};
let mainAction = {
label: acceptLabel,
accessKey: acceptKey,
callback(_event) {
resolve(true);
},
};
let secondaryActions = [
{
label: cancelLabel,
accessKey: cancelKey,
callback(_event) {
resolve(false);
},
},
];
// Show the popup
let ownerDocument = browser.ownerDocument;
ownerDocument.getElementById("identity-credential-provider").hidden =
true;
ownerDocument.getElementById("identity-credential-policy").hidden =
false;
ownerDocument.getElementById("identity-credential-account").hidden =
true;
ownerDocument.getElementById("identity-credential-header").hidden =
false;
browser.ownerGlobal.PopupNotifications.show(
browser,
"identity-credential",
"",
"identity-credential-notification-icon",
mainAction,
secondaryActions,
options
);
}
});
}
/**
* Ask the user, using a PopupNotification, to select an account from a provided list.
* @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()

View File

@@ -11,6 +11,9 @@ interface nsIIdentityCredentialPromptService : nsISupports {
// Display to the user an interface to choose from among the identity providers listed
// Resolves with an index referring to one pair of the elements of the lists.
Promise showProviderPrompt(in BrowsingContext browsingContext, in jsval identityProviders, in jsval identityManifests);
// Display to the user an interface to approve (or disapprove) of the terms of service for
// the identity provider when used on the current site.
Promise showPolicyPrompt(in BrowsingContext browsingContext, in jsval identityProvider, in jsval identityManifest, in jsval identityClientMetadata);
// Display to the user an interface to choose from among the accounts listed with the information of the provider.
// Resolves with an index referring to one of the elements of the list.
Promise showAccountListPrompt(in BrowsingContext browsingContext, in jsval accountList, in jsval identityProvider, in jsval identityManifest);

View File

@@ -15,4 +15,6 @@ support-files = ["custom.svg"]
["browser_credential_chooser.js"]
["browser_policy_dialog.js"]
["browser_provider_dialog.js"]

View File

@@ -0,0 +1,204 @@
/* 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/. */
"use strict";
XPCOMUtils.defineLazyServiceGetter(
this,
"IdentityCredentialPromptService",
"@mozilla.org/browser/identitycredentialpromptservice;1",
"nsIIdentityCredentialPromptService"
);
const TEST_URL = "https://example.com/";
// Test that a policy dialog does not appear when no policies are given
add_task(async function test_policy_dialog_empty() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
let prompt = IdentityCredentialPromptService.showPolicyPrompt(
tab.linkedBrowser.browsingContext,
{
configURL: "https://idp.example/",
clientId: "123",
},
{
accounts_endpoint: "",
client_metadata_endpoint: "",
id_assertion_endpoint: "",
},
{} // No policies!
);
// Make sure we resolve with true without interaction
let value = await prompt;
is(value, true, "Automatically accept the missing policies");
// Close tab
await BrowserTestUtils.removeTab(tab);
});
// Make sure that a policy dialog shows up when we have policies to show.
// Also test the accept path.
add_task(async function test_policy_dialog() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
// Show a prompt- the operative argument is the last one
let prompt = IdentityCredentialPromptService.showPolicyPrompt(
tab.linkedBrowser.browsingContext,
{
configURL: "https://idp.example/",
clientId: "123",
},
{
accounts_endpoint: "",
client_metadata_endpoint: "",
id_assertion_endpoint: "",
branding: {
background_color: "0x6200ee",
color: "0xffffff",
icons: [
{
size: 256,
url: "https://example.net/browser/toolkit/components/credentialmanagement/tests/browser/custom.svg",
},
],
name: "demo ip",
},
},
{
privacy_policy_url: "https://idp.example/privacy-policy.html",
terms_of_service_url: "https://idp.example/terms-of-service.html",
}
);
// Make sure the popup shows up
await popupShown;
let popupHiding = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphiding"
);
// Validate the contents of the popup
let document = tab.linkedBrowser.browsingContext.topChromeWindow.document;
let description = document.getElementById(
"identity-credential-policy-explanation"
);
ok(
description.textContent.includes("idp.example"),
"IDP domain in the policy prompt text"
);
ok(
description.textContent.includes("example.com"),
"RP domain in the policy prompt text"
);
ok(
description.textContent.includes("Privacy Policy"),
"Link to the privacy policy in the policy prompt text"
);
ok(
description.textContent.includes("Terms of Service"),
"Link to the ToS in the policy prompt text"
);
let title = document.getElementById("identity-credential-header-text");
ok(
title.textContent.includes("demo ip"),
"IDP domain in the policy prompt header as business short name"
);
const headerIcon = document.getElementsByClassName(
"identity-credential-header-icon"
)[0];
ok(BrowserTestUtils.isVisible(headerIcon), "Header Icon is showing");
ok(
headerIcon.src.startsWith(
"data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTS42MjUgMTNhLjYyNS42MjUgMCAwIDEgMC0xLjI1bDMuMjUgMEE0Ljg4IDQuODggMCAwIDAgOC43NSA2Ljg3NWwwLS4yNWEuNjI1LjYyNSAwIDAgMSAxLjI1IDBsMCAuMjVBNi4xMzIgNi4xMzIgMCAwIDEgMy44NzUgMTNsLTMuMjUgMHoiLz"
),
"The header icon matches the icon resource from manifest"
);
// Accept the policies
document
.getElementsByClassName("popup-notification-primary-button")[0]
.click();
// Make sure the call to the propmt resolves with true
let value = await prompt;
is(value, true, "User clicking accept resolves with true");
// Wait for the prompt to go away
await popupHiding;
// Close tab
await BrowserTestUtils.removeTab(tab);
});
// Test that rejecting the policies works
add_task(async function test_policy_reject() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
// Show the same prompt with policies
let prompt = IdentityCredentialPromptService.showPolicyPrompt(
tab.linkedBrowser.browsingContext,
{
configURL: "https://idp.example/",
clientId: "123",
},
{
accounts_endpoint: "",
client_metadata_endpoint: "",
id_assertion_endpoint: "",
},
{
privacy_policy_url: "https://idp.example/privacy-policy.html",
terms_of_service_url: "https://idp.example/terms-of-service.html",
}
);
// Wait for the prompt to show up
await popupShown;
let popupHiding = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphiding"
);
let document = tab.linkedBrowser.browsingContext.topChromeWindow.document;
let title = document.getElementById("identity-credential-header-text");
ok(
title.textContent.includes("idp.example"),
"IDP domain in the policy prompt header as domain"
);
// Click reject.
document
.getElementsByClassName("popup-notification-secondary-button")[0]
.click();
// Make sure the prompt call accepts with an indication of the user's reject choice.
let value = await prompt;
is(value, false, "User clicking reject causes the promise to resolve(false)");
// Wait for the popup to go away.
await popupHiding;
// Close tab.
await BrowserTestUtils.removeTab(tab);
});