summaryrefslogtreecommitdiff
path: root/chromium/content/browser/renderer_host/embedded_frame_sink_provider_impl_unittest.cc
blob: 02999045b0b30bcb32868aa755824210123134a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// 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 "content/browser/renderer_host/embedded_frame_sink_provider_impl.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/fake_host_frame_sink_client.h"
#include "components/viz/test/mock_compositor_frame_sink_client.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/renderer_host/embedded_frame_sink_impl.h"
#include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom.h"
#include "ui/compositor/compositor.h"

#if !defined(OS_ANDROID)
#include "content/browser/compositor/image_transport_factory.h"
#endif

using testing::ElementsAre;
using testing::IsEmpty;

namespace content {
namespace {

constexpr uint32_t kRendererClientId = 3;
constexpr viz::FrameSinkId kFrameSinkParent(kRendererClientId, 1);
constexpr viz::FrameSinkId kFrameSinkA(kRendererClientId, 3);
constexpr viz::FrameSinkId kFrameSinkB(kRendererClientId, 4);

// Runs RunLoop until |endpoint| encounters a connection error.
template <class T>
void WaitForConnectionError(T* endpoint) {
  base::RunLoop run_loop;
  endpoint->set_connection_error_handler(run_loop.QuitClosure());
  run_loop.Run();
}

// Stub EmbeddedFrameSinkClient that stores the latest SurfaceInfo.
class StubEmbeddedFrameSinkClient
    : public blink::mojom::EmbeddedFrameSinkClient {
 public:
  StubEmbeddedFrameSinkClient() : binding_(this) {}
  ~StubEmbeddedFrameSinkClient() override {}

  blink::mojom::EmbeddedFrameSinkClientPtr GetInterfacePtr() {
    blink::mojom::EmbeddedFrameSinkClientPtr client;
    binding_.Bind(mojo::MakeRequest(&client));
    binding_.set_connection_error_handler(
        base::BindOnce([](bool* error_variable) { *error_variable = true; },
                       &connection_error_));
    return client;
  }

  void Close() { binding_.Close(); }

  const viz::SurfaceInfo& last_surface_info() const {
    return last_surface_info_;
  }

  bool connection_error() const { return connection_error_; }

 private:
  // blink::mojom::EmbeddedFrameSinkClient:
  void OnFirstSurfaceActivation(const viz::SurfaceInfo& surface_info) override {
    last_surface_info_ = surface_info;
  }

  mojo::Binding<blink::mojom::EmbeddedFrameSinkClient> binding_;
  viz::SurfaceInfo last_surface_info_;
  bool connection_error_ = false;

  DISALLOW_COPY_AND_ASSIGN(StubEmbeddedFrameSinkClient);
};

}  // namespace

class EmbeddedFrameSinkProviderImplTest : public testing::Test {
 public:
  EmbeddedFrameSinkProviderImpl* provider() { return provider_.get(); }

  // Gets the EmbeddedFrameSinkImpl for |frame_sink_id| or null if it doesn't
  // exist.
  EmbeddedFrameSinkImpl* GetEmbeddedFrameSink(
      const viz::FrameSinkId& frame_sink_id) {
    auto iter = provider_->frame_sink_map_.find(frame_sink_id);
    if (iter == provider_->frame_sink_map_.end())
      return nullptr;
    return iter->second.get();
  }

  // Gets list of FrameSinkId for all EmbeddedFrameSinkImpls.
  std::vector<viz::FrameSinkId> GetAllCanvases() {
    std::vector<viz::FrameSinkId> frame_sink_ids;
    for (auto& map_entry : provider_->frame_sink_map_)
      frame_sink_ids.push_back(map_entry.second->frame_sink_id());
    std::sort(frame_sink_ids.begin(), frame_sink_ids.end());
    return frame_sink_ids;
  }

  void DeleteEmbeddedFrameSinkProviderImpl() { provider_.reset(); }

  void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }

 protected:
  void SetUp() override {
    host_frame_sink_manager_ = std::make_unique<viz::HostFrameSinkManager>();

    // The FrameSinkManagerImpl implementation is in-process here for tests.
    frame_sink_manager_ = std::make_unique<viz::FrameSinkManagerImpl>();
    surface_utils::ConnectWithLocalFrameSinkManager(
        host_frame_sink_manager_.get(), frame_sink_manager_.get());

    provider_ = std::make_unique<EmbeddedFrameSinkProviderImpl>(
        host_frame_sink_manager_.get(), kRendererClientId);

    host_frame_sink_manager_->RegisterFrameSinkId(kFrameSinkParent,
                                                  &host_frame_sink_client_);
  }
  void TearDown() override {
    host_frame_sink_manager_->InvalidateFrameSinkId(kFrameSinkParent);
    provider_.reset();
    host_frame_sink_manager_.reset();
    frame_sink_manager_.reset();
  }

 private:
  // A MessageLoop is required for mojo bindings which are used to
  // connect to graphics services.
  base::MessageLoop message_loop_;
  viz::FakeHostFrameSinkClient host_frame_sink_client_;
  std::unique_ptr<viz::HostFrameSinkManager> host_frame_sink_manager_;
  std::unique_ptr<viz::FrameSinkManagerImpl> frame_sink_manager_;
  std::unique_ptr<EmbeddedFrameSinkProviderImpl> provider_;
};

// Mimics the workflow of OffscreenCanvas.commit() on renderer process.
TEST_F(EmbeddedFrameSinkProviderImplTest,
       SingleHTMLCanvasElementTransferToOffscreen) {
  // Mimic connection from the renderer main thread to browser.
  StubEmbeddedFrameSinkClient efs_client;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, kFrameSinkA,
                                        efs_client.GetInterfacePtr());

  EmbeddedFrameSinkImpl* efs_impl = GetEmbeddedFrameSink(kFrameSinkA);

  // There should be a single EmbeddedFrameSinkImpl and it should have the
  // provided FrameSinkId.
  EXPECT_EQ(kFrameSinkA, efs_impl->frame_sink_id());
  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));

  // Mimic connection from the renderer main or worker thread to browser.
  viz::mojom::CompositorFrameSinkPtr compositor_frame_sink;
  viz::MockCompositorFrameSinkClient compositor_frame_sink_client;
  provider()->CreateCompositorFrameSink(
      kFrameSinkA, compositor_frame_sink_client.BindInterfacePtr(),
      mojo::MakeRequest(&compositor_frame_sink));

  // Renderer submits a CompositorFrame with |local_id|.
  const viz::LocalSurfaceId local_id(1, base::UnguessableToken::Create());
  compositor_frame_sink->SubmitCompositorFrame(
      local_id, viz::MakeDefaultCompositorFrame(), base::nullopt,
      base::TimeTicks::Now().since_origin().InMicroseconds());

  RunUntilIdle();

  // EmbeddedFrameSinkImpl in browser should have LocalSurfaceId that was
  // submitted with the CompositorFrame.
  EXPECT_EQ(local_id, efs_impl->local_surface_id());

  // EmbeddedFrameSinkClient in the renderer should get the new SurfaceId
  // including the |local_id|.
  const auto& surface_info = efs_client.last_surface_info();
  EXPECT_EQ(kFrameSinkA, surface_info.id().frame_sink_id());
  EXPECT_EQ(local_id, surface_info.id().local_surface_id());
}

// Check that renderer closing the mojom::EmbeddedFrameSinkClient connection
// destroys the EmbeddedFrameSinkImpl in browser.
TEST_F(EmbeddedFrameSinkProviderImplTest, ClientClosesConnection) {
  StubEmbeddedFrameSinkClient efs_client;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, kFrameSinkA,
                                        efs_client.GetInterfacePtr());

  RunUntilIdle();

  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));

  // Mimic closing the connection from the renderer.
  efs_client.Close();

  RunUntilIdle();

  // The renderer closing the connection should destroy the
  // EmbeddedFrameSinkImpl.
  EXPECT_THAT(GetAllCanvases(), IsEmpty());
}

// Check that destroying EmbeddedFrameSinkProviderImpl closes connection to
// renderer.
TEST_F(EmbeddedFrameSinkProviderImplTest, ProviderClosesConnections) {
  StubEmbeddedFrameSinkClient efs_client;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, kFrameSinkA,
                                        efs_client.GetInterfacePtr());

  RunUntilIdle();

  // There should be a EmbeddedFrameSinkImpl and |efs_client| should be
  // bound.
  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA));
  EXPECT_FALSE(efs_client.connection_error());

  // Delete EmbeddedFrameSinkProviderImpl before client disconnects.
  DeleteEmbeddedFrameSinkProviderImpl();

  RunUntilIdle();

  // This should destroy the EmbeddedFrameSinkImpl and close the connection
  // to |efs_client| triggering a connection error.
  EXPECT_TRUE(efs_client.connection_error());
}

// Check that connecting CompositorFrameSink without first making a
// EmbeddedFrameSink connection fails.
TEST_F(EmbeddedFrameSinkProviderImplTest, ClientConnectionWrongOrder) {
  // Mimic connection from the renderer main or worker thread.
  viz::mojom::CompositorFrameSinkPtr compositor_frame_sink;
  viz::MockCompositorFrameSinkClient compositor_frame_sink_client;
  // Try to connect CompositorFrameSink without first making
  // EmbeddedFrameSink connection. This should fail.
  provider()->CreateCompositorFrameSink(
      kFrameSinkA, compositor_frame_sink_client.BindInterfacePtr(),
      mojo::MakeRequest(&compositor_frame_sink));

  // The request will fail and trigger a connection error.
  WaitForConnectionError(&compositor_frame_sink);
}

// Check that trying to create an EmbeddedFrameSinkImpl when the parent
// FrameSinkId has already been invalidated fails.
TEST_F(EmbeddedFrameSinkProviderImplTest, ParentNotRegistered) {
  StubEmbeddedFrameSinkClient efs_client;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkA, kFrameSinkB,
                                        efs_client.GetInterfacePtr());

  viz::mojom::CompositorFrameSinkPtr compositor_frame_sink;
  viz::MockCompositorFrameSinkClient compositor_frame_sink_client;
  // The embedder, kFrameSinkA, has already been invalidated and isn't
  // registered at this point. This request should fail.
  provider()->CreateCompositorFrameSink(
      kFrameSinkB, compositor_frame_sink_client.BindInterfacePtr(),
      mojo::MakeRequest(&compositor_frame_sink));

  // The request will fail and trigger a connection error.
  WaitForConnectionError(&compositor_frame_sink);
}

// Check that trying to create an EmbeddedFrameSinkImpl with a client id
// that doesn't match the renderer fails.
TEST_F(EmbeddedFrameSinkProviderImplTest, InvalidClientId) {
  const viz::FrameSinkId invalid_frame_sink_id(4, 3);
  EXPECT_NE(kRendererClientId, invalid_frame_sink_id.client_id());

  StubEmbeddedFrameSinkClient efs_client;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, invalid_frame_sink_id,
                                        efs_client.GetInterfacePtr());

  RunUntilIdle();

  // No EmbeddedFrameSinkImpl should have been created.
  EXPECT_THAT(GetAllCanvases(), IsEmpty());

  // The connection for |efs_client| will have failed and triggered a
  // connection error.
  EXPECT_TRUE(efs_client.connection_error());
}

// Mimic renderer with two offscreen canvases.
TEST_F(EmbeddedFrameSinkProviderImplTest,
       MultiHTMLCanvasElementTransferToOffscreen) {
  StubEmbeddedFrameSinkClient efs_client_a;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, kFrameSinkA,
                                        efs_client_a.GetInterfacePtr());

  StubEmbeddedFrameSinkClient efs_client_b;
  provider()->RegisterEmbeddedFrameSink(kFrameSinkParent, kFrameSinkB,
                                        efs_client_b.GetInterfacePtr());

  RunUntilIdle();

  // There should be two EmbeddedFrameSinkImpls created.
  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA, kFrameSinkB));

  // Mimic closing first connection from the renderer.
  efs_client_a.Close();

  RunUntilIdle();

  EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkB));

  // Mimic closing second connection from the renderer.
  efs_client_b.Close();

  RunUntilIdle();

  EXPECT_THAT(GetAllCanvases(), IsEmpty());
}

}  // namespace content