blob: f7efe9572f7b98f0afc0ea692b6e2a439c2b89bd [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/modules/webshare/navigator_share.h"
#include <memory>
#include <utility>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_file_property_bag.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_share_data.h"
#include "third_party/blink/renderer/core/fileapi/file.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
using mojom::blink::SharedFile;
using mojom::blink::SharedFilePtr;
using mojom::blink::ShareService;
// A mock ShareService used to intercept calls to the mojo methods.
class MockShareService : public ShareService {
public:
MockShareService() : error_(mojom::ShareError::OK) {}
~MockShareService() override = default;
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(
mojo::PendingReceiver<mojom::blink::ShareService>(std::move(handle)));
}
void set_error(mojom::ShareError value) { error_ = value; }
const WTF::String& title() const { return title_; }
const WTF::String& text() const { return text_; }
const KURL& url() const { return url_; }
const WTF::Vector<SharedFilePtr>& files() const { return files_; }
mojom::ShareError error() const { return error_; }
private:
void Share(const WTF::String& title,
const WTF::String& text,
const KURL& url,
WTF::Vector<SharedFilePtr> files,
ShareCallback callback) override {
title_ = title;
text_ = text;
url_ = url;
files_.clear();
files_.ReserveInitialCapacity(files.size());
for (const auto& entry : files) {
files_.push_back(entry->Clone());
}
std::move(callback).Run(error_);
}
mojo::Receiver<ShareService> receiver_{this};
WTF::String title_;
WTF::String text_;
KURL url_;
WTF::Vector<SharedFilePtr> files_;
mojom::ShareError error_;
};
class NavigatorShareTest : public testing::Test {
public:
NavigatorShareTest()
: holder_(std::make_unique<DummyPageHolder>()),
handle_scope_(GetScriptState()->GetIsolate()),
context_(GetScriptState()->GetContext()),
context_scope_(context_) {}
Document& GetDocument() { return holder_->GetDocument(); }
LocalFrame& GetFrame() { return holder_->GetFrame(); }
ScriptState* GetScriptState() const {
return ToScriptStateForMainWorld(&holder_->GetFrame());
}
void Share(const ShareData& share_data) {
LocalFrame::NotifyUserActivation(
&GetFrame(), mojom::UserActivationNotificationType::kTest);
Navigator* navigator = GetFrame().DomWindow()->navigator();
NonThrowableExceptionState exception_state;
ScriptPromise promise = NavigatorShare::share(GetScriptState(), *navigator,
&share_data, exception_state);
test::RunPendingTasks();
EXPECT_EQ(mock_share_service_.error() == mojom::ShareError::OK
? v8::Promise::kFulfilled
: v8::Promise::kRejected,
promise.V8Promise()->State());
}
MockShareService& mock_share_service() { return mock_share_service_; }
protected:
void SetUp() override {
GetFrame().Loader().CommitNavigation(
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), KURL("https://example.com")),
nullptr /* extra_data */);
test::RunPendingTasks();
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
ShareService::Name_,
WTF::BindRepeating(&MockShareService::Bind,
WTF::Unretained(&mock_share_service_)));
}
void TearDown() override {
// Remove the testing binder to avoid crashes between tests caused by
// MockShareService rebinding an already-bound Binding.
// See https://crbug.com/1010116 for more information.
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
ShareService::Name_, {});
}
public:
MockShareService mock_share_service_;
std::unique_ptr<DummyPageHolder> holder_;
v8::HandleScope handle_scope_;
v8::Local<v8::Context> context_;
v8::Context::Scope context_scope_;
};
TEST_F(NavigatorShareTest, ShareText) {
const String title = "Subject";
const String message = "Body";
const String url = "https://example.com/path?query#fragment";
ShareData share_data;
share_data.setTitle(title);
share_data.setText(message);
share_data.setUrl(url);
Share(share_data);
EXPECT_EQ(mock_share_service().title(), title);
EXPECT_EQ(mock_share_service().text(), message);
EXPECT_EQ(mock_share_service().url(), KURL(url));
EXPECT_EQ(mock_share_service().files().size(), 0U);
EXPECT_TRUE(
GetDocument().IsUseCounted(WebFeature::kWebShareSuccessfulWithoutFiles));
}
File* CreateSampleFile(ExecutionContext* context,
const String& file_name,
const String& content_type,
const String& file_contents) {
HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString> blob_parts;
blob_parts.push_back(ArrayBufferOrArrayBufferViewOrBlobOrUSVString());
blob_parts.back().SetUSVString(file_contents);
FilePropertyBag file_property_bag;
file_property_bag.setType(content_type);
return File::Create(context, blob_parts, file_name, &file_property_bag);
}
TEST_F(NavigatorShareTest, ShareFile) {
const String file_name = "chart.svg";
const String content_type = "image/svg+xml";
const String file_contents = "<svg></svg>";
HeapVector<Member<File>> files;
files.push_back(CreateSampleFile(ExecutionContext::From(GetScriptState()),
file_name, content_type, file_contents));
ShareData share_data;
share_data.setFiles(files);
Share(share_data);
EXPECT_EQ(mock_share_service().files().size(), 1U);
EXPECT_EQ(mock_share_service().files()[0]->name, file_name);
EXPECT_EQ(mock_share_service().files()[0]->blob->GetType(), content_type);
EXPECT_EQ(mock_share_service().files()[0]->blob->size(),
file_contents.length());
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kWebShareSuccessfulContainingFiles));
}
TEST_F(NavigatorShareTest, CancelShare) {
const String title = "Subject";
ShareData share_data;
share_data.setTitle(title);
mock_share_service().set_error(mojom::blink::ShareError::CANCELED);
Share(share_data);
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kWebShareUnsuccessfulWithoutFiles));
}
TEST_F(NavigatorShareTest, CancelShareWithFile) {
const String file_name = "counts.csv";
const String content_type = "text/csv";
const String file_contents = "1,2,3";
HeapVector<Member<File>> files;
files.push_back(CreateSampleFile(ExecutionContext::From(GetScriptState()),
file_name, content_type, file_contents));
ShareData share_data;
share_data.setFiles(files);
mock_share_service().set_error(mojom::blink::ShareError::CANCELED);
Share(share_data);
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kWebShareUnsuccessfulContainingFiles));
}
} // namespace blink