tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHTMLCanvasFrame.cpp (19037B)


      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 /* rendering object for the HTML <canvas> element */
      8 
      9 #include "nsHTMLCanvasFrame.h"
     10 
     11 #include "ActiveLayerTracker.h"
     12 #include "mozilla/Assertions.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/dom/HTMLCanvasElement.h"
     15 #include "mozilla/layers/ImageDataSerializer.h"
     16 #include "mozilla/layers/RenderRootStateManager.h"
     17 #include "mozilla/layers/WebRenderBridgeChild.h"
     18 #include "mozilla/layers/WebRenderCanvasRenderer.h"
     19 #include "mozilla/webgpu/CanvasContext.h"
     20 #include "nsDisplayList.h"
     21 #include "nsGkAtoms.h"
     22 #include "nsLayoutUtils.h"
     23 #include "nsStyleUtil.h"
     24 
     25 using namespace mozilla;
     26 using namespace mozilla::dom;
     27 using namespace mozilla::layers;
     28 using namespace mozilla::gfx;
     29 
     30 /* Helper for our nsIFrame::GetIntrinsicSize() impl. Takes the result of
     31 * "GetCanvasSize()" as a parameter, which may help avoid redundant
     32 * indirect calls to GetCanvasSize().
     33 *
     34 * @param aCanvasSizeInPx The canvas's size in CSS pixels, as returned
     35 *                        by GetCanvasSize().
     36 * @return The canvas's intrinsic size, as an IntrinsicSize object.
     37 */
     38 static IntrinsicSize IntrinsicSizeFromCanvasSize(
     39    const CSSIntSize& aCanvasSizeInPx) {
     40  return IntrinsicSize(CSSIntSize::ToAppUnits(aCanvasSizeInPx));
     41 }
     42 
     43 /* Helper for our nsIFrame::GetIntrinsicRatio() impl. Takes the result of
     44 * "GetCanvasSize()" as a parameter, which may help avoid redundant
     45 * indirect calls to GetCanvasSize().
     46 *
     47 * @return The canvas's intrinsic ratio.
     48 */
     49 static AspectRatio IntrinsicRatioFromCanvasSize(
     50    const CSSIntSize& aCanvasSizeInPx) {
     51  return AspectRatio::FromSize(aCanvasSizeInPx);
     52 }
     53 
     54 class nsDisplayCanvas final : public nsPaintedDisplayItem {
     55 public:
     56  nsDisplayCanvas(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
     57      : nsPaintedDisplayItem(aBuilder, aFrame) {
     58    MOZ_COUNT_CTOR(nsDisplayCanvas);
     59  }
     60 
     61  MOZ_COUNTED_DTOR_FINAL(nsDisplayCanvas)
     62 
     63  NS_DISPLAY_DECL_NAME("nsDisplayCanvas", TYPE_CANVAS)
     64 
     65  nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
     66                           bool* aSnap) const override {
     67    *aSnap = false;
     68    auto* f = static_cast<nsHTMLCanvasFrame*>(Frame());
     69    auto* canvas = HTMLCanvasElement::FromNode(f->GetContent());
     70    if (!canvas->GetIsOpaque()) {
     71      return {};
     72    }
     73    // OK, the entire region painted by the canvas is opaque. But what is
     74    // that region? It's the canvas's "dest rect" (controlled by the
     75    // object-fit/object-position CSS properties), clipped to the container's
     76    // ink overflow box (which is what GetBounds() returns). So, we grab those
     77    // rects and intersect them.
     78    const nsRect constraintRect = GetBounds(aBuilder, aSnap);
     79    const nsRect destRect =
     80        f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame());
     81    return nsRegion(destRect.Intersect(constraintRect));
     82  }
     83 
     84  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
     85    *aSnap = true;
     86    return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
     87  }
     88 
     89  bool CreateWebRenderCommands(
     90      mozilla::wr::DisplayListBuilder& aBuilder,
     91      wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc,
     92      mozilla::layers::RenderRootStateManager* aManager,
     93      nsDisplayListBuilder* aDisplayListBuilder) override {
     94    HTMLCanvasElement* element =
     95        static_cast<HTMLCanvasElement*>(mFrame->GetContent());
     96    element->HandlePrintCallback(mFrame->PresContext());
     97 
     98    if (element->IsOffscreen()) {
     99      // If we are offscreen, then we either display via an ImageContainer
    100      // which is updated asynchronously, likely from a worker thread, or a
    101      // CompositableHandle managed inside the compositor process. There is
    102      // nothing to paint until the owner attaches it.
    103 
    104      element->FlushOffscreenCanvas();
    105 
    106      auto* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
    107      nsRect dest = canvasFrame->GetDestRect(
    108          mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
    109      LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
    110          dest, mFrame->PresContext()->AppUnitsPerDevPixel());
    111 
    112      RefPtr<ImageContainer> container = element->GetImageContainer();
    113      if (container) {
    114        MOZ_ASSERT(container->IsAsync());
    115        aManager->CommandBuilder().PushImage(this, container, aBuilder,
    116                                             aResources, aSc, bounds, bounds);
    117        return true;
    118      }
    119 
    120      return true;
    121    }
    122 
    123    switch (element->GetCurrentContextType()) {
    124      case CanvasContextType::Canvas2D:
    125      case CanvasContextType::WebGL1:
    126      case CanvasContextType::WebGL2:
    127      case CanvasContextType::WebGPU: {
    128        bool isRecycled;
    129        RefPtr<WebRenderCanvasData> canvasData =
    130            aManager->CommandBuilder()
    131                .CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(
    132                    this, &isRecycled);
    133        auto* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
    134        if (!canvasFrame->UpdateWebRenderCanvasData(aDisplayListBuilder,
    135                                                    canvasData)) {
    136          return true;
    137        }
    138        WebRenderCanvasRendererAsync* data = canvasData->GetCanvasRenderer();
    139        MOZ_ASSERT(data);
    140        data->UpdateCompositableClient();
    141 
    142        // Push IFrame for async image pipeline.
    143        // XXX Remove this once partial display list update is supported.
    144        nsRect dest = canvasFrame->GetDestRect(
    145            mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
    146        LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
    147            dest, mFrame->PresContext()->AppUnitsPerDevPixel());
    148 
    149        // We don't push a stacking context for this async image pipeline here.
    150        // Instead, we do it inside the iframe that hosts the image. As a
    151        // result, a bunch of the calculations normally done as part of that
    152        // stacking context need to be done manually and pushed over to the
    153        // parent side, where it will be done when we build the display list for
    154        // the iframe. That happens in WebRenderCompositableHolder.s2);
    155        aBuilder.PushIFrame(bounds, !BackfaceIsHidden(),
    156                            data->GetPipelineId().ref(),
    157                            /*ignoreMissingPipelines*/ true);
    158 
    159        LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), bounds.Size());
    160        auto filter = wr::ToImageRendering(mFrame->UsedImageRendering());
    161        auto mixBlendMode = wr::MixBlendMode::Normal;
    162        aManager->WrBridge()->AddWebRenderParentCommand(
    163            OpUpdateAsyncImagePipeline(data->GetPipelineId().value(), scBounds,
    164                                       wr::WrRotation::Degree0, filter,
    165                                       mixBlendMode));
    166        break;
    167      }
    168      case CanvasContextType::ImageBitmap: {
    169        nsHTMLCanvasFrame* canvasFrame =
    170            static_cast<nsHTMLCanvasFrame*>(mFrame);
    171        CSSIntSize canvasSizeInPx = canvasFrame->GetCanvasSize();
    172        if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0) {
    173          return true;
    174        }
    175        bool isRecycled;
    176        RefPtr<WebRenderCanvasData> canvasData =
    177            aManager->CommandBuilder()
    178                .CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(
    179                    this, &isRecycled);
    180        if (!canvasFrame->UpdateWebRenderCanvasData(aDisplayListBuilder,
    181                                                    canvasData)) {
    182          canvasData->ClearImageContainer();
    183          return true;
    184        }
    185 
    186        nsRect dest = canvasFrame->GetDestRect(
    187            mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame());
    188 
    189        LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
    190            dest, mFrame->PresContext()->AppUnitsPerDevPixel());
    191 
    192        aManager->CommandBuilder().PushImage(
    193            this, canvasData->GetImageContainer(), aBuilder, aResources, aSc,
    194            bounds, bounds);
    195        break;
    196      }
    197      case CanvasContextType::NoContext:
    198        break;
    199      default:
    200        MOZ_ASSERT_UNREACHABLE("unknown canvas context type");
    201    }
    202    return true;
    203  }
    204 
    205  // FirstContentfulPaint is supposed to ignore "white" canvases.  We use
    206  // MaybeModified (if GetContext() was called on the canvas) as a standin for
    207  // "white"
    208  bool IsContentful() const override {
    209    nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
    210    HTMLCanvasElement* canvas = HTMLCanvasElement::FromNode(f->GetContent());
    211    return canvas->MaybeModified();
    212  }
    213 
    214  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
    215    auto* f = static_cast<nsHTMLCanvasFrame*>(Frame());
    216    auto* canvas = HTMLCanvasElement::FromNode(f->GetContent());
    217 
    218    CSSIntSize canvasSizeInPx = f->GetCanvasSize();
    219    nsPresContext* presContext = f->PresContext();
    220    canvas->HandlePrintCallback(presContext);
    221 
    222    if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0) {
    223      return;
    224    }
    225 
    226    nsRect dest = f->GetDestRect(mFrame->GetContentRectRelativeToSelf() +
    227                                 ToReferenceFrame());
    228    if (dest.IsEmpty()) {
    229      return;
    230    }
    231 
    232    gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
    233 
    234    if (RefPtr<layers::Image> image = canvas->GetAsImage()) {
    235      gfxRect destGFXRect = presContext->AppUnitsToGfxUnits(dest);
    236 
    237      // Transform the canvas into the right place
    238      gfxPoint p = destGFXRect.TopLeft();
    239      Matrix transform = Matrix::Translation(p.x, p.y);
    240      transform.PreScale(destGFXRect.Width() / canvasSizeInPx.width,
    241                         destGFXRect.Height() / canvasSizeInPx.height);
    242 
    243      aCtx->SetMatrix(
    244          gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
    245 
    246      RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
    247      if (!surface || !surface->IsValid()) {
    248        return;
    249      }
    250 
    251      transform = gfxUtils::SnapTransform(
    252          transform, gfxRect(0, 0, canvasSizeInPx.width, canvasSizeInPx.height),
    253          nullptr);
    254      aCtx->Multiply(transform);
    255 
    256      aCtx->GetDrawTarget()->FillRect(
    257          Rect(0, 0, canvasSizeInPx.width, canvasSizeInPx.height),
    258          SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
    259                         nsLayoutUtils::GetSamplingFilterForFrame(f)));
    260      return;
    261    }
    262 
    263    if (canvas->IsOffscreen()) {
    264      return;
    265    }
    266 
    267    RefPtr<CanvasRenderer> renderer = new CanvasRenderer();
    268    if (!canvas->InitializeCanvasRenderer(aBuilder, renderer)) {
    269      return;
    270    }
    271    renderer->FirePreTransactionCallback();
    272    const auto snapshot = renderer->BorrowSnapshot();
    273    if (!snapshot) {
    274      return;
    275    }
    276    const auto& surface = snapshot->mSurf;
    277    DrawTarget& dt = *aCtx->GetDrawTarget();
    278    gfx::Rect destRect =
    279        NSRectToSnappedRect(dest, presContext->AppUnitsPerDevPixel(), dt);
    280 
    281    if (!renderer->YIsDown()) {
    282      // Calculate y-coord that is as far below the bottom of destGFXRect as
    283      // the origin was above the top, then reflect about that.
    284      float y = destRect.Y() + destRect.YMost();
    285      Matrix transform = Matrix::Translation(0.0f, y).PreScale(1.0f, -1.0f);
    286      aCtx->Multiply(transform);
    287    }
    288 
    289    const auto& srcRect = surface->GetRect();
    290    dt.DrawSurface(
    291        surface, destRect,
    292        Rect(float(srcRect.X()), float(srcRect.Y()), float(srcRect.Width()),
    293             float(srcRect.Height())),
    294        DrawSurfaceOptions(nsLayoutUtils::GetSamplingFilterForFrame(f)));
    295 
    296    renderer->FireDidTransactionCallback();
    297    renderer->ResetDirty();
    298  }
    299 };
    300 
    301 nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
    302  return new (aPresShell)
    303      nsHTMLCanvasFrame(aStyle, aPresShell->GetPresContext());
    304 }
    305 
    306 NS_QUERYFRAME_HEAD(nsHTMLCanvasFrame)
    307  NS_QUERYFRAME_ENTRY(nsHTMLCanvasFrame)
    308 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
    309 
    310 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame)
    311 
    312 void nsHTMLCanvasFrame::Destroy(DestroyContext& aContext) {
    313  if (IsPrimaryFrame()) {
    314    HTMLCanvasElement::FromNode(*mContent)->ResetPrintCallback();
    315  }
    316  nsContainerFrame::Destroy(aContext);
    317 }
    318 
    319 nsHTMLCanvasFrame::~nsHTMLCanvasFrame() = default;
    320 
    321 CSSIntSize nsHTMLCanvasFrame::GetCanvasSize() const {
    322  CSSIntSize size;
    323  if (auto* canvas = HTMLCanvasElement::FromNodeOrNull(GetContent())) {
    324    size = canvas->GetSize();
    325    MOZ_ASSERT(size.width >= 0 && size.height >= 0,
    326               "we should've required <canvas> width/height attrs to be "
    327               "unsigned (non-negative) values");
    328  } else {
    329    MOZ_ASSERT_UNREACHABLE("couldn't get canvas size");
    330  }
    331  return size;
    332 }
    333 
    334 nscoord nsHTMLCanvasFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
    335                                          IntrinsicISizeType aType) {
    336  if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
    337    return *containISize;
    338  }
    339  bool vertical = GetWritingMode().IsVertical();
    340  return nsPresContext::CSSPixelsToAppUnits(vertical ? GetCanvasSize().height
    341                                                     : GetCanvasSize().width);
    342 }
    343 
    344 /* virtual */
    345 IntrinsicSize nsHTMLCanvasFrame::GetIntrinsicSize() {
    346  const auto containAxes = GetContainSizeAxes();
    347  IntrinsicSize size = containAxes.IsBoth()
    348                           ? IntrinsicSize(0, 0)
    349                           : IntrinsicSizeFromCanvasSize(GetCanvasSize());
    350  return FinishIntrinsicSize(containAxes, size);
    351 }
    352 
    353 /* virtual */
    354 AspectRatio nsHTMLCanvasFrame::GetIntrinsicRatio() const {
    355  if (GetContainSizeAxes().IsAny()) {
    356    return AspectRatio();
    357  }
    358 
    359  return IntrinsicRatioFromCanvasSize(GetCanvasSize());
    360 }
    361 
    362 /* virtual */
    363 nsIFrame::SizeComputationResult nsHTMLCanvasFrame::ComputeSize(
    364    const SizeComputationInput& aSizingInput, WritingMode aWM,
    365    const LogicalSize& aCBSize, nscoord aAvailableISize,
    366    const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
    367    const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
    368  return {ComputeSizeWithIntrinsicDimensions(
    369              aSizingInput.mRenderingContext, aWM, GetIntrinsicSize(),
    370              GetAspectRatio(), aCBSize, aMargin, aBorderPadding,
    371              aSizeOverrides, aFlags),
    372          AspectRatioUsage::None};
    373 }
    374 
    375 nsRect nsHTMLCanvasFrame::GetDestRect(const nsRect& aFrameContentBox) const {
    376  const CSSIntSize canvasSizeInPx = GetCanvasSize();
    377  IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
    378  AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
    379  return nsLayoutUtils::ComputeObjectDestRect(aFrameContentBox, intrinsicSize,
    380                                              intrinsicRatio, StylePosition());
    381 }
    382 
    383 void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
    384                               ReflowOutput& aMetrics,
    385                               const ReflowInput& aReflowInput,
    386                               nsReflowStatus& aStatus) {
    387  MarkInReflow();
    388  DO_GLOBAL_REFLOW_COUNT("nsHTMLCanvasFrame");
    389  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    390  NS_FRAME_TRACE(
    391      NS_FRAME_TRACE_CALLS,
    392      ("enter nsHTMLCanvasFrame::Reflow: availSize=%d,%d",
    393       aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
    394 
    395  MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
    396 
    397  WritingMode wm = aReflowInput.GetWritingMode();
    398  const LogicalSize finalSize = aReflowInput.ComputedSizeWithBorderPadding(wm);
    399 
    400  aMetrics.SetSize(wm, finalSize);
    401  aMetrics.SetOverflowAreasToDesiredBounds();
    402  aMetrics.mOverflowAreas.UnionAllWith(
    403      GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf()));
    404  FinishAndStoreOverflow(&aMetrics);
    405 
    406  // Reflow the single anon block child.
    407  nsReflowStatus childStatus;
    408  nsIFrame* childFrame = mFrames.FirstChild();
    409  WritingMode childWM = childFrame->GetWritingMode();
    410  LogicalSize availSize = aReflowInput.ComputedSize(childWM);
    411  availSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
    412  NS_ASSERTION(!childFrame->GetNextSibling(), "HTML canvas should have 1 kid");
    413  ReflowOutput childDesiredSize(aReflowInput.GetWritingMode());
    414  ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame,
    415                               availSize);
    416  ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput, 0,
    417              0, ReflowChildFlags::Default, childStatus, nullptr);
    418  FinishReflowChild(childFrame, aPresContext, childDesiredSize,
    419                    &childReflowInput, 0, 0, ReflowChildFlags::Default);
    420 
    421  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
    422                 ("exit nsHTMLCanvasFrame::Reflow: size=%d,%d",
    423                  aMetrics.ISize(wm), aMetrics.BSize(wm)));
    424 }
    425 
    426 bool nsHTMLCanvasFrame::UpdateWebRenderCanvasData(
    427    nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
    428  HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(GetContent());
    429  return element->UpdateWebRenderCanvasData(aBuilder, aCanvasData);
    430 }
    431 
    432 void nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    433                                         const nsDisplayListSet& aLists) {
    434  if (!IsVisibleForPainting()) {
    435    return;
    436  }
    437 
    438  DisplayBorderBackgroundOutline(aBuilder, aLists);
    439 
    440  if (HidesContent()) {
    441    return;
    442  }
    443 
    444  DisplayListClipState::AutoSaveRestore clipState(aBuilder);
    445  auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay());
    446  if (!clipAxes.isEmpty()) {
    447    nsRect clipRect;
    448    nsRectCornerRadii radii;
    449    bool haveRadii =
    450        ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii);
    451    if (haveRadii ||
    452        nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
    453      clipState.ClipContainingBlockDescendants(
    454          clipRect + aBuilder->ToReferenceFrame(this),
    455          haveRadii ? &radii : nullptr);
    456    }
    457  }
    458 
    459  aLists.Content()->AppendNewToTop<nsDisplayCanvas>(aBuilder, this);
    460 }
    461 
    462 void nsHTMLCanvasFrame::AppendDirectlyOwnedAnonBoxes(
    463    nsTArray<OwnedAnonBox>& aResult) {
    464  MOZ_ASSERT(mFrames.FirstChild(), "Must have our canvas content anon box");
    465  MOZ_ASSERT(!mFrames.FirstChild()->GetNextSibling(),
    466             "Must only have our canvas content anon box");
    467  aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
    468 }
    469 
    470 void nsHTMLCanvasFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
    471                                           bool) {
    472  // Our one child (the canvas content anon box) is unpainted and isn't relevant
    473  // for child-overflow purposes. So we need to provide our own trivial impl to
    474  // avoid receiving the child-considering impl that we would otherwise inherit.
    475 }
    476 
    477 #ifdef ACCESSIBILITY
    478 a11y::AccType nsHTMLCanvasFrame::AccessibleType() {
    479  return a11y::eHTMLCanvasType;
    480 }
    481 #endif
    482 
    483 #ifdef DEBUG_FRAME_DUMP
    484 nsresult nsHTMLCanvasFrame::GetFrameName(nsAString& aResult) const {
    485  return MakeFrameName(u"HTMLCanvas"_ns, aResult);
    486 }
    487 #endif