OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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 "content/browser/renderer_host/offscreen_canvas_provider_impl.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <utility> |
| 9 #include <vector> |
| 10 |
| 11 #include "base/memory/ptr_util.h" |
| 12 #include "base/message_loop/message_loop.h" |
| 13 #include "base/run_loop.h" |
| 14 #include "cc/ipc/mojo_compositor_frame_sink.mojom.h" |
| 15 #include "cc/output/compositor_frame.h" |
| 16 #include "content/browser/compositor/test/no_transport_image_transport_factory.h
" |
| 17 #include "content/browser/renderer_host/offscreen_canvas_surface_impl.h" |
| 18 #include "testing/gmock/include/gmock/gmock.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" |
| 20 #include "third_party/WebKit/public/platform/modules/offscreencanvas/offscreen_c
anvas_surface.mojom.h" |
| 21 |
| 22 #if !defined(OS_ANDROID) |
| 23 #include "content/browser/compositor/image_transport_factory.h" |
| 24 #endif |
| 25 |
| 26 using testing::ElementsAre; |
| 27 using testing::IsEmpty; |
| 28 |
| 29 namespace content { |
| 30 namespace { |
| 31 |
| 32 constexpr uint32_t kRendererClientId = 3; |
| 33 constexpr cc::FrameSinkId kFrameSinkParent(kRendererClientId, 1); |
| 34 constexpr cc::FrameSinkId kFrameSinkA(kRendererClientId, 3); |
| 35 constexpr cc::FrameSinkId kFrameSinkB(kRendererClientId, 4); |
| 36 |
| 37 // Stub OffscreenCanvasSurfaceClient that stores the latest SurfaceInfo. |
| 38 class StubOffscreenCanvasSurfaceClient |
| 39 : public blink::mojom::OffscreenCanvasSurfaceClient { |
| 40 public: |
| 41 StubOffscreenCanvasSurfaceClient() : binding_(this) {} |
| 42 ~StubOffscreenCanvasSurfaceClient() override {} |
| 43 |
| 44 blink::mojom::OffscreenCanvasSurfaceClientPtr GetInterfacePtr() { |
| 45 return binding_.CreateInterfacePtrAndBind(); |
| 46 } |
| 47 |
| 48 const cc::SurfaceInfo& GetLastSurfaceInfo() const { |
| 49 return last_surface_info_; |
| 50 } |
| 51 |
| 52 private: |
| 53 // blink::mojom::OffscreenCanvasSurfaceClient: |
| 54 void OnSurfaceCreated(const cc::SurfaceInfo& surface_info) override { |
| 55 last_surface_info_ = surface_info; |
| 56 } |
| 57 |
| 58 mojo::Binding<blink::mojom::OffscreenCanvasSurfaceClient> binding_; |
| 59 cc::SurfaceInfo last_surface_info_; |
| 60 |
| 61 DISALLOW_COPY_AND_ASSIGN(StubOffscreenCanvasSurfaceClient); |
| 62 }; |
| 63 |
| 64 // Stub MojoCompositorFrameSinkClient that does nothing. |
| 65 class StubCompositorFrameSinkClient |
| 66 : public cc::mojom::MojoCompositorFrameSinkClient { |
| 67 public: |
| 68 StubCompositorFrameSinkClient() : binding_(this) {} |
| 69 ~StubCompositorFrameSinkClient() override {} |
| 70 |
| 71 cc::mojom::MojoCompositorFrameSinkClientPtr GetInterfacePtr() { |
| 72 return binding_.CreateInterfacePtrAndBind(); |
| 73 } |
| 74 |
| 75 private: |
| 76 // cc::mojom::MojoCompositorFrameSinkClient: |
| 77 void DidReceiveCompositorFrameAck( |
| 78 const cc::ReturnedResourceArray& resources) override {} |
| 79 void OnBeginFrame(const cc::BeginFrameArgs& begin_frame_args) override {} |
| 80 void ReclaimResources(const cc::ReturnedResourceArray& resources) override {} |
| 81 |
| 82 mojo::Binding<cc::mojom::MojoCompositorFrameSinkClient> binding_; |
| 83 |
| 84 DISALLOW_COPY_AND_ASSIGN(StubCompositorFrameSinkClient); |
| 85 }; |
| 86 |
| 87 // Create a CompositorFrame suitable to send over IPC. |
| 88 cc::CompositorFrame MakeCompositorFrame() { |
| 89 cc::CompositorFrame frame; |
| 90 frame.metadata.begin_frame_ack.source_id = |
| 91 cc::BeginFrameArgs::kManualSourceId; |
| 92 frame.metadata.begin_frame_ack.sequence_number = |
| 93 cc::BeginFrameArgs::kStartingFrameNumber; |
| 94 frame.metadata.device_scale_factor = 1.0f; |
| 95 |
| 96 auto render_pass = cc::RenderPass::Create(); |
| 97 render_pass->id = 1; |
| 98 render_pass->output_rect = gfx::Rect(100, 100); |
| 99 frame.render_pass_list.push_back(std::move(render_pass)); |
| 100 |
| 101 return frame; |
| 102 } |
| 103 |
| 104 // Creates a closure that sets |error_variable| true when run. |
| 105 base::Closure ConnectionErrorClosure(bool* error_variable) { |
| 106 return base::Bind([](bool* error_variable) { *error_variable = true; }, |
| 107 error_variable); |
| 108 } |
| 109 |
| 110 } // namespace |
| 111 |
| 112 class OffscreenCanvasProviderImplTest : public testing::Test { |
| 113 public: |
| 114 OffscreenCanvasProviderImpl* provider() { return provider_.get(); } |
| 115 |
| 116 // Gets the OffscreenCanvasSurfaceImpl for |frame_sink_id| or null if it |
| 117 // it doesn't exist. |
| 118 OffscreenCanvasSurfaceImpl* GetOffscreenCanvasSurface( |
| 119 const cc::FrameSinkId& frame_sink_id) { |
| 120 auto iter = provider_->canvas_map_.find(frame_sink_id); |
| 121 if (iter == provider_->canvas_map_.end()) |
| 122 return nullptr; |
| 123 return iter->second.get(); |
| 124 } |
| 125 |
| 126 // Gets list of FrameSinkId for all offscreen canvases. |
| 127 std::vector<cc::FrameSinkId> GetAllCanvases() { |
| 128 std::vector<cc::FrameSinkId> frame_sink_ids; |
| 129 for (auto& map_entry : provider_->canvas_map_) |
| 130 frame_sink_ids.push_back(map_entry.second->frame_sink_id()); |
| 131 std::sort(frame_sink_ids.begin(), frame_sink_ids.end()); |
| 132 return frame_sink_ids; |
| 133 } |
| 134 |
| 135 void DeleteOffscreenCanvasProviderImpl() { provider_.reset(); } |
| 136 |
| 137 void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } |
| 138 |
| 139 protected: |
| 140 void SetUp() override { |
| 141 #if !defined(OS_ANDROID) |
| 142 ImageTransportFactory::InitializeForUnitTests( |
| 143 std::unique_ptr<ImageTransportFactory>( |
| 144 new NoTransportImageTransportFactory)); |
| 145 ImageTransportFactory::GetInstance() |
| 146 ->GetFrameSinkManagerHost() |
| 147 ->ConnectToFrameSinkManager(); |
| 148 #endif |
| 149 provider_ = |
| 150 base::MakeUnique<OffscreenCanvasProviderImpl>(kRendererClientId); |
| 151 } |
| 152 void TearDown() override { |
| 153 provider_.reset(); |
| 154 #if !defined(OS_ANDROID) |
| 155 ImageTransportFactory::Terminate(); |
| 156 #endif |
| 157 } |
| 158 |
| 159 private: |
| 160 base::MessageLoop message_loop_; |
| 161 std::unique_ptr<OffscreenCanvasProviderImpl> provider_; |
| 162 }; |
| 163 |
| 164 // Mimics the workflow of OffscreenCanvas.commit() on renderer process. |
| 165 TEST_F(OffscreenCanvasProviderImplTest, |
| 166 SingleHTMLCanvasElementTransferToOffscreen) { |
| 167 // Mimic connection from the renderer main thread to browser. |
| 168 StubOffscreenCanvasSurfaceClient surface_client; |
| 169 blink::mojom::OffscreenCanvasSurfacePtr surface; |
| 170 provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA, |
| 171 surface_client.GetInterfacePtr(), |
| 172 mojo::MakeRequest(&surface)); |
| 173 |
| 174 OffscreenCanvasSurfaceImpl* surface_impl = |
| 175 GetOffscreenCanvasSurface(kFrameSinkA); |
| 176 |
| 177 // There should be a single OffscreenCanvasSurfaceImpl and it should have the |
| 178 // provided FrameSinkId and parent FrameSinkId. |
| 179 EXPECT_EQ(kFrameSinkA, surface_impl->frame_sink_id()); |
| 180 EXPECT_EQ(kFrameSinkParent, surface_impl->parent_frame_sink_id()); |
| 181 EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA)); |
| 182 |
| 183 // Mimic connection from the renderer main or worker thread to browser. |
| 184 cc::mojom::MojoCompositorFrameSinkPtr compositor_frame_sink; |
| 185 StubCompositorFrameSinkClient compositor_frame_sink_client; |
| 186 provider()->CreateCompositorFrameSink( |
| 187 kFrameSinkA, compositor_frame_sink_client.GetInterfacePtr(), |
| 188 mojo::MakeRequest(&compositor_frame_sink)); |
| 189 |
| 190 // Renderer submits a CompositorFrame with |local_id|. |
| 191 const cc::LocalSurfaceId local_id(1, base::UnguessableToken::Create()); |
| 192 compositor_frame_sink->SubmitCompositorFrame(local_id, MakeCompositorFrame()); |
| 193 |
| 194 RunUntilIdle(); |
| 195 |
| 196 // OffscreenCanvasSurfaceImpl in browser should have LocalSurfaceId that was |
| 197 // submitted with the CompositorFrame. |
| 198 EXPECT_EQ(local_id, surface_impl->local_surface_id()); |
| 199 |
| 200 // OffscreenCanvasSurfaceClient in the renderer should get the new SurfaceId |
| 201 // including the |local_id|. |
| 202 const auto& surface_info = surface_client.GetLastSurfaceInfo(); |
| 203 EXPECT_EQ(kFrameSinkA, surface_info.id().frame_sink_id()); |
| 204 EXPECT_EQ(local_id, surface_info.id().local_surface_id()); |
| 205 } |
| 206 |
| 207 // Check that renderer closing the mojom::OffscreenCanvasSurface connection |
| 208 // destroys the OffscreenCanvasSurfaceImpl in browser. |
| 209 TEST_F(OffscreenCanvasProviderImplTest, ClientClosesConnection) { |
| 210 StubOffscreenCanvasSurfaceClient surface_client; |
| 211 blink::mojom::OffscreenCanvasSurfacePtr surface; |
| 212 provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA, |
| 213 surface_client.GetInterfacePtr(), |
| 214 mojo::MakeRequest(&surface)); |
| 215 |
| 216 RunUntilIdle(); |
| 217 |
| 218 EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA)); |
| 219 |
| 220 // Mimic closing the connection from the renderer. |
| 221 surface.reset(); |
| 222 |
| 223 RunUntilIdle(); |
| 224 |
| 225 // The renderer closing the connection should destroy the |
| 226 // OffscreenCanvasSurfaceImpl. |
| 227 EXPECT_THAT(GetAllCanvases(), IsEmpty()); |
| 228 } |
| 229 |
| 230 // Check that destroying OffscreenCanvasProviderImpl closes connection to |
| 231 // renderer. |
| 232 TEST_F(OffscreenCanvasProviderImplTest, ProviderClosesConnections) { |
| 233 StubOffscreenCanvasSurfaceClient surface_client; |
| 234 blink::mojom::OffscreenCanvasSurfacePtr surface; |
| 235 provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA, |
| 236 surface_client.GetInterfacePtr(), |
| 237 mojo::MakeRequest(&surface)); |
| 238 |
| 239 // Observe connection errors on |surface|. |
| 240 bool connection_error = false; |
| 241 surface.set_connection_error_handler( |
| 242 ConnectionErrorClosure(&connection_error)); |
| 243 |
| 244 RunUntilIdle(); |
| 245 |
| 246 // There should be a OffscreenCanvasSurfaceImpl and |surface| should be bound. |
| 247 EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA)); |
| 248 EXPECT_TRUE(surface.is_bound()); |
| 249 EXPECT_FALSE(connection_error); |
| 250 |
| 251 // Delete OffscreenCanvasProviderImpl before client disconnects. |
| 252 DeleteOffscreenCanvasProviderImpl(); |
| 253 |
| 254 RunUntilIdle(); |
| 255 |
| 256 // This should destroy the OffscreenCanvasSurfaceImpl and close the connection |
| 257 // to |surface| triggering a connection error. |
| 258 EXPECT_TRUE(connection_error); |
| 259 } |
| 260 |
| 261 // Check that connecting MojoCompositorFrameSink without first making a |
| 262 // OffscreenCanvasSurface connection fails. |
| 263 TEST_F(OffscreenCanvasProviderImplTest, ClientConnectionWrongOrder) { |
| 264 // Mimic connection from the renderer main or worker thread. |
| 265 cc::mojom::MojoCompositorFrameSinkPtr compositor_frame_sink; |
| 266 StubCompositorFrameSinkClient compositor_frame_sink_client; |
| 267 // Try to connect MojoCompositorFrameSink without first making |
| 268 // OffscreenCanvasSurface connection. This should fail. |
| 269 provider()->CreateCompositorFrameSink( |
| 270 kFrameSinkA, compositor_frame_sink_client.GetInterfacePtr(), |
| 271 mojo::MakeRequest(&compositor_frame_sink)); |
| 272 |
| 273 // Observe connection errors on |compositor_frame_sink|. |
| 274 bool connection_error = false; |
| 275 compositor_frame_sink.set_connection_error_handler( |
| 276 ConnectionErrorClosure(&connection_error)); |
| 277 |
| 278 RunUntilIdle(); |
| 279 |
| 280 // The connection for |compositor_frame_sink| will have failed and triggered a |
| 281 // connection error. |
| 282 EXPECT_TRUE(connection_error); |
| 283 } |
| 284 |
| 285 // Check that trying to create an OffscreenCanvasSurfaceImpl with a client id |
| 286 // that doesn't match the renderer fails. |
| 287 TEST_F(OffscreenCanvasProviderImplTest, InvalidClientId) { |
| 288 const cc::FrameSinkId invalid_frame_sink_id(4, 3); |
| 289 EXPECT_NE(kRendererClientId, invalid_frame_sink_id.client_id()); |
| 290 |
| 291 StubOffscreenCanvasSurfaceClient surface_client; |
| 292 blink::mojom::OffscreenCanvasSurfacePtr surface; |
| 293 provider()->CreateOffscreenCanvasSurface( |
| 294 kFrameSinkParent, invalid_frame_sink_id, surface_client.GetInterfacePtr(), |
| 295 mojo::MakeRequest(&surface)); |
| 296 |
| 297 // Observe connection errors on |surface|. |
| 298 bool connection_error = false; |
| 299 surface.set_connection_error_handler( |
| 300 ConnectionErrorClosure(&connection_error)); |
| 301 |
| 302 RunUntilIdle(); |
| 303 |
| 304 // No OffscreenCanvasSurfaceImpl should have been created. |
| 305 EXPECT_THAT(GetAllCanvases(), IsEmpty()); |
| 306 |
| 307 // The connection for |surface| will have failed and triggered a connection |
| 308 // error. |
| 309 EXPECT_TRUE(connection_error); |
| 310 } |
| 311 |
| 312 // Mimic renderer with two offscreen canvases. |
| 313 TEST_F(OffscreenCanvasProviderImplTest, |
| 314 MultiHTMLCanvasElementTransferToOffscreen) { |
| 315 StubOffscreenCanvasSurfaceClient surface_client_a; |
| 316 blink::mojom::OffscreenCanvasSurfacePtr surface_a; |
| 317 provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkA, |
| 318 surface_client_a.GetInterfacePtr(), |
| 319 mojo::MakeRequest(&surface_a)); |
| 320 |
| 321 StubOffscreenCanvasSurfaceClient surface_client_b; |
| 322 blink::mojom::OffscreenCanvasSurfacePtr surface_b; |
| 323 provider()->CreateOffscreenCanvasSurface(kFrameSinkParent, kFrameSinkB, |
| 324 surface_client_b.GetInterfacePtr(), |
| 325 mojo::MakeRequest(&surface_b)); |
| 326 |
| 327 RunUntilIdle(); |
| 328 |
| 329 // There should be two OffscreenCanvasSurfaceImpls created. |
| 330 EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkA, kFrameSinkB)); |
| 331 |
| 332 // Mimic closing first connection from the renderer. |
| 333 surface_a.reset(); |
| 334 |
| 335 RunUntilIdle(); |
| 336 |
| 337 EXPECT_THAT(GetAllCanvases(), ElementsAre(kFrameSinkB)); |
| 338 |
| 339 // Mimic closing second connection from the renderer. |
| 340 surface_b.reset(); |
| 341 |
| 342 RunUntilIdle(); |
| 343 |
| 344 EXPECT_THAT(GetAllCanvases(), IsEmpty()); |
| 345 } |
| 346 |
| 347 } // namespace content |
OLD | NEW |