blob: 4d660191e5cad24c5bd6b747e783b8016748586f [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/credentialmanager/CredentialsContainer.h"
#include <memory>
#include <utility>
#include "bindings/core/v8/V8BindingForTesting.h"
#include "bindings/core/v8/V8GCController.h"
#include "core/dom/Document.h"
#include "core/frame/FrameTestHelpers.h"
#include "core/frame/LocalFrame.h"
#include "modules/credentialmanager/CredentialManagerProxy.h"
#include "modules/credentialmanager/CredentialRequestOptions.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "platform/testing/UnitTestHelpers.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/wtf/Functional.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/modules/credentialmanager/credential_manager.mojom-blink.h"
namespace blink {
namespace {
class MockCredentialManager
: public ::password_manager::mojom::blink::CredentialManager {
WTF_MAKE_NONCOPYABLE(MockCredentialManager);
public:
MockCredentialManager() : binding_(this) {}
~MockCredentialManager() {}
void Bind(
::password_manager::mojom::blink::CredentialManagerRequest request) {
binding_.Bind(std::move(request));
}
void WaitForConnectionError() {
if (!binding_.is_bound())
return;
binding_.set_connection_error_handler(WTF::Bind(&testing::ExitRunLoop));
testing::EnterRunLoop();
}
void WaitForCallToGet() {
if (get_callback_)
return;
testing::EnterRunLoop();
}
void InvokeGetCallback() {
EXPECT_TRUE(binding_.is_bound());
auto info = password_manager::mojom::blink::CredentialInfo::New();
info->type = password_manager::mojom::blink::CredentialType::EMPTY;
info->federation = SecurityOrigin::CreateUnique();
std::move(get_callback_)
.Run(password_manager::mojom::blink::CredentialManagerError::SUCCESS,
std::move(info));
}
protected:
virtual void Store(
password_manager::mojom::blink::CredentialInfoPtr credential,
StoreCallback callback) {}
virtual void PreventSilentAccess(PreventSilentAccessCallback callback) {}
virtual void Get(
password_manager::mojom::blink::CredentialMediationRequirement mediation,
bool include_passwords,
const WTF::Vector<::blink::KURL>& federations,
GetCallback callback) {
get_callback_ = std::move(callback);
testing::ExitRunLoop();
}
private:
mojo::Binding<::password_manager::mojom::blink::CredentialManager> binding_;
GetCallback get_callback_;
};
class CredentialManagerTestingContext {
STACK_ALLOCATED();
public:
CredentialManagerTestingContext(
MockCredentialManager* mock_credential_manager) {
dummy_context_.GetDocument().SetSecurityOrigin(
SecurityOrigin::CreateFromString("https://example.test"));
dummy_context_.GetDocument().SetSecureContextStateForTesting(
SecureContextState::kSecure);
service_manager::InterfaceProvider::TestApi test_api(
&dummy_context_.GetFrame().GetInterfaceProvider());
test_api.SetBinderForName(
::password_manager::mojom::blink::CredentialManager::Name_,
WTF::BindRepeating(
[](MockCredentialManager* mock_credential_manager,
mojo::ScopedMessagePipeHandle handle) {
mock_credential_manager->Bind(
::password_manager::mojom::blink::CredentialManagerRequest(
std::move(handle)));
},
WTF::Unretained(mock_credential_manager)));
}
Document* Document() { return &dummy_context_.GetDocument(); }
LocalFrame* Frame() { return &dummy_context_.GetFrame(); }
ScriptState* ScriptState() { return dummy_context_.GetScriptState(); }
private:
V8TestingScope dummy_context_;
};
} // namespace
// The completion callbacks for pending mojom::CredentialManager calls each own
// a persistent handle to a ScriptPromiseResolver instance. Ensure that if the
// document is destored while a call is pending, it can still be freed up.
TEST(CredentialsContainerTest, PendingGetRequest_NoGCCycles) {
MockCredentialManager mock_credential_manager;
WeakPersistent<Document> weak_document;
{
CredentialManagerTestingContext context(&mock_credential_manager);
weak_document = context.Document();
CredentialsContainer::Create()->get(context.ScriptState(),
CredentialRequestOptions());
mock_credential_manager.WaitForCallToGet();
}
V8GCController::CollectAllGarbageForTesting(v8::Isolate::GetCurrent());
ThreadState::Current()->CollectAllGarbage();
ASSERT_EQ(nullptr, weak_document.Get());
mock_credential_manager.InvokeGetCallback();
mock_credential_manager.WaitForConnectionError();
}
// If the document is detached before the request is resolved, the promise
// should be left unresolved, and there should be no crashes.
TEST(CredentialsContainerTest,
PendingGetRequest_NoCrashOnResponseAfterDocumentShutdown) {
MockCredentialManager mock_credential_manager;
CredentialManagerTestingContext context(&mock_credential_manager);
auto* proxy = CredentialManagerProxy::From(context.Document());
auto promise = CredentialsContainer::Create()->get(
context.ScriptState(), CredentialRequestOptions());
mock_credential_manager.WaitForCallToGet();
context.Document()->Shutdown();
mock_credential_manager.InvokeGetCallback();
proxy->FlushCredentialManagerConnectionForTesting();
EXPECT_EQ(v8::Promise::kPending,
promise.V8Value().As<v8::Promise>()->State());
}
} // namespace blink