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