blob: d844f75896f2c993df11ac326a89734aa950ae1c [file] [log] [blame]
// Copyright 2014 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 <stddef.h>
#include <stdint.h>
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/test/bind_test_util.h"
#include "content/browser/frame_host/clipboard_host_impl.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/test/clipboard_test_util.h"
#include "ui/base/clipboard/test/test_clipboard.h"
#include "ui/gfx/skia_util.h"
namespace content {
namespace {
// A ClipboardHostImpl that mocks out the dependency on RenderFrameHost.
class ClipboardHostImplNoRFH : public ClipboardHostImpl {
public:
ClipboardHostImplNoRFH(
mojo::PendingReceiver<blink::mojom::ClipboardHost> receiver)
: ClipboardHostImpl(/*render_frame_host=*/nullptr, std::move(receiver)) {}
void StartIsPasteAllowedRequest(uint64_t seqno,
const ui::ClipboardFormatType& data_type,
std::string data) override {}
void CompleteRequest(uint64_t seqno) {
FinishPasteIfAllowed(seqno, ClipboardHostImpl::ClipboardPasteAllowed(true));
}
using ClipboardHostImpl::CleanupObsoleteRequests;
using ClipboardHostImpl::is_paste_allowed_requests_for_testing;
using ClipboardHostImpl::kIsPasteAllowedRequestTooOld;
using ClipboardHostImpl::PerformPasteIfAllowed;
};
} // namespace
class ClipboardHostImplTest : public ::testing::Test {
protected:
ClipboardHostImplTest()
: clipboard_(ui::TestClipboard::CreateForCurrentThread()) {
ClipboardHostImpl::Create(/*render_frame_host=*/nullptr,
remote_.BindNewPipeAndPassReceiver());
}
~ClipboardHostImplTest() override {
ui::Clipboard::DestroyClipboardForCurrentThread();
}
mojo::Remote<blink::mojom::ClipboardHost>& mojo_clipboard() {
return remote_;
}
ui::Clipboard* system_clipboard() { return clipboard_; }
private:
const BrowserTaskEnvironment task_environment_;
mojo::Remote<blink::mojom::ClipboardHost> remote_;
ui::Clipboard* const clipboard_;
};
// Test that it actually works.
TEST_F(ClipboardHostImplTest, SimpleImage) {
SkBitmap bitmap;
bitmap.allocN32Pixels(3, 2);
bitmap.eraseARGB(255, 0, 255, 0);
mojo_clipboard()->WriteImage(bitmap);
uint64_t sequence_number =
system_clipboard()->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);
mojo_clipboard()->CommitWrite();
base::RunLoop().RunUntilIdle();
EXPECT_NE(sequence_number, system_clipboard()->GetSequenceNumber(
ui::ClipboardBuffer::kCopyPaste));
EXPECT_FALSE(system_clipboard()->IsFormatAvailable(
ui::ClipboardFormatType::GetPlainTextType(),
ui::ClipboardBuffer::kCopyPaste));
EXPECT_TRUE(system_clipboard()->IsFormatAvailable(
ui::ClipboardFormatType::GetBitmapType(),
ui::ClipboardBuffer::kCopyPaste));
SkBitmap actual = ui::clipboard_test_util::ReadImage(system_clipboard());
EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, actual));
}
TEST_F(ClipboardHostImplTest, ReentrancyInSyncCall) {
// Due to the nature of this test, it's somewhat racy. On some platforms
// (currently Linux), reading the clipboard requires running a nested message
// loop. During that time, it's possible to send a bad message that causes the
// message pipe to be closed. Make sure ClipboardHostImpl doesn't UaF |this|
// after exiting the nested message loop.
// ReadText() is a sync method, so normally, one wouldn't call this method
// directly. These are not normal times though...
mojo_clipboard()->ReadText(ui::ClipboardBuffer::kCopyPaste,
base::DoNothing());
// Now purposely write a raw message which (hopefully) won't deserialize to
// anything valid. The receiver side should still be in the midst of
// dispatching ReadText() when Mojo attempts to deserialize this message,
// which should cause a validation failure that signals a connection error.
base::RunLoop run_loop;
mojo::WriteMessageRaw(mojo_clipboard().internal_state()->handle(), "moo", 3,
nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
mojo_clipboard().set_disconnect_handler(run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(mojo_clipboard().is_connected());
}
TEST_F(ClipboardHostImplTest, IsPasteAllowedRequest_AddCallback) {
ClipboardHostImpl::IsPasteAllowedRequest request;
int count = 0;
// First call to AddCallback should return true, the next false.
EXPECT_TRUE(request.AddCallback(base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
})));
EXPECT_FALSE(request.AddCallback(base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
})));
// In both cases, the callbacks should noy be called since the request is
// not complete.
EXPECT_EQ(0, count);
}
TEST_F(ClipboardHostImplTest, IsPasteAllowedRequest_Complete) {
ClipboardHostImpl::IsPasteAllowedRequest request;
int count = 0;
// Add a callback. It should not fire right away.
request.AddCallback(base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
ASSERT_EQ(ClipboardHostImpl::ClipboardPasteAllowed(true), allowed);
}));
EXPECT_EQ(0, count);
// Complete the request. Callback should fire. Whether paste is allowed
// or not is not important.
request.Complete(ClipboardHostImpl::ClipboardPasteAllowed(true));
EXPECT_EQ(1, count);
// Adding a new callback after completion invokes it immediately.
request.AddCallback(base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
ASSERT_EQ(ClipboardHostImpl::ClipboardPasteAllowed(true), allowed);
}));
EXPECT_EQ(2, count);
}
TEST_F(ClipboardHostImplTest, IsPasteAllowedRequest_IsObsolete) {
ClipboardHostImpl::IsPasteAllowedRequest request;
// A request that is not too old is not obsolete, even if it has no callbacks.
EXPECT_FALSE(request.IsObsolete(
request.time() + ClipboardHostImpl::kIsPasteAllowedRequestTooOld / 2));
// A request that still has callbacks is not obsolete, even if older than
// "too old".
request.AddCallback(base::DoNothing());
EXPECT_FALSE(request.IsObsolete(
request.time() + ClipboardHostImpl::kIsPasteAllowedRequestTooOld +
base::TimeDelta::FromMicroseconds(1)));
// A request is obsolete once it is too old and has no callbacks.
// Whether paste is allowed or not is not important.
request.Complete(ClipboardHostImpl::ClipboardPasteAllowed(true));
EXPECT_TRUE(request.IsObsolete(
request.time() + ClipboardHostImpl::kIsPasteAllowedRequestTooOld +
base::TimeDelta::FromMicroseconds(1)));
}
class ClipboardHostImplScanTest : public ::testing::Test {
protected:
ClipboardHostImplScanTest()
: clipboard_(ui::TestClipboard::CreateForCurrentThread()),
fake_clipboard_host_impl_(remote_.BindNewPipeAndPassReceiver()) {}
~ClipboardHostImplScanTest() override {
ui::Clipboard::DestroyClipboardForCurrentThread();
}
ClipboardHostImplNoRFH* clipboard_host_impl() {
return &fake_clipboard_host_impl_;
}
BrowserTaskEnvironment* task_environment() { return &task_environment_; }
private:
BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
mojo::Remote<blink::mojom::ClipboardHost> remote_;
ui::Clipboard* const clipboard_;
ClipboardHostImplNoRFH fake_clipboard_host_impl_;
};
TEST_F(ClipboardHostImplScanTest, PerformPasteIfAllowed_EmptyData) {
int count = 0;
// When data is empty, the callback is invoked right away.
clipboard_host_impl()->PerformPasteIfAllowed(
1, ui::ClipboardFormatType::GetPlainTextType(), "",
base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
}));
EXPECT_EQ(
0u,
clipboard_host_impl()->is_paste_allowed_requests_for_testing().size());
EXPECT_EQ(1, count);
}
TEST_F(ClipboardHostImplScanTest, PerformPasteIfAllowed) {
int count = 0;
clipboard_host_impl()->PerformPasteIfAllowed(
1, ui::ClipboardFormatType::GetPlainTextType(), "data",
base::BindLambdaForTesting(
[&count](ClipboardHostImpl::ClipboardPasteAllowed allowed) {
++count;
}));
EXPECT_EQ(
1u,
clipboard_host_impl()->is_paste_allowed_requests_for_testing().size());
EXPECT_EQ(0, count);
// Completing the request invokes the callback. The request will
// remain pending until it is cleaned up.
clipboard_host_impl()->CompleteRequest(1);
EXPECT_EQ(
1u,
clipboard_host_impl()->is_paste_allowed_requests_for_testing().size());
EXPECT_EQ(1, count);
}
TEST_F(ClipboardHostImplScanTest, CleanupObsoleteScanRequests) {
// Perform a request and complete it.
clipboard_host_impl()->PerformPasteIfAllowed(
1, ui::ClipboardFormatType::GetPlainTextType(), "data",
base::DoNothing());
clipboard_host_impl()->CompleteRequest(1);
EXPECT_EQ(
1u,
clipboard_host_impl()->is_paste_allowed_requests_for_testing().size());
// Make sure an appropriate amount of time passes to make the request old.
// It should be cleaned up.
task_environment()->FastForwardBy(
ClipboardHostImplNoRFH::kIsPasteAllowedRequestTooOld +
base::TimeDelta::FromMicroseconds(1));
clipboard_host_impl()->CleanupObsoleteRequests();
EXPECT_EQ(
0u,
clipboard_host_impl()->is_paste_allowed_requests_for_testing().size());
}
} // namespace content