Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* rendering object for the HTML <canvas> element */
7 :
8 : #include "nsHTMLCanvasFrame.h"
9 :
10 : #include "nsGkAtoms.h"
11 : #include "mozilla/Assertions.h"
12 : #include "mozilla/dom/HTMLCanvasElement.h"
13 : #include "nsDisplayList.h"
14 : #include "nsLayoutUtils.h"
15 : #include "nsStyleUtil.h"
16 : #include "ImageLayers.h"
17 : #include "Layers.h"
18 : #include "ActiveLayerTracker.h"
19 :
20 : #include <algorithm>
21 :
22 : using namespace mozilla;
23 : using namespace mozilla::dom;
24 : using namespace mozilla::layers;
25 : using namespace mozilla::gfx;
26 :
27 : /* Helper for our nsIFrame::GetIntrinsicSize() impl. Takes the result of
28 : * "GetCanvasSize()" as a parameter, which may help avoid redundant
29 : * indirect calls to GetCanvasSize().
30 : *
31 : * @param aCanvasSizeInPx The canvas's size in CSS pixels, as returned
32 : * by GetCanvasSize().
33 : * @return The canvas's intrinsic size, as an IntrinsicSize object.
34 : */
35 : static IntrinsicSize
36 0 : IntrinsicSizeFromCanvasSize(const nsIntSize& aCanvasSizeInPx)
37 : {
38 0 : IntrinsicSize intrinsicSize;
39 0 : intrinsicSize.width.SetCoordValue(
40 0 : nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.width));
41 0 : intrinsicSize.height.SetCoordValue(
42 0 : nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.height));
43 :
44 0 : return intrinsicSize;
45 : }
46 :
47 : /* Helper for our nsIFrame::GetIntrinsicRatio() impl. Takes the result of
48 : * "GetCanvasSize()" as a parameter, which may help avoid redundant
49 : * indirect calls to GetCanvasSize().
50 : *
51 : * @param aCanvasSizeInPx The canvas's size in CSS pixels, as returned
52 : * by GetCanvasSize().
53 : * @return The canvas's intrinsic ratio, as a nsSize.
54 : */
55 : static nsSize
56 0 : IntrinsicRatioFromCanvasSize(const nsIntSize& aCanvasSizeInPx)
57 : {
58 0 : return nsSize(nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.width),
59 0 : nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.height));
60 : }
61 :
62 : class nsDisplayCanvas : public nsDisplayItem {
63 : public:
64 0 : nsDisplayCanvas(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
65 0 : : nsDisplayItem(aBuilder, aFrame)
66 : {
67 0 : MOZ_COUNT_CTOR(nsDisplayCanvas);
68 0 : }
69 : #ifdef NS_BUILD_REFCNT_LOGGING
70 0 : virtual ~nsDisplayCanvas() {
71 0 : MOZ_COUNT_DTOR(nsDisplayCanvas);
72 0 : }
73 : #endif
74 :
75 0 : NS_DISPLAY_DECL_NAME("nsDisplayCanvas", TYPE_CANVAS)
76 :
77 0 : virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
78 : bool* aSnap) override {
79 0 : *aSnap = false;
80 0 : nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
81 : HTMLCanvasElement* canvas =
82 0 : HTMLCanvasElement::FromContent(f->GetContent());
83 0 : nsRegion result;
84 0 : if (canvas->GetIsOpaque()) {
85 : // OK, the entire region painted by the canvas is opaque. But what is
86 : // that region? It's the canvas's "dest rect" (controlled by the
87 : // object-fit/object-position CSS properties), clipped to the container's
88 : // content box (which is what GetBounds() returns). So, we grab those
89 : // rects and intersect them.
90 0 : nsRect constraintRect = GetBounds(aBuilder, aSnap);
91 :
92 : // Need intrinsic size & ratio, for ComputeObjectDestRect:
93 0 : nsIntSize canvasSize = f->GetCanvasSize();
94 0 : IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSize);
95 0 : nsSize intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSize);
96 :
97 : const nsRect destRect =
98 : nsLayoutUtils::ComputeObjectDestRect(constraintRect,
99 : intrinsicSize, intrinsicRatio,
100 0 : f->StylePosition());
101 0 : return nsRegion(destRect.Intersect(constraintRect));
102 : }
103 0 : return result;
104 : }
105 :
106 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
107 : bool* aSnap) override {
108 0 : *aSnap = true;
109 0 : nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
110 0 : return f->GetInnerArea() + ToReferenceFrame();
111 : }
112 :
113 0 : virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
114 : LayerManager* aManager,
115 : const ContainerLayerParameters& aContainerParameters) override
116 : {
117 0 : return static_cast<nsHTMLCanvasFrame*>(mFrame)->
118 0 : BuildLayer(aBuilder, aManager, this, aContainerParameters);
119 : }
120 0 : virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
121 : LayerManager* aManager,
122 : const ContainerLayerParameters& aParameters) override
123 : {
124 0 : if (HTMLCanvasElement::FromContent(mFrame->GetContent())->ShouldForceInactiveLayer(aManager))
125 0 : return LAYER_INACTIVE;
126 :
127 : // If compositing is cheap, just do that
128 0 : if (aManager->IsCompositingCheap() ||
129 0 : ActiveLayerTracker::IsContentActive(mFrame))
130 0 : return mozilla::LAYER_ACTIVE;
131 :
132 0 : return LAYER_INACTIVE;
133 : }
134 : };
135 :
136 :
137 : nsIFrame*
138 0 : NS_NewHTMLCanvasFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
139 : {
140 0 : return new (aPresShell) nsHTMLCanvasFrame(aContext);
141 : }
142 :
143 0 : NS_QUERYFRAME_HEAD(nsHTMLCanvasFrame)
144 0 : NS_QUERYFRAME_ENTRY(nsHTMLCanvasFrame)
145 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
146 :
147 0 : NS_IMPL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame)
148 :
149 : void
150 0 : nsHTMLCanvasFrame::Init(nsIContent* aContent,
151 : nsContainerFrame* aParent,
152 : nsIFrame* aPrevInFlow)
153 : {
154 0 : nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
155 :
156 : // We can fill in the canvas before the canvas frame is created, in
157 : // which case we never get around to marking the content as active. Therefore,
158 : // we mark it active here when we create the frame.
159 0 : ActiveLayerTracker::NotifyContentChange(this);
160 0 : }
161 :
162 0 : nsHTMLCanvasFrame::~nsHTMLCanvasFrame()
163 : {
164 0 : }
165 :
166 : nsIntSize
167 0 : nsHTMLCanvasFrame::GetCanvasSize()
168 : {
169 0 : nsIntSize size(0,0);
170 : HTMLCanvasElement *canvas =
171 0 : HTMLCanvasElement::FromContentOrNull(GetContent());
172 0 : if (canvas) {
173 0 : size = canvas->GetSize();
174 0 : MOZ_ASSERT(size.width >= 0 && size.height >= 0,
175 : "we should've required <canvas> width/height attrs to be "
176 : "unsigned (non-negative) values");
177 : } else {
178 0 : NS_NOTREACHED("couldn't get canvas size");
179 : }
180 :
181 0 : return size;
182 : }
183 :
184 : /* virtual */ nscoord
185 0 : nsHTMLCanvasFrame::GetMinISize(gfxContext *aRenderingContext)
186 : {
187 : // XXX The caller doesn't account for constraints of the height,
188 : // min-height, and max-height properties.
189 0 : bool vertical = GetWritingMode().IsVertical();
190 0 : nscoord result = nsPresContext::CSSPixelsToAppUnits(
191 0 : vertical ? GetCanvasSize().height : GetCanvasSize().width);
192 0 : DISPLAY_MIN_WIDTH(this, result);
193 0 : return result;
194 : }
195 :
196 : /* virtual */ nscoord
197 0 : nsHTMLCanvasFrame::GetPrefISize(gfxContext *aRenderingContext)
198 : {
199 : // XXX The caller doesn't account for constraints of the height,
200 : // min-height, and max-height properties.
201 0 : bool vertical = GetWritingMode().IsVertical();
202 0 : nscoord result = nsPresContext::CSSPixelsToAppUnits(
203 0 : vertical ? GetCanvasSize().height : GetCanvasSize().width);
204 0 : DISPLAY_PREF_WIDTH(this, result);
205 0 : return result;
206 : }
207 :
208 : /* virtual */ IntrinsicSize
209 0 : nsHTMLCanvasFrame::GetIntrinsicSize()
210 : {
211 0 : return IntrinsicSizeFromCanvasSize(GetCanvasSize());
212 : }
213 :
214 : /* virtual */ nsSize
215 0 : nsHTMLCanvasFrame::GetIntrinsicRatio()
216 : {
217 0 : return IntrinsicRatioFromCanvasSize(GetCanvasSize());
218 : }
219 :
220 : /* virtual */
221 : LogicalSize
222 0 : nsHTMLCanvasFrame::ComputeSize(gfxContext *aRenderingContext,
223 : WritingMode aWM,
224 : const LogicalSize& aCBSize,
225 : nscoord aAvailableISize,
226 : const LogicalSize& aMargin,
227 : const LogicalSize& aBorder,
228 : const LogicalSize& aPadding,
229 : ComputeSizeFlags aFlags)
230 : {
231 0 : nsIntSize size = GetCanvasSize();
232 :
233 0 : IntrinsicSize intrinsicSize;
234 0 : intrinsicSize.width.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(size.width));
235 0 : intrinsicSize.height.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(size.height));
236 :
237 0 : nsSize intrinsicRatio = GetIntrinsicRatio(); // won't actually be used
238 :
239 : return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
240 : intrinsicSize, intrinsicRatio,
241 : aCBSize, aMargin, aBorder, aPadding,
242 0 : aFlags);
243 : }
244 :
245 : void
246 0 : nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
247 : ReflowOutput& aMetrics,
248 : const ReflowInput& aReflowInput,
249 : nsReflowStatus& aStatus)
250 : {
251 0 : MarkInReflow();
252 0 : DO_GLOBAL_REFLOW_COUNT("nsHTMLCanvasFrame");
253 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
254 0 : NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
255 : ("enter nsHTMLCanvasFrame::Reflow: availSize=%d,%d",
256 : aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
257 :
258 0 : NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
259 :
260 0 : aStatus.Reset();
261 :
262 0 : WritingMode wm = aReflowInput.GetWritingMode();
263 : LogicalSize finalSize(wm,
264 : aReflowInput.ComputedISize(),
265 0 : aReflowInput.ComputedBSize());
266 :
267 : // stash this away so we can compute our inner area later
268 0 : mBorderPadding = aReflowInput.ComputedLogicalBorderPadding();
269 :
270 0 : finalSize.ISize(wm) += mBorderPadding.IStartEnd(wm);
271 0 : finalSize.BSize(wm) += mBorderPadding.BStartEnd(wm);
272 :
273 0 : if (GetPrevInFlow()) {
274 0 : nscoord y = GetContinuationOffset(&finalSize.ISize(wm));
275 0 : finalSize.BSize(wm) -= y + mBorderPadding.BStart(wm);
276 0 : finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
277 : }
278 :
279 0 : aMetrics.SetSize(wm, finalSize);
280 0 : aMetrics.SetOverflowAreasToDesiredBounds();
281 0 : FinishAndStoreOverflow(&aMetrics);
282 :
283 : // Reflow the single anon block child.
284 0 : nsReflowStatus childStatus;
285 0 : nsIFrame* childFrame = mFrames.FirstChild();
286 0 : WritingMode childWM = childFrame->GetWritingMode();
287 0 : LogicalSize availSize = aReflowInput.ComputedSize(childWM);
288 0 : availSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
289 0 : NS_ASSERTION(!childFrame->GetNextSibling(), "HTML canvas should have 1 kid");
290 0 : ReflowOutput childDesiredSize(aReflowInput.GetWritingMode(), aMetrics.mFlags);
291 : ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame,
292 0 : availSize);
293 0 : ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput,
294 0 : 0, 0, 0, childStatus, nullptr);
295 : FinishReflowChild(childFrame, aPresContext, childDesiredSize,
296 0 : &childReflowInput, 0, 0, 0);
297 :
298 0 : NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
299 : ("exit nsHTMLCanvasFrame::Reflow: size=%d,%d",
300 : aMetrics.ISize(wm), aMetrics.BSize(wm)));
301 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
302 0 : }
303 :
304 : // FIXME taken from nsImageFrame, but then had splittable frame stuff
305 : // removed. That needs to be fixed.
306 : // XXXdholbert As in nsImageFrame, this function's clients should probably
307 : // just be calling GetContentRectRelativeToSelf().
308 : nsRect
309 0 : nsHTMLCanvasFrame::GetInnerArea() const
310 : {
311 0 : nsMargin bp = mBorderPadding.GetPhysicalMargin(GetWritingMode());
312 0 : nsRect r;
313 0 : r.x = bp.left;
314 0 : r.y = bp.top;
315 0 : r.width = mRect.width - bp.left - bp.right;
316 0 : r.height = mRect.height - bp.top - bp.bottom;
317 0 : return r;
318 : }
319 :
320 : already_AddRefed<Layer>
321 0 : nsHTMLCanvasFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
322 : LayerManager* aManager,
323 : nsDisplayItem* aItem,
324 : const ContainerLayerParameters& aContainerParameters)
325 : {
326 0 : nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
327 0 : HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(GetContent());
328 0 : nsIntSize canvasSizeInPx = GetCanvasSize();
329 :
330 0 : nsPresContext* presContext = PresContext();
331 0 : element->HandlePrintCallback(presContext->Type());
332 :
333 0 : if (canvasSizeInPx.width <= 0 || canvasSizeInPx.height <= 0 || area.IsEmpty())
334 0 : return nullptr;
335 :
336 : CanvasLayer* oldLayer = static_cast<CanvasLayer*>
337 0 : (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem));
338 0 : RefPtr<Layer> layer = element->GetCanvasLayer(aBuilder, oldLayer, aManager);
339 0 : if (!layer)
340 0 : return nullptr;
341 :
342 0 : IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
343 0 : nsSize intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
344 :
345 : nsRect dest =
346 : nsLayoutUtils::ComputeObjectDestRect(area, intrinsicSize, intrinsicRatio,
347 0 : StylePosition());
348 :
349 0 : gfxRect destGFXRect = presContext->AppUnitsToGfxUnits(dest);
350 :
351 : // Transform the canvas into the right place
352 0 : gfxPoint p = destGFXRect.TopLeft() + aContainerParameters.mOffset;
353 0 : Matrix transform = Matrix::Translation(p.x, p.y);
354 0 : transform.PreScale(destGFXRect.Width() / canvasSizeInPx.width,
355 0 : destGFXRect.Height() / canvasSizeInPx.height);
356 0 : layer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
357 0 : if (layer->GetType() == layers::Layer::TYPE_CANVAS) {
358 0 : RefPtr<CanvasLayer> canvasLayer = static_cast<CanvasLayer*>(layer.get());
359 0 : canvasLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(this));
360 0 : } else if (layer->GetType() == layers::Layer::TYPE_IMAGE) {
361 0 : RefPtr<ImageLayer> imageLayer = static_cast<ImageLayer*>(layer.get());
362 0 : imageLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(this));
363 : }
364 :
365 0 : return layer.forget();
366 : }
367 :
368 : void
369 0 : nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
370 : const nsRect& aDirtyRect,
371 : const nsDisplayListSet& aLists)
372 : {
373 0 : if (!IsVisibleForPainting(aBuilder))
374 0 : return;
375 :
376 0 : DisplayBorderBackgroundOutline(aBuilder, aLists);
377 :
378 : uint32_t clipFlags =
379 0 : nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
380 0 : 0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
381 :
382 : DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
383 0 : clip(aBuilder, this, clipFlags);
384 :
385 0 : aLists.Content()->AppendNewToTop(
386 0 : new (aBuilder) nsDisplayCanvas(aBuilder, this));
387 :
388 0 : DisplaySelectionOverlay(aBuilder, aLists.Content(),
389 0 : nsISelectionDisplay::DISPLAY_IMAGES);
390 : }
391 :
392 : // get the offset into the content area of the image where aImg starts if it is a continuation.
393 : // from nsImageFrame
394 : nscoord
395 0 : nsHTMLCanvasFrame::GetContinuationOffset(nscoord* aWidth) const
396 : {
397 0 : nscoord offset = 0;
398 0 : if (aWidth) {
399 0 : *aWidth = 0;
400 : }
401 :
402 0 : if (GetPrevInFlow()) {
403 0 : for (nsIFrame* prevInFlow = GetPrevInFlow() ; prevInFlow; prevInFlow = prevInFlow->GetPrevInFlow()) {
404 0 : nsRect rect = prevInFlow->GetRect();
405 0 : if (aWidth) {
406 0 : *aWidth = rect.width;
407 : }
408 0 : offset += rect.height;
409 : }
410 0 : offset -= mBorderPadding.GetPhysicalMargin(GetWritingMode()).top;
411 0 : offset = std::max(0, offset);
412 : }
413 0 : return offset;
414 : }
415 :
416 : void
417 0 : nsHTMLCanvasFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
418 : {
419 0 : MOZ_ASSERT(mFrames.FirstChild(), "Must have our canvas content anon box");
420 0 : MOZ_ASSERT(!mFrames.FirstChild()->GetNextSibling(),
421 : "Must only have our canvas content anon box");
422 0 : aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
423 0 : }
424 :
425 : #ifdef ACCESSIBILITY
426 : a11y::AccType
427 0 : nsHTMLCanvasFrame::AccessibleType()
428 : {
429 0 : return a11y::eHTMLCanvasType;
430 : }
431 : #endif
432 :
433 : #ifdef DEBUG_FRAME_DUMP
434 : nsresult
435 0 : nsHTMLCanvasFrame::GetFrameName(nsAString& aResult) const
436 : {
437 0 : return MakeFrameName(NS_LITERAL_STRING("HTMLCanvas"), aResult);
438 : }
439 : #endif
440 :
|