1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/webshare/navigator_share.h"
6
7 #include <memory>
8 #include <utility>
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
11 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
12 #include "third_party/blink/renderer/bindings/core/v8/v8_file_property_bag.h"
13 #include "third_party/blink/renderer/bindings/modules/v8/v8_share_data.h"
14 #include "third_party/blink/renderer/core/fileapi/file.h"
15 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
16 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
17 #include "third_party/blink/renderer/core/frame/local_frame.h"
18 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
19 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
20 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
21 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
22
23 namespace blink {
24
25 using mojom::blink::SharedFile;
26 using mojom::blink::SharedFilePtr;
27 using mojom::blink::ShareService;
28
29 // A mock ShareService used to intercept calls to the mojo methods.
30 class MockShareService : public ShareService {
31 public:
MockShareService()32 MockShareService() : error_(mojom::ShareError::OK) {}
33 ~MockShareService() override = default;
34
Bind(mojo::ScopedMessagePipeHandle handle)35 void Bind(mojo::ScopedMessagePipeHandle handle) {
36 receiver_.Bind(
37 mojo::PendingReceiver<mojom::blink::ShareService>(std::move(handle)));
38 }
39
set_error(mojom::ShareError value)40 void set_error(mojom::ShareError value) { error_ = value; }
41
title() const42 const WTF::String& title() const { return title_; }
text() const43 const WTF::String& text() const { return text_; }
url() const44 const KURL& url() const { return url_; }
files() const45 const WTF::Vector<SharedFilePtr>& files() const { return files_; }
error() const46 mojom::ShareError error() const { return error_; }
47
48 private:
Share(const WTF::String & title,const WTF::String & text,const KURL & url,WTF::Vector<SharedFilePtr> files,ShareCallback callback)49 void Share(const WTF::String& title,
50 const WTF::String& text,
51 const KURL& url,
52 WTF::Vector<SharedFilePtr> files,
53 ShareCallback callback) override {
54 title_ = title;
55 text_ = text;
56 url_ = url;
57
58 files_.clear();
59 files_.ReserveInitialCapacity(files.size());
60 for (const auto& entry : files) {
61 files_.push_back(entry->Clone());
62 }
63
64 std::move(callback).Run(error_);
65 }
66
67 mojo::Receiver<ShareService> receiver_{this};
68 WTF::String title_;
69 WTF::String text_;
70 KURL url_;
71 WTF::Vector<SharedFilePtr> files_;
72 mojom::ShareError error_;
73 };
74
75 class NavigatorShareTest : public testing::Test {
76 public:
NavigatorShareTest()77 NavigatorShareTest()
78 : holder_(std::make_unique<DummyPageHolder>()),
79 handle_scope_(GetScriptState()->GetIsolate()),
80 context_(GetScriptState()->GetContext()),
81 context_scope_(context_) {}
82
GetDocument()83 Document& GetDocument() { return holder_->GetDocument(); }
84
GetFrame()85 LocalFrame& GetFrame() { return holder_->GetFrame(); }
86
GetScriptState() const87 ScriptState* GetScriptState() const {
88 return ToScriptStateForMainWorld(&holder_->GetFrame());
89 }
90
Share(const ShareData & share_data)91 void Share(const ShareData& share_data) {
92 LocalFrame::NotifyUserActivation(&GetFrame());
93 Navigator* navigator = GetFrame().DomWindow()->navigator();
94 NonThrowableExceptionState exception_state;
95 ScriptPromise promise = NavigatorShare::share(GetScriptState(), *navigator,
96 &share_data, exception_state);
97 test::RunPendingTasks();
98 EXPECT_EQ(mock_share_service_.error() == mojom::ShareError::OK
99 ? v8::Promise::kFulfilled
100 : v8::Promise::kRejected,
101 promise.V8Value().As<v8::Promise>()->State());
102 }
103
mock_share_service()104 MockShareService& mock_share_service() { return mock_share_service_; }
105
106 protected:
SetUp()107 void SetUp() override {
108 GetFrame().Loader().CommitNavigation(
109 WebNavigationParams::CreateWithHTMLBuffer(SharedBuffer::Create(),
110 KURL("https://example.com")),
111 nullptr /* extra_data */);
112 test::RunPendingTasks();
113
114 GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
115 ShareService::Name_,
116 WTF::BindRepeating(&MockShareService::Bind,
117 WTF::Unretained(&mock_share_service_)));
118 }
119
TearDown()120 void TearDown() override {
121 // Remove the testing binder to avoid crashes between tests caused by
122 // MockShareService rebinding an already-bound Binding.
123 // See https://crbug.com/1010116 for more information.
124 GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
125 ShareService::Name_, {});
126 }
127
128 public:
129 MockShareService mock_share_service_;
130
131 std::unique_ptr<DummyPageHolder> holder_;
132 v8::HandleScope handle_scope_;
133 v8::Local<v8::Context> context_;
134 v8::Context::Scope context_scope_;
135 };
136
TEST_F(NavigatorShareTest,ShareText)137 TEST_F(NavigatorShareTest, ShareText) {
138 const String title = "Subject";
139 const String message = "Body";
140 const String url = "https://example.com/path?query#fragment";
141
142 ShareData share_data;
143 share_data.setTitle(title);
144 share_data.setText(message);
145 share_data.setUrl(url);
146 Share(share_data);
147
148 EXPECT_EQ(mock_share_service().title(), title);
149 EXPECT_EQ(mock_share_service().text(), message);
150 EXPECT_EQ(mock_share_service().url(), KURL(url));
151 EXPECT_EQ(mock_share_service().files().size(), 0U);
152 EXPECT_TRUE(
153 GetDocument().IsUseCounted(WebFeature::kWebShareSuccessfulWithoutFiles));
154 }
155
CreateSampleFile(ExecutionContext * context,const String & file_name,const String & content_type,const String & file_contents)156 File* CreateSampleFile(ExecutionContext* context,
157 const String& file_name,
158 const String& content_type,
159 const String& file_contents) {
160 HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString> blob_parts;
161 blob_parts.push_back(ArrayBufferOrArrayBufferViewOrBlobOrUSVString());
162 blob_parts.back().SetUSVString(file_contents);
163
164 FilePropertyBag file_property_bag;
165 file_property_bag.setType(content_type);
166 return File::Create(context, blob_parts, file_name, &file_property_bag);
167 }
168
TEST_F(NavigatorShareTest,ShareFile)169 TEST_F(NavigatorShareTest, ShareFile) {
170 const String file_name = "chart.svg";
171 const String content_type = "image/svg+xml";
172 const String file_contents = "<svg></svg>";
173
174 HeapVector<Member<File>> files;
175 files.push_back(CreateSampleFile(ExecutionContext::From(GetScriptState()),
176 file_name, content_type, file_contents));
177
178 ShareData share_data;
179 share_data.setFiles(files);
180 Share(share_data);
181
182 EXPECT_EQ(mock_share_service().files().size(), 1U);
183 EXPECT_EQ(mock_share_service().files()[0]->name, file_name);
184 EXPECT_EQ(mock_share_service().files()[0]->blob->GetType(), content_type);
185 EXPECT_EQ(mock_share_service().files()[0]->blob->size(),
186 file_contents.length());
187 EXPECT_TRUE(GetDocument().IsUseCounted(
188 WebFeature::kWebShareSuccessfulContainingFiles));
189 }
190
TEST_F(NavigatorShareTest,CancelShare)191 TEST_F(NavigatorShareTest, CancelShare) {
192 const String title = "Subject";
193 ShareData share_data;
194 share_data.setTitle(title);
195
196 mock_share_service().set_error(mojom::blink::ShareError::CANCELED);
197 Share(share_data);
198 EXPECT_TRUE(GetDocument().IsUseCounted(
199 WebFeature::kWebShareUnsuccessfulWithoutFiles));
200 }
201
TEST_F(NavigatorShareTest,CancelShareWithFile)202 TEST_F(NavigatorShareTest, CancelShareWithFile) {
203 const String file_name = "counts.csv";
204 const String content_type = "text/csv";
205 const String file_contents = "1,2,3";
206
207 HeapVector<Member<File>> files;
208 files.push_back(CreateSampleFile(ExecutionContext::From(GetScriptState()),
209 file_name, content_type, file_contents));
210
211 ShareData share_data;
212 share_data.setFiles(files);
213
214 mock_share_service().set_error(mojom::blink::ShareError::CANCELED);
215 Share(share_data);
216 EXPECT_TRUE(GetDocument().IsUseCounted(
217 WebFeature::kWebShareUnsuccessfulContainingFiles));
218 }
219
220 } // namespace blink
221