HTMLCanvasElement.cpp (50243B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 "mozilla/dom/HTMLCanvasElement.h" 8 9 #include "ActiveLayerTracker.h" 10 #include "CanvasUtils.h" 11 #include "ClientWebGLContext.h" 12 #include "ImageEncoder.h" 13 #include "MediaTrackGraph.h" 14 #include "VRManagerChild.h" 15 #include "WindowRenderer.h" 16 #include "jsapi.h" 17 #include "jsfriendapi.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/Base64.h" 20 #include "mozilla/BasePrincipal.h" 21 #include "mozilla/EventDispatcher.h" 22 #include "mozilla/MouseEvents.h" 23 #include "mozilla/Preferences.h" 24 #include "mozilla/PresShell.h" 25 #include "mozilla/ProfilerLabels.h" 26 #include "mozilla/ProfilerMarkers.h" 27 #include "mozilla/StaticPrefs_privacy.h" 28 #include "mozilla/dom/BlobImpl.h" 29 #include "mozilla/dom/CanvasCaptureMediaStream.h" 30 #include "mozilla/dom/CanvasRenderingContext2D.h" 31 #include "mozilla/dom/Document.h" 32 #include "mozilla/dom/Event.h" 33 #include "mozilla/dom/File.h" 34 #include "mozilla/dom/GeneratePlaceholderCanvasData.h" 35 #include "mozilla/dom/HTMLCanvasElementBinding.h" 36 #include "mozilla/dom/MouseEvent.h" 37 #include "mozilla/dom/OffscreenCanvas.h" 38 #include "mozilla/dom/OffscreenCanvasDisplayHelper.h" 39 #include "mozilla/dom/VideoStreamTrack.h" 40 #include "mozilla/gfx/Rect.h" 41 #include "mozilla/layers/CanvasRenderer.h" 42 #include "mozilla/layers/WebRenderCanvasRenderer.h" 43 #include "mozilla/layers/WebRenderUserData.h" 44 #include "mozilla/webgpu/CanvasContext.h" 45 #include "nsAttrValueInlines.h" 46 #include "nsContentUtils.h" 47 #include "nsDOMJSUtils.h" 48 #include "nsDisplayList.h" 49 #include "nsITimer.h" 50 #include "nsJSUtils.h" 51 #include "nsLayoutUtils.h" 52 #include "nsMathUtils.h" 53 #include "nsNetUtil.h" 54 #include "nsRefreshDriver.h" 55 #include "nsStreamUtils.h" 56 57 using namespace mozilla::layers; 58 using namespace mozilla::gfx; 59 60 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas) 61 62 namespace mozilla::dom { 63 64 class RequestedFrameRefreshObserver : public nsARefreshObserver { 65 NS_INLINE_DECL_REFCOUNTING(RequestedFrameRefreshObserver, override) 66 67 public: 68 RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement, 69 nsRefreshDriver* aRefreshDriver, 70 bool aReturnPlaceholderData) 71 : mRegistered(false), 72 mWatching(false), 73 mReturnPlaceholderData(aReturnPlaceholderData), 74 mOwningElement(aOwningElement), 75 mRefreshDriver(aRefreshDriver), 76 mWatchManager(this, AbstractThread::MainThread()), 77 mPendingThrottledCapture(false) { 78 MOZ_ASSERT(mOwningElement); 79 } 80 81 static already_AddRefed<DataSourceSurface> CopySurface( 82 const RefPtr<SourceSurface>& aSurface, bool aReturnPlaceholderData) { 83 RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); 84 if (!data) { 85 return nullptr; 86 } 87 88 DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ); 89 if (!read.IsMapped()) { 90 return nullptr; 91 } 92 93 RefPtr<DataSourceSurface> copy = Factory::CreateDataSourceSurfaceWithStride( 94 data->GetSize(), data->GetFormat(), read.GetStride()); 95 if (!copy) { 96 return nullptr; 97 } 98 99 DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE); 100 if (!write.IsMapped()) { 101 return nullptr; 102 } 103 104 MOZ_ASSERT(read.GetStride() == write.GetStride()); 105 MOZ_ASSERT(data->GetSize() == copy->GetSize()); 106 MOZ_ASSERT(data->GetFormat() == copy->GetFormat()); 107 108 if (aReturnPlaceholderData) { 109 auto size = write.GetStride() * copy->GetSize().height; 110 auto* data = write.GetData(); 111 GeneratePlaceholderCanvasData(size, data); 112 } else { 113 memcpy(write.GetData(), read.GetData(), 114 write.GetStride() * copy->GetSize().height); 115 } 116 117 return copy.forget(); 118 } 119 120 void SetReturnPlaceholderData(bool aReturnPlaceholderData) { 121 mReturnPlaceholderData = aReturnPlaceholderData; 122 } 123 124 void NotifyCaptureStateChange() { 125 if (mPendingThrottledCapture) { 126 return; 127 } 128 129 if (!mOwningElement) { 130 return; 131 } 132 133 Watchable<FrameCaptureState>* captureState = 134 mOwningElement->GetFrameCaptureState(); 135 if (!captureState) { 136 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 137 "Abort: No capture state"_ns); 138 return; 139 } 140 141 if (captureState->Ref() == FrameCaptureState::CLEAN) { 142 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 143 "Abort: CLEAN"_ns); 144 return; 145 } 146 147 if (!mRefreshDriver) { 148 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 149 "Abort: no refresh driver"_ns); 150 return; 151 } 152 153 if (!mRefreshDriver->IsThrottled()) { 154 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 155 "Abort: not throttled"_ns); 156 return; 157 } 158 159 TimeStamp now = TimeStamp::Now(); 160 TimeStamp next = 161 mLastCaptureTime.IsNull() 162 ? now 163 : mLastCaptureTime + TimeDuration::FromMilliseconds( 164 nsRefreshDriver::DefaultInterval()); 165 if (mLastCaptureTime.IsNull() || next <= now) { 166 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 167 "CaptureFrame direct while throttled"_ns); 168 CaptureFrame(now); 169 return; 170 } 171 172 nsCString str; 173 if (profiler_thread_is_being_profiled_for_markers()) { 174 str.AppendPrintf("Delaying CaptureFrame by %.2fms", 175 (next - now).ToMilliseconds()); 176 } 177 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, str); 178 179 mPendingThrottledCapture = true; 180 AbstractThread::MainThread()->DelayedDispatch( 181 NS_NewRunnableFunction( 182 __func__, 183 [this, self = RefPtr<RequestedFrameRefreshObserver>(this), next] { 184 mPendingThrottledCapture = false; 185 AUTO_PROFILER_MARKER_TEXT( 186 "Canvas CaptureStream", MEDIA_RT, {}, 187 "CaptureFrame after delay while throttled"_ns); 188 CaptureFrame(next); 189 }), 190 // next >= now, so this is a guard for (next - now) flooring to 0. 191 std::max<uint32_t>( 192 1, static_cast<uint32_t>((next - now).ToMilliseconds()))); 193 } 194 195 void WillRefresh(TimeStamp aTime) override { 196 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 197 "CaptureFrame by refresh driver"_ns); 198 199 CaptureFrame(aTime); 200 } 201 202 void CaptureFrame(TimeStamp aTime) { 203 MOZ_ASSERT(NS_IsMainThread()); 204 205 if (!mOwningElement) { 206 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 207 "Abort: no owning element"_ns); 208 return; 209 } 210 211 if (mOwningElement->IsWriteOnly()) { 212 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 213 "Abort: write only"_ns); 214 return; 215 } 216 217 if (auto* captureStateWatchable = mOwningElement->GetFrameCaptureState(); 218 captureStateWatchable && 219 *captureStateWatchable == FrameCaptureState::CLEAN) { 220 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 221 "Abort: CLEAN"_ns); 222 return; 223 } 224 225 // Mark the context already now, since if the frame capture state is DIRTY 226 // and we catch an early return below (not marking it CLEAN), the next draw 227 // will not trigger a capture state change from the 228 // Watchable<FrameCaptureState>. 229 mOwningElement->MarkContextCleanForFrameCapture(); 230 231 mOwningElement->ProcessDestroyedFrameListeners(); 232 233 if (!mOwningElement->IsFrameCaptureRequested(aTime)) { 234 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 235 "Abort: no capture requested"_ns); 236 return; 237 } 238 239 RefPtr<SourceSurface> snapshot; 240 { 241 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 242 "GetSnapshot"_ns); 243 snapshot = mOwningElement->GetSurfaceSnapshot(nullptr); 244 if (!snapshot) { 245 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 246 "Abort: snapshot failed"_ns); 247 return; 248 } 249 } 250 251 RefPtr<DataSourceSurface> copy; 252 { 253 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 254 "CopySurface"_ns); 255 copy = CopySurface(snapshot, mReturnPlaceholderData); 256 if (!copy) { 257 PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, 258 "Abort: copy failed"_ns); 259 return; 260 } 261 } 262 263 nsCString str; 264 if (profiler_thread_is_being_profiled_for_markers()) { 265 TimeDuration sinceLast = 266 aTime - (mLastCaptureTime.IsNull() ? aTime : mLastCaptureTime); 267 str.AppendPrintf("Forwarding captured frame %.2fms after last", 268 sinceLast.ToMilliseconds()); 269 } 270 AUTO_PROFILER_MARKER_TEXT("Canvas CaptureStream", MEDIA_RT, {}, str); 271 272 if (!mLastCaptureTime.IsNull() && aTime <= mLastCaptureTime) { 273 aTime = mLastCaptureTime + TimeDuration::FromMilliseconds(1); 274 } 275 mLastCaptureTime = aTime; 276 277 mOwningElement->SetFrameCapture(copy.forget(), aTime); 278 } 279 280 void DetachFromRefreshDriver() { 281 MOZ_ASSERT(mOwningElement); 282 MOZ_ASSERT(mRefreshDriver); 283 284 Unregister(); 285 mRefreshDriver = nullptr; 286 mWatchManager.Shutdown(); 287 } 288 289 bool IsRegisteredAndWatching() { return mRegistered && mWatching; } 290 291 void Register() { 292 if (!mRegistered) { 293 MOZ_ASSERT(mRefreshDriver); 294 if (mRefreshDriver) { 295 mRefreshDriver->AddRefreshObserver(this, FlushType::Display, 296 "Canvas frame capture listeners"); 297 mRegistered = true; 298 } 299 } 300 301 if (mWatching) { 302 return; 303 } 304 305 if (!mOwningElement) { 306 return; 307 } 308 309 if (Watchable<FrameCaptureState>* captureState = 310 mOwningElement->GetFrameCaptureState()) { 311 mWatchManager.Watch( 312 *captureState, 313 &RequestedFrameRefreshObserver::NotifyCaptureStateChange); 314 mWatching = true; 315 } 316 } 317 318 void Unregister() { 319 if (mRegistered) { 320 MOZ_ASSERT(mRefreshDriver); 321 if (mRefreshDriver) { 322 mRefreshDriver->RemoveRefreshObserver(this, FlushType::Display); 323 mRegistered = false; 324 } 325 } 326 327 if (!mWatching) { 328 return; 329 } 330 331 if (!mOwningElement) { 332 return; 333 } 334 335 if (Watchable<FrameCaptureState>* captureState = 336 mOwningElement->GetFrameCaptureState()) { 337 mWatchManager.Unwatch( 338 *captureState, 339 &RequestedFrameRefreshObserver::NotifyCaptureStateChange); 340 mWatching = false; 341 } 342 } 343 344 private: 345 virtual ~RequestedFrameRefreshObserver() { 346 MOZ_ASSERT(!mRefreshDriver); 347 MOZ_ASSERT(!mRegistered); 348 MOZ_ASSERT(!mWatching); 349 } 350 351 bool mRegistered; 352 bool mWatching; 353 bool mReturnPlaceholderData; 354 const WeakPtr<HTMLCanvasElement> mOwningElement; 355 RefPtr<nsRefreshDriver> mRefreshDriver; 356 WatchManager<RequestedFrameRefreshObserver> mWatchManager; 357 TimeStamp mLastCaptureTime; 358 bool mPendingThrottledCapture; 359 }; 360 361 // --------------------------------------------------------------------------- 362 363 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas, mContext, 364 mCallback) 365 366 HTMLCanvasPrintState::HTMLCanvasPrintState( 367 HTMLCanvasElement* aCanvas, nsICanvasRenderingContextInternal* aContext, 368 nsITimerCallback* aCallback) 369 : mIsDone(false), 370 mPendingNotify(false), 371 mCanvas(aCanvas), 372 mContext(aContext), 373 mCallback(aCallback) {} 374 375 HTMLCanvasPrintState::~HTMLCanvasPrintState() = default; 376 377 /* virtual */ 378 JSObject* HTMLCanvasPrintState::WrapObject(JSContext* aCx, 379 JS::Handle<JSObject*> aGivenProto) { 380 return MozCanvasPrintState_Binding::Wrap(aCx, this, aGivenProto); 381 } 382 383 nsISupports* HTMLCanvasPrintState::Context() const { return mContext; } 384 385 void HTMLCanvasPrintState::Done() { 386 if (!mPendingNotify && !mIsDone) { 387 // The canvas needs to be invalidated for printing reftests on linux to 388 // work. 389 if (mCanvas) { 390 mCanvas->InvalidateCanvas(); 391 } 392 RefPtr<nsRunnableMethod<HTMLCanvasPrintState>> doneEvent = 393 NewRunnableMethod("dom::HTMLCanvasPrintState::NotifyDone", this, 394 &HTMLCanvasPrintState::NotifyDone); 395 if (NS_SUCCEEDED(NS_DispatchToCurrentThread(doneEvent))) { 396 mPendingNotify = true; 397 } 398 } 399 } 400 401 void HTMLCanvasPrintState::NotifyDone() { 402 mIsDone = true; 403 mPendingNotify = false; 404 if (mCallback) { 405 mCallback->Notify(nullptr); 406 } 407 } 408 409 // --------------------------------------------------------------------------- 410 411 HTMLCanvasElementObserver::HTMLCanvasElementObserver( 412 HTMLCanvasElement* aElement) 413 : mElement(aElement) { 414 RegisterObserverEvents(); 415 } 416 417 HTMLCanvasElementObserver::~HTMLCanvasElementObserver() { Destroy(); } 418 419 void HTMLCanvasElementObserver::Destroy() { 420 UnregisterObserverEvents(); 421 mElement = nullptr; 422 } 423 424 void HTMLCanvasElementObserver::RegisterObserverEvents() { 425 if (!mElement) { 426 return; 427 } 428 429 nsCOMPtr<nsIObserverService> observerService = 430 mozilla::services::GetObserverService(); 431 432 MOZ_ASSERT(observerService); 433 434 if (observerService) { 435 observerService->AddObserver(this, "memory-pressure", false); 436 observerService->AddObserver(this, "canvas-device-reset", false); 437 } 438 } 439 440 void HTMLCanvasElementObserver::UnregisterObserverEvents() { 441 if (!mElement) { 442 return; 443 } 444 445 nsCOMPtr<nsIObserverService> observerService = 446 mozilla::services::GetObserverService(); 447 448 // Do not assert on observerService here. This might be triggered by 449 // the cycle collector at a late enough time, that XPCOM services are 450 // no longer available. See bug 1029504. 451 if (observerService) { 452 observerService->RemoveObserver(this, "memory-pressure"); 453 observerService->RemoveObserver(this, "canvas-device-reset"); 454 } 455 } 456 457 NS_IMETHODIMP 458 HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic, 459 const char16_t*) { 460 if (!mElement) { 461 return NS_OK; 462 } 463 464 if (strcmp(aTopic, "memory-pressure") == 0) { 465 mElement->OnMemoryPressure(); 466 } else if (strcmp(aTopic, "canvas-device-reset") == 0) { 467 mElement->OnDeviceReset(); 468 } 469 470 return NS_OK; 471 } 472 473 NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver) 474 475 // --------------------------------------------------------------------------- 476 477 HTMLCanvasElement::HTMLCanvasElement( 478 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 479 : nsGenericHTMLElement(std::move(aNodeInfo)), 480 mResetLayer(true), 481 mMaybeModified(false), 482 mWriteOnly(false) {} 483 484 HTMLCanvasElement::~HTMLCanvasElement() { Destroy(); } 485 486 void HTMLCanvasElement::Destroy() { 487 if (mOffscreenDisplay) { 488 mOffscreenDisplay->DestroyElement(); 489 mOffscreenDisplay = nullptr; 490 mImageContainer = nullptr; 491 } 492 493 if (mContextObserver) { 494 mContextObserver->Destroy(); 495 mContextObserver = nullptr; 496 } 497 498 ResetPrintCallback(); 499 if (mRequestedFrameRefreshObserver) { 500 mRequestedFrameRefreshObserver->DetachFromRefreshDriver(); 501 mRequestedFrameRefreshObserver = nullptr; 502 } 503 } 504 505 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLCanvasElement) 506 507 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLCanvasElement, 508 nsGenericHTMLElement) 509 tmp->Destroy(); 510 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentContext, mPrintCallback, mPrintState, 511 mOriginalCanvas, mOffscreenCanvas) 512 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 513 514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLCanvasElement, 515 nsGenericHTMLElement) 516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentContext, mPrintCallback, 517 mPrintState, mOriginalCanvas, 518 mOffscreenCanvas) 519 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 520 521 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLCanvasElement, 522 nsGenericHTMLElement) 523 524 NS_IMPL_ELEMENT_CLONE(HTMLCanvasElement) 525 526 /* virtual */ 527 JSObject* HTMLCanvasElement::WrapNode(JSContext* aCx, 528 JS::Handle<JSObject*> aGivenProto) { 529 return HTMLCanvasElement_Binding::Wrap(aCx, this, aGivenProto); 530 } 531 532 already_AddRefed<nsICanvasRenderingContextInternal> 533 HTMLCanvasElement::CreateContext(CanvasContextType aContextType) { 534 // Note that the compositor backend will be LAYERS_NONE if there is no widget. 535 RefPtr<nsICanvasRenderingContextInternal> ret = 536 CreateContextHelper(aContextType, GetCompositorBackendType()); 537 if (NS_WARN_IF(!ret)) { 538 return nullptr; 539 } 540 541 // Add Observer for webgl canvas. 542 if (aContextType == CanvasContextType::WebGL1 || 543 aContextType == CanvasContextType::WebGL2 || 544 aContextType == CanvasContextType::Canvas2D) { 545 if (!mContextObserver) { 546 mContextObserver = new HTMLCanvasElementObserver(this); 547 } 548 } 549 550 ret->SetCanvasElement(this); 551 return ret.forget(); 552 } 553 554 nsresult HTMLCanvasElement::UpdateContext( 555 JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions, 556 ErrorResult& aRvForDictionaryInit) { 557 nsresult rv = CanvasRenderingContextHelper::UpdateContext( 558 aCx, aNewContextOptions, aRvForDictionaryInit); 559 560 if (NS_FAILED(rv)) { 561 return rv; 562 } 563 564 // If we have a mRequestedFrameRefreshObserver that wasn't fully registered, 565 // retry that now. 566 if (mRequestedFrameRefreshObserver.get() && 567 !mRequestedFrameRefreshObserver->IsRegisteredAndWatching()) { 568 mRequestedFrameRefreshObserver->Register(); 569 } 570 571 return NS_OK; 572 } 573 574 CSSIntSize HTMLCanvasElement::GetWidthHeight() { 575 CSSIntSize size = kFallbackIntrinsicSizeInPixels; 576 const nsAttrValue* value; 577 578 if ((value = GetParsedAttr(nsGkAtoms::width)) && 579 value->Type() == nsAttrValue::eInteger) { 580 size.width = value->GetIntegerValue(); 581 } 582 583 if ((value = GetParsedAttr(nsGkAtoms::height)) && 584 value->Type() == nsAttrValue::eInteger) { 585 size.height = value->GetIntegerValue(); 586 } 587 588 MOZ_ASSERT(size.width >= 0 && size.height >= 0, 589 "we should've required <canvas> width/height attrs to be " 590 "unsigned (non-negative) values"); 591 592 return size; 593 } 594 595 void HTMLCanvasElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, 596 const nsAttrValue* aValue, 597 const nsAttrValue* aOldValue, 598 nsIPrincipal* aSubjectPrincipal, 599 bool aNotify) { 600 AfterMaybeChangeAttr(aNamespaceID, aName, aNotify); 601 602 return nsGenericHTMLElement::AfterSetAttr( 603 aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 604 } 605 606 void HTMLCanvasElement::OnAttrSetButNotChanged( 607 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue, 608 bool aNotify) { 609 AfterMaybeChangeAttr(aNamespaceID, aName, aNotify); 610 611 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName, 612 aValue, aNotify); 613 } 614 615 void HTMLCanvasElement::AfterMaybeChangeAttr(int32_t aNamespaceID, 616 nsAtom* aName, bool aNotify) { 617 if (mCurrentContext && aNamespaceID == kNameSpaceID_None && 618 (aName == nsGkAtoms::width || aName == nsGkAtoms::height || 619 aName == nsGkAtoms::moz_opaque)) { 620 ErrorResult dummy; 621 UpdateContext(nullptr, JS::NullHandleValue, dummy); 622 } 623 } 624 625 void HTMLCanvasElement::HandlePrintCallback(nsPresContext* aPresContext) { 626 // Only call the print callback here if 1) we're in a print testing mode or 627 // print preview mode, 2) the canvas has a print callback and 3) the callback 628 // hasn't already been called. For real printing the callback is handled in 629 // nsPageSequenceFrame::PrePrintNextSheet. 630 if ((aPresContext->Type() == nsPresContext::eContext_PageLayout || 631 aPresContext->Type() == nsPresContext::eContext_PrintPreview) && 632 !mPrintState && GetMozPrintCallback()) { 633 DispatchPrintCallback(nullptr); 634 } 635 } 636 637 nsresult HTMLCanvasElement::DispatchPrintCallback(nsITimerCallback* aCallback) { 638 // For print reftests the context may not be initialized yet, so get a context 639 // so mCurrentContext is set. 640 if (!mCurrentContext) { 641 nsresult rv; 642 nsCOMPtr<nsISupports> context; 643 rv = GetContext(u"2d"_ns, getter_AddRefs(context)); 644 NS_ENSURE_SUCCESS(rv, rv); 645 } 646 mPrintState = new HTMLCanvasPrintState(this, mCurrentContext, aCallback); 647 648 RefPtr<nsRunnableMethod<HTMLCanvasElement>> renderEvent = 649 NewRunnableMethod("dom::HTMLCanvasElement::CallPrintCallback", this, 650 &HTMLCanvasElement::CallPrintCallback); 651 return OwnerDoc()->Dispatch(renderEvent.forget()); 652 } 653 654 void HTMLCanvasElement::CallPrintCallback() { 655 AUTO_PROFILER_MARKER_TEXT("HTMLCanvasElement Printing", LAYOUT_Printing, {}, 656 "HTMLCanvasElement::CallPrintCallback"_ns); 657 if (!mPrintState) { 658 // `mPrintState` might have been destroyed by cancelling the previous 659 // printing (especially the canvas frame destruction) during processing 660 // event loops in the printing. 661 return; 662 } 663 RefPtr<PrintCallback> callback = GetMozPrintCallback(); 664 RefPtr<HTMLCanvasPrintState> state = mPrintState; 665 callback->Call(*state); 666 } 667 668 void HTMLCanvasElement::ResetPrintCallback() { 669 if (mPrintState) { 670 mPrintState = nullptr; 671 } 672 } 673 674 bool HTMLCanvasElement::IsPrintCallbackDone() { 675 if (mPrintState == nullptr) { 676 return true; 677 } 678 679 return mPrintState->mIsDone; 680 } 681 682 HTMLCanvasElement* HTMLCanvasElement::GetOriginalCanvas() { 683 return mOriginalCanvas ? mOriginalCanvas.get() : this; 684 } 685 686 nsresult HTMLCanvasElement::CopyInnerTo(HTMLCanvasElement* aDest) { 687 nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest); 688 NS_ENSURE_SUCCESS(rv, rv); 689 Document* destDoc = aDest->OwnerDoc(); 690 if (destDoc->IsStaticDocument()) { 691 // The Firefox print preview code can create a static clone from an 692 // existing static clone, so we may not be the original 'canvas' element. 693 aDest->mOriginalCanvas = GetOriginalCanvas(); 694 695 if (GetMozPrintCallback()) { 696 destDoc->SetHasPrintCallbacks(); 697 } 698 699 // We make sure that the canvas is not zero sized since that would cause 700 // the DrawImage call below to return an error, which would cause printing 701 // to fail. 702 CSSIntSize size = GetWidthHeight(); 703 if (size.height > 0 && size.width > 0) { 704 nsCOMPtr<nsISupports> cxt; 705 aDest->GetContext(u"2d"_ns, getter_AddRefs(cxt)); 706 RefPtr<CanvasRenderingContext2D> context2d = 707 static_cast<CanvasRenderingContext2D*>(cxt.get()); 708 if (context2d && !mPrintCallback) { 709 CanvasImageSource source; 710 source.SetAsHTMLCanvasElement() = this; 711 ErrorResult err; 712 context2d->DrawImage(source, 0.0, 0.0, err); 713 rv = err.StealNSResult(); 714 } 715 } 716 } 717 return rv; 718 } 719 720 nsChangeHint HTMLCanvasElement::GetAttributeChangeHint( 721 const nsAtom* aAttribute, AttrModType aModType) const { 722 nsChangeHint retval = 723 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType); 724 if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { 725 retval |= NS_STYLE_HINT_REFLOW; 726 } else if (aAttribute == nsGkAtoms::moz_opaque) { 727 retval |= NS_STYLE_HINT_VISUAL; 728 } 729 return retval; 730 } 731 732 void HTMLCanvasElement::MapAttributesIntoRule( 733 MappedDeclarationsBuilder& aBuilder) { 734 MapAspectRatioInto(aBuilder); 735 MapCommonAttributesInto(aBuilder); 736 } 737 738 nsMapRuleToAttributesFunc HTMLCanvasElement::GetAttributeMappingFunction() 739 const { 740 return &MapAttributesIntoRule; 741 } 742 743 NS_IMETHODIMP_(bool) 744 HTMLCanvasElement::IsAttributeMapped(const nsAtom* aAttribute) const { 745 static const MappedAttributeEntry attributes[] = { 746 {nsGkAtoms::width}, {nsGkAtoms::height}, {nullptr}}; 747 static const MappedAttributeEntry* const map[] = {attributes, 748 sCommonAttributeMap}; 749 return FindAttributeDependence(aAttribute, map); 750 } 751 752 bool HTMLCanvasElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 753 const nsAString& aValue, 754 nsIPrincipal* aMaybeScriptedPrincipal, 755 nsAttrValue& aResult) { 756 if (aNamespaceID == kNameSpaceID_None && 757 (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) { 758 return aResult.ParseNonNegativeIntValue(aValue); 759 } 760 761 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 762 aMaybeScriptedPrincipal, aResult); 763 } 764 765 void HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType, 766 JS::Handle<JS::Value> aParams, 767 nsAString& aDataURL, 768 nsIPrincipal& aSubjectPrincipal, 769 ErrorResult& aRv) { 770 bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef(); 771 772 if (!CallerCanRead(aSubjectPrincipal)) { 773 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 774 return; 775 } 776 777 nsString dataURL; 778 nsresult rv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, dataURL); 779 if (recheckCanRead && !CallerCanRead(aSubjectPrincipal)) { 780 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 781 return; 782 } 783 784 if (NS_FAILED(rv)) { 785 aDataURL.Assign(u"data:,"_ns); 786 return; 787 } 788 789 aDataURL = std::move(dataURL); 790 } 791 792 void HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback) { 793 mPrintCallback = aCallback; 794 } 795 796 PrintCallback* HTMLCanvasElement::GetMozPrintCallback() const { 797 if (mOriginalCanvas) { 798 return mOriginalCanvas->GetMozPrintCallback(); 799 } 800 return mPrintCallback; 801 } 802 803 static uint32_t sCaptureSourceId = 0; 804 class CanvasCaptureTrackSource : public MediaStreamTrackSource { 805 public: 806 NS_DECL_ISUPPORTS_INHERITED 807 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource, 808 MediaStreamTrackSource) 809 810 CanvasCaptureTrackSource(nsIPrincipal* aPrincipal, 811 CanvasCaptureMediaStream* aCaptureStream) 812 : MediaStreamTrackSource( 813 aPrincipal, nsString(), 814 TrackingId(TrackingId::Source::Canvas, sCaptureSourceId++, 815 TrackingId::TrackAcrossProcesses::Yes)), 816 mCaptureStream(aCaptureStream) {} 817 818 MediaSourceEnum GetMediaSource() const override { 819 return MediaSourceEnum::Other; 820 } 821 822 bool HasAlpha() const override { 823 if (!mCaptureStream || !mCaptureStream->Canvas()) { 824 // In cycle-collection 825 return false; 826 } 827 return !mCaptureStream->Canvas()->GetIsOpaque(); 828 } 829 830 void GetSettings(dom::MediaTrackSettings& aResult) override { 831 aResult.mWidth.Construct(mCaptureStream->Canvas()->Width()); 832 aResult.mHeight.Construct(mCaptureStream->Canvas()->Height()); 833 } 834 835 void Stop() override { 836 if (!mCaptureStream) { 837 return; 838 } 839 840 mCaptureStream->StopCapture(); 841 } 842 843 void Disable() override {} 844 845 void Enable() override {} 846 847 private: 848 virtual ~CanvasCaptureTrackSource() = default; 849 850 RefPtr<CanvasCaptureMediaStream> mCaptureStream; 851 }; 852 853 NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource, MediaStreamTrackSource) 854 NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource, MediaStreamTrackSource) 855 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasCaptureTrackSource) 856 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource) 857 NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource, 858 MediaStreamTrackSource, mCaptureStream) 859 860 already_AddRefed<CanvasCaptureMediaStream> HTMLCanvasElement::CaptureStream( 861 const Optional<double>& aFrameRate, nsIPrincipal& aSubjectPrincipal, 862 ErrorResult& aRv) { 863 if (IsWriteOnly()) { 864 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 865 return nullptr; 866 } 867 868 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 869 if (!window) { 870 aRv.Throw(NS_ERROR_FAILURE); 871 return nullptr; 872 } 873 874 // Check if we transferred the OffscreenCanvas to a DOM worker. This is not 875 // defined by the spec yet, so it is better to fail now than implement 876 // something not compliant: 877 // https://github.com/w3c/mediacapture-fromelement/issues/65 878 // https://github.com/w3c/mediacapture-extensions/pull/26 879 // https://github.com/web-platform-tests/wpt/issues/21102 880 if (mOffscreenDisplay && 881 NS_WARN_IF(!mOffscreenDisplay->CanElementCaptureStream())) { 882 aRv.ThrowNotSupportedError( 883 "Capture stream not supported when OffscreenCanvas transferred to " 884 "worker"); 885 return nullptr; 886 } 887 888 auto stream = MakeRefPtr<CanvasCaptureMediaStream>(window, this); 889 890 nsCOMPtr<nsIPrincipal> principal = NodePrincipal(); 891 nsresult rv = stream->Init(aFrameRate, principal); 892 if (NS_FAILED(rv)) { 893 aRv.Throw(rv); 894 return nullptr; 895 } 896 897 RefPtr<MediaStreamTrack> track = 898 new VideoStreamTrack(window, stream->GetSourceStream(), 899 new CanvasCaptureTrackSource(principal, stream)); 900 stream->AddTrackInternal(track); 901 902 // Check site-specific permission and display prompt if appropriate. 903 // If no permission, arrange for the frame capture listener to return 904 // all-white, opaque image data. 905 CanvasUtils::ImageExtraction extractionBehaviour = 906 CanvasUtils::ImageExtractionResult(this, nullptr, &aSubjectPrincipal); 907 908 rv = RegisterFrameCaptureListener( 909 stream->FrameCaptureListener(), 910 extractionBehaviour == CanvasUtils::ImageExtraction::Placeholder); 911 if (NS_FAILED(rv)) { 912 aRv.Throw(rv); 913 return nullptr; 914 } 915 916 return stream.forget(); 917 } 918 919 nsresult HTMLCanvasElement::ExtractData(JSContext* aCx, 920 nsIPrincipal& aSubjectPrincipal, 921 nsAString& aType, 922 const nsAString& aOptions, 923 nsIInputStream** aStream) { 924 // Check site-specific permission and display prompt if appropriate. 925 // If no permission, return all-white, opaque image data. 926 CanvasUtils::ImageExtraction extractionBehaviour = 927 CanvasUtils::ImageExtractionResult(this, aCx, &aSubjectPrincipal); 928 929 if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { 930 auto size = GetWidthHeight(); 931 auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(), 932 CanvasExtractionAPI::ToDataURL, size, 933 GetCurrentContext()); 934 OwnerDoc()->RecordCanvasUsage(usage); 935 } 936 937 nsCString randomizationKey = VoidCString(); 938 if (extractionBehaviour == CanvasUtils::ImageExtraction::EfficientRandomize) { 939 nsRFPService::GetFingerprintingRandomizationKeyAsString( 940 GetCookieJarSettings(), randomizationKey); 941 } 942 943 return ImageEncoder::ExtractData(aType, aOptions, GetSize(), 944 extractionBehaviour, randomizationKey, 945 mCurrentContext, mOffscreenDisplay, aStream); 946 } 947 948 nsresult HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, 949 nsIPrincipal& aSubjectPrincipal, 950 const nsAString& aMimeType, 951 const JS::Value& aEncoderOptions, 952 nsAString& aDataURL) { 953 CSSIntSize size = GetWidthHeight(); 954 if (size.height == 0 || size.width == 0) { 955 aDataURL = u"data:,"_ns; 956 return NS_OK; 957 } 958 959 nsAutoString type; 960 nsContentUtils::ASCIIToLower(aMimeType, type); 961 962 nsAutoString params; 963 bool usingCustomParseOptions; 964 nsresult rv = 965 ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions); 966 if (NS_FAILED(rv)) { 967 return rv; 968 } 969 970 nsCOMPtr<nsIInputStream> stream; 971 rv = 972 ExtractData(aCx, aSubjectPrincipal, type, params, getter_AddRefs(stream)); 973 974 // If there are unrecognized custom parse options, we should fall back to 975 // the default values for the encoder without any options at all. 976 if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) { 977 rv = ExtractData(aCx, aSubjectPrincipal, type, u""_ns, 978 getter_AddRefs(stream)); 979 } 980 981 NS_ENSURE_SUCCESS(rv, rv); 982 983 // build data URL string 984 aDataURL = u"data:"_ns + type + u";base64,"_ns; 985 986 uint64_t count; 987 rv = stream->Available(&count); 988 NS_ENSURE_SUCCESS(rv, rv); 989 NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); 990 991 return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, 992 aDataURL.Length()); 993 } 994 995 UniquePtr<uint8_t[]> HTMLCanvasElement::GetImageBuffer( 996 CanvasUtils::ImageExtraction aExtractionBehavior, int32_t* aOutFormat, 997 gfx::IntSize* aOutImageSize) { 998 if (mCurrentContext) { 999 return mCurrentContext->GetImageBuffer(aExtractionBehavior, aOutFormat, 1000 aOutImageSize); 1001 } 1002 if (mOffscreenDisplay) { 1003 return mOffscreenDisplay->GetImageBuffer(aExtractionBehavior, aOutFormat, 1004 aOutImageSize); 1005 } 1006 return nullptr; 1007 } 1008 1009 void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback, 1010 const nsAString& aType, 1011 JS::Handle<JS::Value> aParams, 1012 nsIPrincipal& aSubjectPrincipal, 1013 ErrorResult& aRv) { 1014 bool recheckCanRead = mOffscreenDisplay && mOffscreenDisplay->HasWorkerRef(); 1015 1016 if (!CallerCanRead(aSubjectPrincipal)) { 1017 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 1018 return; 1019 } 1020 1021 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject(); 1022 MOZ_ASSERT(global); 1023 1024 CSSIntSize elemSize = GetWidthHeight(); 1025 if (elemSize.width == 0 || elemSize.height == 0) { 1026 // According to spec, blob should return null if either its horizontal 1027 // dimension or its vertical dimension is zero. See link below. 1028 // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-toblob 1029 OwnerDoc()->Dispatch(NewRunnableMethod<Blob*, const char*>( 1030 "dom::HTMLCanvasElement::ToBlob", &aCallback, 1031 static_cast<void (BlobCallback::*)(Blob*, const char*)>( 1032 &BlobCallback::Call), 1033 nullptr, nullptr)); 1034 return; 1035 } 1036 1037 // Check site-specific permission and display prompt if appropriate. 1038 // If no permission, return all-white, opaque image data. 1039 CanvasUtils::ImageExtraction extractionBehaviour = 1040 CanvasUtils::ImageExtractionResult(this, aCx, &aSubjectPrincipal); 1041 1042 // Encoder callback when encoding is complete. 1043 class EncodeCallback : public EncodeCompleteCallback { 1044 public: 1045 EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback, 1046 OffscreenCanvasDisplayHelper* aOffscreenDisplay, 1047 nsIPrincipal* aSubjectPrincipal) 1048 : mGlobal(aGlobal), 1049 mBlobCallback(aCallback), 1050 mOffscreenDisplay(aOffscreenDisplay), 1051 mSubjectPrincipal(aSubjectPrincipal) {} 1052 1053 // This is called on main thread. 1054 MOZ_CAN_RUN_SCRIPT 1055 nsresult ReceiveBlobImpl(already_AddRefed<BlobImpl> aBlobImpl) override { 1056 MOZ_ASSERT(NS_IsMainThread()); 1057 1058 RefPtr<BlobImpl> blobImpl = aBlobImpl; 1059 1060 RefPtr<Blob> blob; 1061 1062 if (blobImpl && (!mOffscreenDisplay || 1063 mOffscreenDisplay->CallerCanRead(*mSubjectPrincipal))) { 1064 blob = Blob::Create(mGlobal, blobImpl); 1065 } 1066 1067 RefPtr<BlobCallback> callback(std::move(mBlobCallback)); 1068 ErrorResult rv; 1069 1070 callback->Call(blob, rv); 1071 1072 mGlobal = nullptr; 1073 MOZ_ASSERT(!mBlobCallback); 1074 1075 return rv.StealNSResult(); 1076 } 1077 1078 bool CanBeDeletedOnAnyThread() override { 1079 // EncodeCallback is used from the main thread only. 1080 return false; 1081 } 1082 1083 nsCOMPtr<nsIGlobalObject> mGlobal; 1084 RefPtr<BlobCallback> mBlobCallback; 1085 RefPtr<OffscreenCanvasDisplayHelper> mOffscreenDisplay; 1086 RefPtr<nsIPrincipal> mSubjectPrincipal; 1087 }; 1088 1089 RefPtr<EncodeCompleteCallback> callback = new EncodeCallback( 1090 global, &aCallback, recheckCanRead ? mOffscreenDisplay.get() : nullptr, 1091 recheckCanRead ? &aSubjectPrincipal : nullptr); 1092 1093 auto usage = CanvasUsage::CreateUsage(false, GetCurrentContextType(), 1094 CanvasExtractionAPI::ToBlob, 1095 GetWidthHeight(), GetCurrentContext()); 1096 if (extractionBehaviour != CanvasUtils::ImageExtraction::Placeholder) { 1097 OwnerDoc()->RecordCanvasUsage(usage); 1098 } 1099 1100 CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams, 1101 extractionBehaviour, aRv); 1102 } 1103 1104 OffscreenCanvas* HTMLCanvasElement::TransferControlToOffscreen( 1105 ErrorResult& aRv) { 1106 if (mCurrentContext || mOffscreenCanvas) { 1107 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1108 return nullptr; 1109 } 1110 1111 MOZ_ASSERT(!mOffscreenDisplay); 1112 1113 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); 1114 if (!win) { 1115 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1116 return nullptr; 1117 } 1118 1119 LayersBackend backend = LayersBackend::LAYERS_NONE; 1120 if (nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc())) { 1121 if (WindowRenderer* renderer = docWidget->GetWindowRenderer()) { 1122 backend = renderer->GetCompositorBackendType(); 1123 } 1124 } 1125 1126 CSSIntSize sz = GetWidthHeight(); 1127 mOffscreenDisplay = 1128 MakeRefPtr<OffscreenCanvasDisplayHelper>(this, sz.width, sz.height); 1129 mOffscreenCanvas = new OffscreenCanvas(win->AsGlobal(), sz.width, sz.height, 1130 backend, do_AddRef(mOffscreenDisplay)); 1131 if (mWriteOnly) { 1132 mOffscreenCanvas->SetWriteOnly(mExpandedReader); 1133 } 1134 1135 if (!mContextObserver) { 1136 mContextObserver = new HTMLCanvasElementObserver(this); 1137 } 1138 1139 return mOffscreenCanvas; 1140 } 1141 1142 nsresult HTMLCanvasElement::GetContext(const nsAString& aContextId, 1143 nsISupports** aContext) { 1144 ErrorResult rv; 1145 mMaybeModified = true; // For FirstContentfulPaint 1146 *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take(); 1147 return rv.StealNSResult(); 1148 } 1149 1150 already_AddRefed<nsISupports> HTMLCanvasElement::GetContext( 1151 JSContext* aCx, const nsAString& aContextId, 1152 JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { 1153 if (mOffscreenCanvas) { 1154 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1155 return nullptr; 1156 } 1157 1158 mMaybeModified = true; // For FirstContentfulPaint 1159 return CanvasRenderingContextHelper::GetOrCreateContext( 1160 aCx, aContextId, 1161 aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue, aRv); 1162 } 1163 1164 CSSIntSize HTMLCanvasElement::GetSize() { return GetWidthHeight(); } 1165 1166 bool HTMLCanvasElement::IsWriteOnly() const { 1167 if (mOffscreenDisplay && mOffscreenDisplay->IsWriteOnly()) { 1168 return true; 1169 } 1170 return mWriteOnly; 1171 } 1172 1173 void HTMLCanvasElement::SetWriteOnly( 1174 nsIPrincipal* aExpandedReader /* = nullptr */) { 1175 mExpandedReader = aExpandedReader; 1176 mWriteOnly = true; 1177 if (mOffscreenCanvas) { 1178 mOffscreenCanvas->SetWriteOnly(aExpandedReader); 1179 } 1180 } 1181 1182 bool HTMLCanvasElement::CallerCanRead(nsIPrincipal& aPrincipal) const { 1183 if (mOffscreenDisplay && !mOffscreenDisplay->CallerCanRead(aPrincipal)) { 1184 return false; 1185 } 1186 1187 if (!mWriteOnly) { 1188 return true; 1189 } 1190 1191 // If mExpandedReader is set, this canvas was tainted only by 1192 // mExpandedReader's resources. So allow reading if the subject 1193 // principal subsumes mExpandedReader. 1194 if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) { 1195 return true; 1196 } 1197 1198 return nsContentUtils::PrincipalHasPermission(aPrincipal, 1199 nsGkAtoms::all_urlsPermission); 1200 } 1201 1202 void HTMLCanvasElement::SetWidth(uint32_t aWidth, ErrorResult& aRv) { 1203 if (mOffscreenCanvas) { 1204 aRv.ThrowInvalidStateError( 1205 "Cannot set width of placeholder canvas transferred to " 1206 "OffscreenCanvas."); 1207 return; 1208 } 1209 1210 SetUnsignedIntAttr(nsGkAtoms::width, aWidth, kFallbackIntrinsicWidthInPixels, 1211 aRv); 1212 } 1213 1214 void HTMLCanvasElement::SetHeight(uint32_t aHeight, ErrorResult& aRv) { 1215 if (mOffscreenCanvas) { 1216 aRv.ThrowInvalidStateError( 1217 "Cannot set height of placeholder canvas transferred to " 1218 "OffscreenCanvas."); 1219 return; 1220 } 1221 1222 SetUnsignedIntAttr(nsGkAtoms::height, aHeight, 1223 kFallbackIntrinsicHeightInPixels, aRv); 1224 } 1225 1226 void HTMLCanvasElement::SetSize(const nsIntSize& aSize, ErrorResult& aRv) { 1227 if (mOffscreenCanvas) { 1228 aRv.ThrowInvalidStateError( 1229 "Cannot set width of placeholder canvas transferred to " 1230 "OffscreenCanvas."); 1231 return; 1232 } 1233 1234 if (NS_WARN_IF(aSize.IsEmpty())) { 1235 aRv.ThrowRangeError("Canvas size is empty, must be non-empty."); 1236 return; 1237 } 1238 1239 SetUnsignedIntAttr(nsGkAtoms::width, aSize.width, 1240 kFallbackIntrinsicWidthInPixels, aRv); 1241 MOZ_ASSERT(!aRv.Failed()); 1242 SetUnsignedIntAttr(nsGkAtoms::height, aSize.height, 1243 kFallbackIntrinsicHeightInPixels, aRv); 1244 MOZ_ASSERT(!aRv.Failed()); 1245 } 1246 1247 void HTMLCanvasElement::FlushOffscreenCanvas() { 1248 if (mOffscreenDisplay) { 1249 mOffscreenDisplay->FlushForDisplay(); 1250 } 1251 } 1252 1253 void HTMLCanvasElement::InvalidateCanvasPlaceholder(uint32_t aWidth, 1254 uint32_t aHeight) { 1255 ErrorResult rv; 1256 SetUnsignedIntAttr(nsGkAtoms::width, aWidth, kFallbackIntrinsicWidthInPixels, 1257 rv); 1258 MOZ_ASSERT(!rv.Failed()); 1259 SetUnsignedIntAttr(nsGkAtoms::height, aHeight, 1260 kFallbackIntrinsicHeightInPixels, rv); 1261 MOZ_ASSERT(!rv.Failed()); 1262 } 1263 1264 static bool InvalidateCanvasData(nsIFrame* aFrame, uint32_t aKey) { 1265 RefPtr data = GetWebRenderUserData<WebRenderCanvasData>(aFrame, aKey); 1266 if (!data) { 1267 return false; 1268 } 1269 CanvasRenderer* renderer = data->GetCanvasRenderer(); 1270 if (!renderer) { 1271 return false; 1272 } 1273 renderer->SetDirty(); 1274 aFrame->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); 1275 return true; 1276 } 1277 1278 void HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect) { 1279 // Cache the current ImageContainer to avoid contention on the mutex. 1280 if (mOffscreenDisplay) { 1281 mImageContainer = mOffscreenDisplay->GetImageContainer(); 1282 } 1283 1284 // We don't need to flush anything here; if there's no frame or if 1285 // we plan to reframe we don't need to invalidate it anyway. 1286 nsIFrame* frame = GetPrimaryFrame(); 1287 if (!frame) { 1288 return; 1289 } 1290 1291 // When using layers-free WebRender, we cannot invalidate the layer (because 1292 // there isn't one). Instead, we mark the CanvasRenderer dirty and scheduling 1293 // an empty transaction which is effectively equivalent. 1294 bool invalidated = false; 1295 for (auto* item : frame->DisplayItems()) { 1296 if (item->GetType() == DisplayItemType::TYPE_CANVAS) { 1297 invalidated |= InvalidateCanvasData(frame, item->GetPerFrameKey()); 1298 } 1299 } 1300 invalidated = 1301 invalidated || 1302 InvalidateCanvasData(frame, uint32_t(DisplayItemType::TYPE_CANVAS)); 1303 if (!invalidated) { 1304 if (damageRect) { 1305 CSSIntSize size = GetWidthHeight(); 1306 if (size.width != 0 && size.height != 0) { 1307 gfx::IntRect invalRect = gfx::IntRect::Truncate(*damageRect); 1308 frame->InvalidateLayer(DisplayItemType::TYPE_CANVAS, &invalRect); 1309 } 1310 } else { 1311 frame->InvalidateLayer(DisplayItemType::TYPE_CANVAS); 1312 } 1313 1314 // This path is taken in two situations: 1315 // 1) WebRender is enabled and has not yet processed a display list. 1316 // 2) WebRender is disabled and layer invalidation failed. 1317 // In both cases, schedule a full paint to properly update canvas. 1318 frame->SchedulePaint(nsIFrame::PAINT_DEFAULT, false); 1319 } 1320 1321 /* 1322 * Treat canvas invalidations as animation activity for JS. Frequently 1323 * invalidating a canvas will feed into heuristics and cause JIT code to be 1324 * kept around longer, for smoother animations. 1325 */ 1326 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { 1327 if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) { 1328 js::NotifyAnimationActivity(obj); 1329 } 1330 } 1331 } 1332 1333 void HTMLCanvasElement::InvalidateCanvas() { 1334 // We don't need to flush anything here; if there's no frame or if 1335 // we plan to reframe we don't need to invalidate it anyway. 1336 nsIFrame* frame = GetPrimaryFrame(); 1337 if (!frame) return; 1338 1339 frame->InvalidateFrame(); 1340 } 1341 1342 bool HTMLCanvasElement::GetIsOpaque() { 1343 if (mCurrentContext) { 1344 return mCurrentContext->GetIsOpaque(); 1345 } 1346 1347 return GetOpaqueAttr(); 1348 } 1349 1350 bool HTMLCanvasElement::GetOpaqueAttr() { 1351 return HasAttr(nsGkAtoms::moz_opaque); 1352 } 1353 1354 CanvasContextType HTMLCanvasElement::GetCurrentContextType() { 1355 if (mCurrentContextType == CanvasContextType::NoContext && 1356 mOffscreenDisplay) { 1357 mCurrentContextType = mOffscreenDisplay->GetContextType(); 1358 } 1359 return mCurrentContextType; 1360 } 1361 1362 already_AddRefed<Image> HTMLCanvasElement::GetAsImage() { 1363 if (mOffscreenDisplay) { 1364 return mOffscreenDisplay->GetAsImage(); 1365 } 1366 1367 if (mCurrentContext) { 1368 return mCurrentContext->GetAsImage(); 1369 } 1370 1371 return nullptr; 1372 } 1373 1374 bool HTMLCanvasElement::UpdateWebRenderCanvasData( 1375 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) { 1376 MOZ_ASSERT(!mOffscreenDisplay); 1377 1378 if (mCurrentContext) { 1379 return mCurrentContext->UpdateWebRenderCanvasData(aBuilder, aCanvasData); 1380 } 1381 1382 // Clear CanvasRenderer of WebRenderCanvasData 1383 aCanvasData->ClearCanvasRenderer(); 1384 return false; 1385 } 1386 1387 bool HTMLCanvasElement::InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder, 1388 CanvasRenderer* aRenderer) { 1389 MOZ_ASSERT(!mOffscreenDisplay); 1390 1391 if (mCurrentContext) { 1392 return mCurrentContext->InitializeCanvasRenderer(aBuilder, aRenderer); 1393 } 1394 1395 return false; 1396 } 1397 1398 void HTMLCanvasElement::MarkContextClean() { 1399 if (!mCurrentContext) return; 1400 1401 mCurrentContext->MarkContextClean(); 1402 } 1403 1404 void HTMLCanvasElement::MarkContextCleanForFrameCapture() { 1405 if (!mCurrentContext) return; 1406 1407 mCurrentContext->MarkContextCleanForFrameCapture(); 1408 } 1409 1410 Watchable<FrameCaptureState>* HTMLCanvasElement::GetFrameCaptureState() { 1411 if (!mCurrentContext) { 1412 return nullptr; 1413 } 1414 return mCurrentContext->GetFrameCaptureState(); 1415 } 1416 1417 nsresult HTMLCanvasElement::RegisterFrameCaptureListener( 1418 FrameCaptureListener* aListener, bool aReturnPlaceholderData) { 1419 WeakPtr<FrameCaptureListener> listener = aListener; 1420 1421 if (mRequestedFrameListeners.Contains(listener)) { 1422 return NS_OK; 1423 } 1424 1425 if (!mRequestedFrameRefreshObserver) { 1426 PresShell* shell = nsContentUtils::FindPresShellForDocument(OwnerDoc()); 1427 if (NS_WARN_IF(!shell)) { 1428 return NS_ERROR_FAILURE; 1429 } 1430 1431 nsPresContext* context = shell->GetPresContext(); 1432 if (NS_WARN_IF(!context)) { 1433 return NS_ERROR_FAILURE; 1434 } 1435 1436 context = context->GetRootPresContext(); 1437 if (NS_WARN_IF(!context)) { 1438 return NS_ERROR_FAILURE; 1439 } 1440 1441 nsRefreshDriver* driver = context->RefreshDriver(); 1442 MOZ_ASSERT(driver); 1443 1444 mRequestedFrameRefreshObserver = 1445 new RequestedFrameRefreshObserver(this, driver, aReturnPlaceholderData); 1446 } else { 1447 mRequestedFrameRefreshObserver->SetReturnPlaceholderData( 1448 aReturnPlaceholderData); 1449 } 1450 1451 mRequestedFrameListeners.AppendElement(listener); 1452 mRequestedFrameRefreshObserver->Register(); 1453 return NS_OK; 1454 } 1455 1456 bool HTMLCanvasElement::IsFrameCaptureRequested(const TimeStamp& aTime) const { 1457 for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) { 1458 if (!listener) { 1459 continue; 1460 } 1461 1462 if (listener->FrameCaptureRequested(aTime)) { 1463 return true; 1464 } 1465 } 1466 return false; 1467 } 1468 1469 void HTMLCanvasElement::ProcessDestroyedFrameListeners() { 1470 // Remove destroyed listeners from the list. 1471 mRequestedFrameListeners.RemoveElementsBy( 1472 [](const auto& weakListener) { return !weakListener; }); 1473 1474 if (mRequestedFrameListeners.IsEmpty()) { 1475 mRequestedFrameRefreshObserver->Unregister(); 1476 } 1477 } 1478 1479 void HTMLCanvasElement::SetFrameCapture( 1480 already_AddRefed<SourceSurface> aSurface, const TimeStamp& aTime) { 1481 RefPtr<SourceSurface> surface = aSurface; 1482 RefPtr<SourceSurfaceImage> image = 1483 new SourceSurfaceImage(surface->GetSize(), surface); 1484 1485 for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) { 1486 if (!listener) { 1487 continue; 1488 } 1489 1490 RefPtr<Image> imageRefCopy = image.get(); 1491 listener->NewFrame(imageRefCopy.forget(), aTime); 1492 } 1493 } 1494 1495 already_AddRefed<SourceSurface> HTMLCanvasElement::GetSurfaceSnapshot( 1496 gfxAlphaType* const aOutAlphaType, DrawTarget* aTarget) { 1497 if (mCurrentContext) { 1498 return mCurrentContext->GetOptimizedSnapshot(aTarget, aOutAlphaType); 1499 } else if (mOffscreenDisplay) { 1500 return mOffscreenDisplay->GetSurfaceSnapshot(); 1501 } 1502 return nullptr; 1503 } 1504 1505 layers::LayersBackend HTMLCanvasElement::GetCompositorBackendType() const { 1506 nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc()); 1507 if (docWidget) { 1508 WindowRenderer* renderer = docWidget->GetWindowRenderer(); 1509 if (renderer) { 1510 return renderer->GetCompositorBackendType(); 1511 } 1512 } 1513 1514 return LayersBackend::LAYERS_NONE; 1515 } 1516 1517 void HTMLCanvasElement::OnMemoryPressure() { 1518 // FIXME(aosmond): We need to implement memory pressure handling for 1519 // OffscreenCanvas when it is on worker threads. See bug 1746260. 1520 1521 if (mCurrentContext) { 1522 mCurrentContext->OnMemoryPressure(); 1523 } 1524 } 1525 1526 void HTMLCanvasElement::OnDeviceReset() { 1527 if (!mOffscreenCanvas && mCurrentContext) { 1528 mCurrentContext->ResetBitmap(); 1529 } 1530 } 1531 1532 ClientWebGLContext* HTMLCanvasElement::GetWebGLContext() { 1533 if (GetCurrentContextType() != CanvasContextType::WebGL1 && 1534 GetCurrentContextType() != CanvasContextType::WebGL2) { 1535 return nullptr; 1536 } 1537 1538 return static_cast<ClientWebGLContext*>(GetCurrentContext()); 1539 } 1540 1541 webgpu::CanvasContext* HTMLCanvasElement::GetWebGPUContext() { 1542 if (GetCurrentContextType() != CanvasContextType::WebGPU) { 1543 return nullptr; 1544 } 1545 1546 return static_cast<webgpu::CanvasContext*>(GetCurrentContext()); 1547 } 1548 1549 } // namespace mozilla::dom