michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* rendering object for the HTML element */ michael@0: michael@0: #include "nsHTMLCanvasFrame.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "mozilla/dom/HTMLCanvasElement.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "Layers.h" michael@0: #include "ActiveLayerTracker.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::layers; michael@0: michael@0: class nsDisplayCanvas : public nsDisplayItem { michael@0: public: michael@0: nsDisplayCanvas(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) michael@0: : nsDisplayItem(aBuilder, aFrame) michael@0: { michael@0: MOZ_COUNT_CTOR(nsDisplayCanvas); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayCanvas() { michael@0: MOZ_COUNT_DTOR(nsDisplayCanvas); michael@0: } michael@0: #endif michael@0: michael@0: NS_DISPLAY_DECL_NAME("nsDisplayCanvas", TYPE_CANVAS) michael@0: michael@0: virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, michael@0: bool* aSnap) MOZ_OVERRIDE { michael@0: *aSnap = false; michael@0: nsIFrame* f = Frame(); michael@0: HTMLCanvasElement *canvas = michael@0: HTMLCanvasElement::FromContent(f->GetContent()); michael@0: nsRegion result; michael@0: if (canvas->GetIsOpaque()) { michael@0: result = GetBounds(aBuilder, aSnap); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, michael@0: bool* aSnap) MOZ_OVERRIDE { michael@0: *aSnap = true; michael@0: nsHTMLCanvasFrame* f = static_cast(Frame()); michael@0: return f->GetInnerArea() + ToReferenceFrame(); michael@0: } michael@0: michael@0: virtual already_AddRefed BuildLayer(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: const ContainerLayerParameters& aContainerParameters) MOZ_OVERRIDE michael@0: { michael@0: return static_cast(mFrame)-> michael@0: BuildLayer(aBuilder, aManager, this, aContainerParameters); michael@0: } michael@0: virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: const ContainerLayerParameters& aParameters) MOZ_OVERRIDE michael@0: { michael@0: if (HTMLCanvasElement::FromContent(mFrame->GetContent())->ShouldForceInactiveLayer(aManager)) michael@0: return LAYER_INACTIVE; michael@0: michael@0: // If compositing is cheap, just do that michael@0: if (aManager->IsCompositingCheap() || michael@0: ActiveLayerTracker::IsContentActive(mFrame)) michael@0: return mozilla::LAYER_ACTIVE; michael@0: michael@0: return LAYER_INACTIVE; michael@0: } michael@0: }; michael@0: michael@0: michael@0: nsIFrame* michael@0: NS_NewHTMLCanvasFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsHTMLCanvasFrame(aContext); michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsHTMLCanvasFrame) michael@0: NS_QUERYFRAME_ENTRY(nsHTMLCanvasFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame) michael@0: michael@0: void michael@0: nsHTMLCanvasFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: // We can fill in the canvas before the canvas frame is created, in michael@0: // which case we never get around to marking the content as active. Therefore, michael@0: // we mark it active here when we create the frame. michael@0: ActiveLayerTracker::NotifyContentChange(this); michael@0: } michael@0: michael@0: nsHTMLCanvasFrame::~nsHTMLCanvasFrame() michael@0: { michael@0: } michael@0: michael@0: nsIntSize michael@0: nsHTMLCanvasFrame::GetCanvasSize() michael@0: { michael@0: nsIntSize size(0,0); michael@0: HTMLCanvasElement *canvas = michael@0: HTMLCanvasElement::FromContentOrNull(GetContent()); michael@0: if (canvas) { michael@0: size = canvas->GetSize(); michael@0: } else { michael@0: NS_NOTREACHED("couldn't get canvas size"); michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsHTMLCanvasFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: // XXX The caller doesn't account for constraints of the height, michael@0: // min-height, and max-height properties. michael@0: nscoord result = nsPresContext::CSSPixelsToAppUnits(GetCanvasSize().width); michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsHTMLCanvasFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: // XXX The caller doesn't account for constraints of the height, michael@0: // min-height, and max-height properties. michael@0: nscoord result = nsPresContext::CSSPixelsToAppUnits(GetCanvasSize().width); michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsHTMLCanvasFrame::GetIntrinsicRatio() michael@0: { michael@0: nsIntSize size(GetCanvasSize()); michael@0: return nsSize(nsPresContext::CSSPixelsToAppUnits(size.width), michael@0: nsPresContext::CSSPixelsToAppUnits(size.height)); michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsHTMLCanvasFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: nsIntSize size = GetCanvasSize(); michael@0: michael@0: IntrinsicSize intrinsicSize; michael@0: intrinsicSize.width.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(size.width)); michael@0: intrinsicSize.height.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(size.height)); michael@0: michael@0: nsSize intrinsicRatio = GetIntrinsicRatio(); // won't actually be used michael@0: michael@0: return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions( michael@0: aRenderingContext, this, michael@0: intrinsicSize, intrinsicRatio, aCBSize, michael@0: aMargin, aBorder, aPadding); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsHTMLCanvasFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("enter nsHTMLCanvasFrame::Reflow: availSize=%d,%d", michael@0: aReflowState.AvailableWidth(), aReflowState.AvailableHeight())); michael@0: michael@0: NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: aMetrics.Width() = aReflowState.ComputedWidth(); michael@0: aMetrics.Height() = aReflowState.ComputedHeight(); michael@0: michael@0: // stash this away so we can compute our inner area later michael@0: mBorderPadding = aReflowState.ComputedPhysicalBorderPadding(); michael@0: michael@0: aMetrics.Width() += mBorderPadding.left + mBorderPadding.right; michael@0: aMetrics.Height() += mBorderPadding.top + mBorderPadding.bottom; michael@0: michael@0: if (GetPrevInFlow()) { michael@0: nscoord y = GetContinuationOffset(&aMetrics.Width()); michael@0: aMetrics.Height() -= y + mBorderPadding.top; michael@0: aMetrics.Height() = std::max(0, aMetrics.Height()); michael@0: } michael@0: michael@0: aMetrics.SetOverflowAreasToDesiredBounds(); michael@0: FinishAndStoreOverflow(&aMetrics); michael@0: michael@0: // Reflow the single anon block child. michael@0: nsReflowStatus childStatus; michael@0: nsSize availSize(aReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE); michael@0: nsIFrame* childFrame = mFrames.FirstChild(); michael@0: NS_ASSERTION(!childFrame->GetNextSibling(), "HTML canvas should have 1 kid"); michael@0: nsHTMLReflowMetrics childDesiredSize(aReflowState.GetWritingMode(), aMetrics.mFlags); michael@0: nsHTMLReflowState childReflowState(aPresContext, aReflowState, childFrame, michael@0: availSize); michael@0: ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowState, michael@0: 0, 0, 0, childStatus, nullptr); michael@0: FinishReflowChild(childFrame, aPresContext, childDesiredSize, michael@0: &childReflowState, 0, 0, 0); michael@0: michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("exit nsHTMLCanvasFrame::Reflow: size=%d,%d", michael@0: aMetrics.Width(), aMetrics.Height())); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // FIXME taken from nsImageFrame, but then had splittable frame stuff michael@0: // removed. That needs to be fixed. michael@0: nsRect michael@0: nsHTMLCanvasFrame::GetInnerArea() const michael@0: { michael@0: nsRect r; michael@0: r.x = mBorderPadding.left; michael@0: r.y = mBorderPadding.top; michael@0: r.width = mRect.width - mBorderPadding.left - mBorderPadding.right; michael@0: r.height = mRect.height - mBorderPadding.top - mBorderPadding.bottom; michael@0: return r; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLCanvasFrame::BuildLayer(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: nsDisplayItem* aItem, michael@0: const ContainerLayerParameters& aContainerParameters) michael@0: { michael@0: nsRect area = GetContentRect() - GetPosition() + aItem->ToReferenceFrame(); michael@0: HTMLCanvasElement* element = static_cast(GetContent()); michael@0: nsIntSize canvasSize = GetCanvasSize(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: element->HandlePrintCallback(presContext->Type()); michael@0: michael@0: if (canvasSize.width <= 0 || canvasSize.height <= 0 || area.IsEmpty()) michael@0: return nullptr; michael@0: michael@0: CanvasLayer* oldLayer = static_cast michael@0: (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem)); michael@0: nsRefPtr layer = element->GetCanvasLayer(aBuilder, oldLayer, aManager); michael@0: if (!layer) michael@0: return nullptr; michael@0: michael@0: gfxRect r = gfxRect(presContext->AppUnitsToGfxUnits(area.x), michael@0: presContext->AppUnitsToGfxUnits(area.y), michael@0: presContext->AppUnitsToGfxUnits(area.width), michael@0: presContext->AppUnitsToGfxUnits(area.height)); michael@0: michael@0: // Transform the canvas into the right place michael@0: gfx::Matrix transform; michael@0: gfxPoint p = r.TopLeft() + aContainerParameters.mOffset; michael@0: transform.Translate(p.x, p.y); michael@0: transform.Scale(r.Width()/canvasSize.width, r.Height()/canvasSize.height); michael@0: layer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); michael@0: layer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(this)); michael@0: layer->SetVisibleRegion(nsIntRect(0, 0, canvasSize.width, canvasSize.height)); michael@0: michael@0: return layer.forget(); michael@0: } michael@0: michael@0: void michael@0: nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!IsVisibleForPainting(aBuilder)) michael@0: return; michael@0: michael@0: DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: michael@0: DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox michael@0: clip(aBuilder, this, DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT); michael@0: michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayCanvas(aBuilder, this)); michael@0: michael@0: DisplaySelectionOverlay(aBuilder, aLists.Content(), michael@0: nsISelectionDisplay::DISPLAY_IMAGES); michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsHTMLCanvasFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::HTMLCanvasFrame; michael@0: } michael@0: michael@0: // get the offset into the content area of the image where aImg starts if it is a continuation. michael@0: // from nsImageFrame michael@0: nscoord michael@0: nsHTMLCanvasFrame::GetContinuationOffset(nscoord* aWidth) const michael@0: { michael@0: nscoord offset = 0; michael@0: if (aWidth) { michael@0: *aWidth = 0; michael@0: } michael@0: michael@0: if (GetPrevInFlow()) { michael@0: for (nsIFrame* prevInFlow = GetPrevInFlow() ; prevInFlow; prevInFlow = prevInFlow->GetPrevInFlow()) { michael@0: nsRect rect = prevInFlow->GetRect(); michael@0: if (aWidth) { michael@0: *aWidth = rect.width; michael@0: } michael@0: offset += rect.height; michael@0: } michael@0: offset -= mBorderPadding.top; michael@0: offset = std::max(0, offset); michael@0: } michael@0: return offset; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsHTMLCanvasFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLCanvasType; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsHTMLCanvasFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("HTMLCanvas"), aResult); michael@0: } michael@0: #endif michael@0: