OffscreenCanvasDisplayHelper.cpp (20427B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "OffscreenCanvasDisplayHelper.h" 8 9 #include "mozilla/SVGObserverUtils.h" 10 #include "mozilla/StaticPrefs_gfx.h" 11 #include "mozilla/dom/Document.h" 12 #include "mozilla/dom/WorkerRef.h" 13 #include "mozilla/dom/WorkerRunnable.h" 14 #include "mozilla/gfx/2D.h" 15 #include "mozilla/gfx/CanvasManagerChild.h" 16 #include "mozilla/gfx/DataSurfaceHelpers.h" 17 #include "mozilla/gfx/Swizzle.h" 18 #include "mozilla/layers/ImageBridgeChild.h" 19 #include "mozilla/layers/PersistentBufferProvider.h" 20 #include "mozilla/layers/TextureClientSharedSurface.h" 21 #include "mozilla/layers/TextureWrapperImage.h" 22 #include "nsICanvasRenderingContextInternal.h" 23 #include "nsRFPService.h" 24 25 namespace mozilla::dom { 26 27 OffscreenCanvasDisplayHelper::OffscreenCanvasDisplayHelper( 28 HTMLCanvasElement* aCanvasElement, uint32_t aWidth, uint32_t aHeight) 29 : mMutex("mozilla::dom::OffscreenCanvasDisplayHelper"), 30 mCanvasElement(aCanvasElement), 31 mImageProducerID(layers::ImageContainer::AllocateProducerID()) { 32 mData.mSize.width = aWidth; 33 mData.mSize.height = aHeight; 34 } 35 36 OffscreenCanvasDisplayHelper::~OffscreenCanvasDisplayHelper() { 37 MutexAutoLock lock(mMutex); 38 NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader", 39 mExpandedReader.forget()); 40 } 41 42 void OffscreenCanvasDisplayHelper::DestroyElement() { 43 MOZ_ASSERT(NS_IsMainThread()); 44 45 MutexAutoLock lock(mMutex); 46 if (mImageContainer) { 47 mImageContainer->ClearImagesInHost(layers::ClearImagesType::All); 48 mImageContainer = nullptr; 49 } 50 mFrontBufferSurface = nullptr; 51 mCanvasElement = nullptr; 52 } 53 54 void OffscreenCanvasDisplayHelper::DestroyCanvas() { 55 if (auto* cm = gfx::CanvasManagerChild::Get()) { 56 cm->EndCanvasTransaction(); 57 } 58 59 MutexAutoLock lock(mMutex); 60 if (mImageContainer) { 61 mImageContainer->ClearImagesInHost(layers::ClearImagesType::All); 62 mImageContainer = nullptr; 63 } 64 mFrontBufferSurface = nullptr; 65 mOffscreenCanvas = nullptr; 66 mWorkerRef = nullptr; 67 } 68 69 void OffscreenCanvasDisplayHelper::SetWriteOnly(nsIPrincipal* aExpandedReader) { 70 MutexAutoLock lock(mMutex); 71 NS_ReleaseOnMainThread("OffscreenCanvasDisplayHelper::mExpandedReader", 72 mExpandedReader.forget()); 73 mExpandedReader = aExpandedReader; 74 mIsWriteOnly = true; 75 } 76 77 bool OffscreenCanvasDisplayHelper::CallerCanRead( 78 nsIPrincipal& aPrincipal) const { 79 MutexAutoLock lock(mMutex); 80 if (!mIsWriteOnly) { 81 return true; 82 } 83 84 // If mExpandedReader is set, this canvas was tainted only by 85 // mExpandedReader's resources. So allow reading if the subject 86 // principal subsumes mExpandedReader. 87 if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) { 88 return true; 89 } 90 91 return nsContentUtils::PrincipalHasPermission(aPrincipal, 92 nsGkAtoms::all_urlsPermission); 93 } 94 95 bool OffscreenCanvasDisplayHelper::CanElementCaptureStream() const { 96 MutexAutoLock lock(mMutex); 97 return !!mWorkerRef; 98 } 99 100 bool OffscreenCanvasDisplayHelper::UsingElementCaptureStream() const { 101 MutexAutoLock lock(mMutex); 102 103 if (NS_WARN_IF(!NS_IsMainThread())) { 104 MOZ_ASSERT_UNREACHABLE("Should not call off main-thread!"); 105 return !!mCanvasElement; 106 } 107 108 return mCanvasElement && mCanvasElement->UsingCaptureStream(); 109 } 110 111 CanvasContextType OffscreenCanvasDisplayHelper::GetContextType() const { 112 MutexAutoLock lock(mMutex); 113 return mType; 114 } 115 116 RefPtr<layers::ImageContainer> OffscreenCanvasDisplayHelper::GetImageContainer() 117 const { 118 MutexAutoLock lock(mMutex); 119 return mImageContainer; 120 } 121 122 void OffscreenCanvasDisplayHelper::UpdateContext( 123 OffscreenCanvas* aOffscreenCanvas, RefPtr<ThreadSafeWorkerRef>&& aWorkerRef, 124 CanvasContextType aType, const Maybe<mozilla::ipc::ActorId>& aChildId) { 125 MutexAutoLock lock(mMutex); 126 127 // Only create ImageContainer if we don't already have one (Bug 2004797). 128 // Recreating it would discard any existing frames, causing flicker. 129 if (!mImageContainer) { 130 mImageContainer = MakeRefPtr<layers::ImageContainer>( 131 layers::ImageUsageType::OffscreenCanvas, 132 layers::ImageContainer::ASYNCHRONOUS); 133 } 134 135 mOffscreenCanvas = aOffscreenCanvas; 136 mWorkerRef = std::move(aWorkerRef); 137 mType = aType; 138 mContextChildId = aChildId; 139 140 if (aChildId) { 141 mContextManagerId = Some(gfx::CanvasManagerChild::Get()->Id()); 142 } else { 143 mContextManagerId.reset(); 144 } 145 146 MaybeQueueInvalidateElement(); 147 } 148 149 void OffscreenCanvasDisplayHelper::FlushForDisplay() { 150 MOZ_ASSERT(NS_IsMainThread()); 151 152 MutexAutoLock lock(mMutex); 153 154 // Without an OffscreenCanvas object bound to us, we either have not drawn 155 // using the canvas at all, or we have already destroyed it. 156 if (!mOffscreenCanvas) { 157 return; 158 } 159 160 // We assign/destroy the worker ref at the same time as the OffscreenCanvas 161 // ref, so we can only have the canvas without a worker ref if it exists on 162 // the main thread. 163 if (!mWorkerRef) { 164 // We queue to ensure that we have the same asynchronous update behaviour 165 // for a main thread and a worker based OffscreenCanvas. 166 mOffscreenCanvas->QueueCommitToCompositor(); 167 return; 168 } 169 170 class FlushWorkerRunnable final : public MainThreadWorkerRunnable { 171 public: 172 explicit FlushWorkerRunnable(OffscreenCanvasDisplayHelper* aDisplayHelper) 173 : MainThreadWorkerRunnable("FlushWorkerRunnable"), 174 mDisplayHelper(aDisplayHelper) {} 175 176 bool WorkerRun(JSContext*, WorkerPrivate*) override { 177 // The OffscreenCanvas can only be freed on the worker thread, so we 178 // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its 179 // destructor. We just need to make sure we don't call into 180 // OffscreenCanvas while holding the lock since it calls back into the 181 // OffscreenCanvasDisplayHelper. 182 RefPtr<OffscreenCanvas> canvas; 183 { 184 MutexAutoLock lock(mDisplayHelper->mMutex); 185 canvas = mDisplayHelper->mOffscreenCanvas; 186 } 187 188 if (canvas) { 189 canvas->CommitFrameToCompositor(); 190 } 191 return true; 192 } 193 194 private: 195 RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper; 196 }; 197 198 // Otherwise we are calling from the main thread during painting to a canvas 199 // on a worker thread. 200 auto task = MakeRefPtr<FlushWorkerRunnable>(this); 201 task->Dispatch(mWorkerRef->Private()); 202 } 203 204 bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor( 205 nsICanvasRenderingContextInternal* aContext, 206 const Maybe<OffscreenCanvasDisplayData>& aData) { 207 auto endTransaction = MakeScopeExit([&]() { 208 if (auto* cm = gfx::CanvasManagerChild::Get()) { 209 cm->EndCanvasTransaction(); 210 } 211 }); 212 213 MutexAutoLock lock(mMutex); 214 215 gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; 216 layers::TextureFlags flags = layers::TextureFlags::IMMUTABLE; 217 218 if (!mCanvasElement) { 219 // Our weak reference to the canvas element has been cleared, so we cannot 220 // present directly anymore. 221 return false; 222 } 223 224 if (aData) { 225 mData = aData.ref(); 226 MaybeQueueInvalidateElement(); 227 } 228 229 if (!mImageContainer) { 230 return false; 231 } 232 233 if (mData.mIsOpaque) { 234 flags |= layers::TextureFlags::IS_OPAQUE; 235 format = gfx::SurfaceFormat::B8G8R8X8; 236 } else if (!mData.mIsAlphaPremult) { 237 flags |= layers::TextureFlags::NON_PREMULTIPLIED; 238 } 239 240 switch (mData.mOriginPos) { 241 case gl::OriginPos::BottomLeft: 242 flags |= layers::TextureFlags::ORIGIN_BOTTOM_LEFT; 243 break; 244 case gl::OriginPos::TopLeft: 245 break; 246 default: 247 MOZ_ASSERT_UNREACHABLE("Unhandled origin position!"); 248 break; 249 } 250 251 auto imageBridge = layers::ImageBridgeChild::GetSingleton(); 252 if (!imageBridge) { 253 return false; 254 } 255 256 bool paintCallbacks = mData.mDoPaintCallbacks; 257 bool hasRemoteTextureDesc = false; 258 RefPtr<layers::Image> image; 259 RefPtr<layers::TextureClient> texture; 260 RefPtr<gfx::SourceSurface> surface; 261 Maybe<layers::SurfaceDescriptor> desc; 262 RefPtr<layers::FwdTransactionTracker> tracker; 263 264 { 265 MutexAutoUnlock unlock(mMutex); 266 if (paintCallbacks) { 267 aContext->OnBeforePaintTransaction(); 268 } 269 270 desc = aContext->PresentFrontBuffer(nullptr); 271 if (desc) { 272 hasRemoteTextureDesc = 273 desc->type() == 274 layers::SurfaceDescriptor::TSurfaceDescriptorRemoteTexture; 275 if (hasRemoteTextureDesc) { 276 tracker = aContext->UseCompositableForwarder(imageBridge); 277 if (tracker) { 278 flags |= layers::TextureFlags::WAIT_FOR_REMOTE_TEXTURE_OWNER; 279 } 280 } 281 } else { 282 if (layers::PersistentBufferProvider* provider = 283 aContext->GetBufferProvider()) { 284 texture = provider->GetTextureClient(); 285 } 286 287 if (!texture) { 288 surface = 289 aContext->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); 290 if (surface && surface->GetType() == gfx::SurfaceType::WEBGL) { 291 // Ensure we can map in the surface. If we get a SourceSurfaceWebgl 292 // surface, then it may not be backed by raw pixels yet. We need to 293 // map it on the owning thread rather than the ImageBridge thread. 294 gfx::DataSourceSurface::ScopedMap map( 295 static_cast<gfx::DataSourceSurface*>(surface.get()), 296 gfx::DataSourceSurface::READ); 297 if (!map.IsMapped()) { 298 surface = nullptr; 299 } 300 } 301 } 302 } 303 304 if (paintCallbacks) { 305 aContext->OnDidPaintTransaction(); 306 } 307 } 308 309 // We save any current surface because we might need it in GetSnapshot. If we 310 // are on a worker thread and not WebGL, then this will be the only way we can 311 // access the pixel data on the main thread. 312 mFrontBufferSurface = surface; 313 314 // We do not use the ImageContainer plumbing with remote textures, so if we 315 // have that, we can just early return here. 316 if (hasRemoteTextureDesc) { 317 const auto& textureDesc = desc->get_SurfaceDescriptorRemoteTexture(); 318 imageBridge->UpdateCompositable(mImageContainer, textureDesc.textureId(), 319 textureDesc.ownerId(), mData.mSize, flags, 320 tracker); 321 return true; 322 } 323 324 if (surface) { 325 auto surfaceImage = MakeRefPtr<layers::SourceSurfaceImage>(surface); 326 surfaceImage->SetTextureFlags(flags); 327 image = surfaceImage; 328 } else { 329 if (desc && !texture) { 330 texture = layers::SharedSurfaceTextureData::CreateTextureClient( 331 *desc, format, mData.mSize, flags, imageBridge); 332 } 333 if (texture) { 334 image = new layers::TextureWrapperImage( 335 texture, gfx::IntRect(gfx::IntPoint(0, 0), texture->GetSize())); 336 } 337 } 338 339 if (image) { 340 AutoTArray<layers::ImageContainer::NonOwningImage, 1> imageList; 341 imageList.AppendElement(layers::ImageContainer::NonOwningImage( 342 image, TimeStamp(), mLastFrameID++, mImageProducerID)); 343 mImageContainer->SetCurrentImages(imageList); 344 } else { 345 mImageContainer->ClearImagesInHost(layers::ClearImagesType::All); 346 } 347 348 return true; 349 } 350 351 void OffscreenCanvasDisplayHelper::MaybeQueueInvalidateElement() { 352 if (!mPendingInvalidate) { 353 mPendingInvalidate = true; 354 NS_DispatchToMainThread(NS_NewRunnableFunction( 355 "OffscreenCanvasDisplayHelper::InvalidateElement", 356 [self = RefPtr{this}] { self->InvalidateElement(); })); 357 } 358 } 359 360 void OffscreenCanvasDisplayHelper::InvalidateElement() { 361 MOZ_ASSERT(NS_IsMainThread()); 362 363 HTMLCanvasElement* canvasElement; 364 gfx::IntSize size; 365 366 { 367 MutexAutoLock lock(mMutex); 368 MOZ_ASSERT(mPendingInvalidate); 369 mPendingInvalidate = false; 370 canvasElement = mCanvasElement; 371 size = mData.mSize; 372 } 373 374 if (canvasElement) { 375 SVGObserverUtils::InvalidateDirectRenderingObservers(canvasElement); 376 canvasElement->InvalidateCanvasPlaceholder(size.width, size.height); 377 canvasElement->InvalidateCanvasContent(nullptr); 378 } 379 } 380 381 already_AddRefed<gfx::SourceSurface> 382 OffscreenCanvasDisplayHelper::TransformSurface(gfx::SourceSurface* aSurface, 383 bool aHasAlpha, 384 bool aIsAlphaPremult, 385 gl::OriginPos aOriginPos) const { 386 if (!aSurface) { 387 return nullptr; 388 } 389 390 if (aOriginPos == gl::OriginPos::TopLeft && (!aHasAlpha || aIsAlphaPremult)) { 391 // If we don't need to y-flip, and it is either opaque or premultiplied, 392 // we can just return the same surface. 393 return do_AddRef(aSurface); 394 } 395 396 // Otherwise we need to copy and apply the necessary transformations. 397 RefPtr<gfx::DataSourceSurface> srcSurface = aSurface->GetDataSurface(); 398 if (!srcSurface) { 399 return nullptr; 400 } 401 402 const auto size = srcSurface->GetSize(); 403 const auto format = srcSurface->GetFormat(); 404 405 RefPtr<gfx::DataSourceSurface> dstSurface = 406 gfx::Factory::CreateDataSourceSurface(size, format, /* aZero */ false); 407 if (!dstSurface) { 408 return nullptr; 409 } 410 411 gfx::DataSourceSurface::ScopedMap srcMap(srcSurface, 412 gfx::DataSourceSurface::READ); 413 gfx::DataSourceSurface::ScopedMap dstMap(dstSurface, 414 gfx::DataSourceSurface::WRITE); 415 if (!srcMap.IsMapped() || !dstMap.IsMapped()) { 416 return nullptr; 417 } 418 419 bool success; 420 switch (aOriginPos) { 421 case gl::OriginPos::BottomLeft: 422 if (aHasAlpha && !aIsAlphaPremult) { 423 success = gfx::PremultiplyYFlipData( 424 srcMap.GetData(), srcMap.GetStride(), format, dstMap.GetData(), 425 dstMap.GetStride(), format, size); 426 } else { 427 success = gfx::SwizzleYFlipData(srcMap.GetData(), srcMap.GetStride(), 428 format, dstMap.GetData(), 429 dstMap.GetStride(), format, size); 430 } 431 break; 432 case gl::OriginPos::TopLeft: 433 if (aHasAlpha && !aIsAlphaPremult) { 434 success = gfx::PremultiplyData(srcMap.GetData(), srcMap.GetStride(), 435 format, dstMap.GetData(), 436 dstMap.GetStride(), format, size); 437 } else { 438 success = gfx::SwizzleData(srcMap.GetData(), srcMap.GetStride(), format, 439 dstMap.GetData(), dstMap.GetStride(), format, 440 size); 441 } 442 break; 443 default: 444 MOZ_ASSERT_UNREACHABLE("Unhandled origin position!"); 445 success = false; 446 break; 447 } 448 449 if (!success) { 450 return nullptr; 451 } 452 453 return dstSurface.forget(); 454 } 455 456 already_AddRefed<gfx::SourceSurface> 457 OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() { 458 MOZ_ASSERT(NS_IsMainThread()); 459 460 class SnapshotWorkerRunnable final : public MainThreadWorkerRunnable { 461 public: 462 explicit SnapshotWorkerRunnable( 463 OffscreenCanvasDisplayHelper* aDisplayHelper) 464 : MainThreadWorkerRunnable("SnapshotWorkerRunnable"), 465 mMonitor("SnapshotWorkerRunnable::mMonitor"), 466 mDisplayHelper(aDisplayHelper) {} 467 468 bool WorkerRun(JSContext*, WorkerPrivate*) override { 469 // The OffscreenCanvas can only be freed on the worker thread, so we 470 // cannot be racing with an OffscreenCanvas::DestroyCanvas call and its 471 // destructor. We just need to make sure we don't call into 472 // OffscreenCanvas while holding the lock since it calls back into the 473 // OffscreenCanvasDisplayHelper. 474 RefPtr<OffscreenCanvas> canvas; 475 { 476 MutexAutoLock lock(mDisplayHelper->mMutex); 477 canvas = mDisplayHelper->mOffscreenCanvas; 478 } 479 480 // Now that we are on the correct thread, we can extract the snapshot. If 481 // it is a Skia surface, perform a copy to threading issues. 482 RefPtr<gfx::SourceSurface> surface; 483 if (canvas) { 484 if (auto* context = canvas->GetContext()) { 485 surface = 486 context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); 487 if (surface && surface->GetType() == gfx::SurfaceType::SKIA) { 488 surface = gfx::Factory::CopyDataSourceSurface( 489 static_cast<gfx::DataSourceSurface*>(surface.get())); 490 } 491 } 492 } 493 494 MonitorAutoLock lock(mMonitor); 495 mSurface = std::move(surface); 496 mComplete = true; 497 lock.NotifyAll(); 498 return true; 499 } 500 501 already_AddRefed<gfx::SourceSurface> Wait(int32_t aTimeoutMs) { 502 MonitorAutoLock lock(mMonitor); 503 504 TimeDuration timeout = TimeDuration::FromMilliseconds(aTimeoutMs); 505 while (!mComplete) { 506 if (lock.Wait(timeout) == CVStatus::Timeout) { 507 return nullptr; 508 } 509 } 510 511 return mSurface.forget(); 512 } 513 514 private: 515 Monitor mMonitor; 516 RefPtr<OffscreenCanvasDisplayHelper> mDisplayHelper; 517 RefPtr<gfx::SourceSurface> mSurface MOZ_GUARDED_BY(mMonitor); 518 bool mComplete MOZ_GUARDED_BY(mMonitor) = false; 519 }; 520 521 bool hasAlpha; 522 bool isAlphaPremult; 523 gl::OriginPos originPos; 524 HTMLCanvasElement* canvasElement; 525 RefPtr<gfx::SourceSurface> surface; 526 RefPtr<SnapshotWorkerRunnable> workerRunnable; 527 528 { 529 MutexAutoLock lock(mMutex); 530 #ifdef MOZ_WIDGET_ANDROID 531 // On Android, we cannot both display a GL context and read back the pixels. 532 if (mCanvasElement) { 533 return nullptr; 534 } 535 #endif 536 537 hasAlpha = !mData.mIsOpaque; 538 isAlphaPremult = mData.mIsAlphaPremult; 539 originPos = mData.mOriginPos; 540 canvasElement = mCanvasElement; 541 if (mWorkerRef) { 542 workerRunnable = MakeRefPtr<SnapshotWorkerRunnable>(this); 543 workerRunnable->Dispatch(mWorkerRef->Private()); 544 } 545 } 546 547 if (workerRunnable) { 548 // We transferred to a DOM worker, so we need to do the readback on the 549 // owning thread and wait for the result. 550 surface = workerRunnable->Wait( 551 StaticPrefs::gfx_offscreencanvas_snapshot_timeout_ms()); 552 } else if (canvasElement) { 553 // If we have a context, it is owned by the main thread. 554 const auto* offscreenCanvas = canvasElement->GetOffscreenCanvas(); 555 if (nsICanvasRenderingContextInternal* context = 556 offscreenCanvas->GetContext()) { 557 surface = 558 context->GetFrontBufferSnapshot(/* requireAlphaPremult */ false); 559 } 560 } 561 562 return TransformSurface(surface, hasAlpha, isAlphaPremult, originPos); 563 } 564 565 already_AddRefed<layers::Image> OffscreenCanvasDisplayHelper::GetAsImage() { 566 MOZ_ASSERT(NS_IsMainThread()); 567 568 RefPtr<gfx::SourceSurface> surface = GetSurfaceSnapshot(); 569 if (!surface) { 570 return nullptr; 571 } 572 return MakeAndAddRef<layers::SourceSurfaceImage>(surface); 573 } 574 575 UniquePtr<uint8_t[]> OffscreenCanvasDisplayHelper::GetImageBuffer( 576 CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat, 577 gfx::IntSize* aOutImageSize) { 578 RefPtr<gfx::SourceSurface> surface = GetSurfaceSnapshot(); 579 if (!surface) { 580 return nullptr; 581 } 582 583 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface(); 584 if (!dataSurface) { 585 return nullptr; 586 } 587 588 *aOutFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; 589 *aOutImageSize = dataSurface->GetSize(); 590 591 UniquePtr<uint8_t[]> imageBuffer = gfx::SurfaceToPackedBGRA(dataSurface); 592 if (!imageBuffer) { 593 return nullptr; 594 } 595 596 nsIPrincipal* principal = nullptr; 597 nsICookieJarSettings* cookieJarSettings = nullptr; 598 { 599 // This function is never called with mOffscreenCanvas set, so we skip 600 // the check for it. 601 MutexAutoLock lock(mMutex); 602 MOZ_ASSERT(!mOffscreenCanvas); 603 604 if (mCanvasElement) { 605 principal = mCanvasElement->NodePrincipal(); 606 cookieJarSettings = mCanvasElement->OwnerDoc()->CookieJarSettings(); 607 } 608 } 609 nsRFPService::PotentiallyDumpImage( 610 principal, imageBuffer.get(), dataSurface->GetSize().width, 611 dataSurface->GetSize().height, 612 dataSurface->GetSize().width * dataSurface->GetSize().height * 4); 613 if (aExtractionBehavior == CanvasUtils::ImageExtraction::Randomize) { 614 nsRFPService::RandomizePixels( 615 cookieJarSettings, principal, imageBuffer.get(), 616 dataSurface->GetSize().width, dataSurface->GetSize().height, 617 dataSurface->GetSize().width * dataSurface->GetSize().height * 4, 618 gfx::SurfaceFormat::A8R8G8B8_UINT32); 619 } 620 621 return imageBuffer; 622 } 623 624 } // namespace mozilla::dom