/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* rendering object that goes directly inside the document's scrollbars */ #include "nsIServiceManager.h" #include "nsHTMLParts.h" #include "nsHTMLContainerFrame.h" #include "nsCSSRendering.h" #include "nsIDocument.h" #include "nsReflowPath.h" #include "nsPresContext.h" #include "nsStyleContext.h" #include "nsViewsCID.h" #include "nsIView.h" #include "nsIViewManager.h" #include "nsIWidget.h" #include "nsPageFrame.h" #include "nsIRenderingContext.h" #include "nsGUIEvent.h" #include "nsIDOMEvent.h" #include "nsStyleConsts.h" #include "nsIViewManager.h" #include "nsHTMLAtoms.h" #include "nsIEventStateManager.h" #include "nsIDeviceContext.h" #include "nsLayoutAtoms.h" #include "nsIPresShell.h" #include "nsIScrollPositionListener.h" #include "nsDisplayList.h" // for focus #include "nsIDOMWindowInternal.h" #include "nsIFocusController.h" #include "nsIScrollableFrame.h" #include "nsIScrollableView.h" #include "nsIDocShell.h" #include "nsICanvasFrame.h" #ifdef DEBUG_rods //#define DEBUG_CANVAS_FOCUS #endif // Interface IDs /** * Root frame class. * * The root frame is the parent frame for the document element's frame. * It only supports having a single child frame which must be an area * frame */ class CanvasFrame : public nsHTMLContainerFrame, public nsIScrollPositionListener, public nsICanvasFrame { public: CanvasFrame(nsStyleContext* aContext) : nsHTMLContainerFrame(aContext), mDoPaintFocus(PR_FALSE) {} // nsISupports NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr); NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow); NS_IMETHOD Destroy(nsPresContext* aPresContext); NS_IMETHOD AppendFrames(nsIAtom* aListName, nsIFrame* aFrameList); NS_IMETHOD InsertFrames(nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList); NS_IMETHOD RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame); NS_IMETHOD Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); virtual PRBool IsContainingBlock() const { return PR_TRUE; } NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists); void PaintFocus(nsIRenderingContext& aRenderingContext, nsPoint aPt); // nsIScrollPositionListener NS_IMETHOD ScrollPositionWillChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY); NS_IMETHOD ScrollPositionDidChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY); // nsICanvasFrame NS_IMETHOD SetHasFocus(PRBool aHasFocus); /** * Get the "type" of the frame * * @see nsLayoutAtoms::canvasFrame */ virtual nsIAtom* GetType() const; #ifdef DEBUG NS_IMETHOD GetFrameName(nsAString& aResult) const; #endif NS_IMETHOD GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, nsIContent** aContent); nsRect CanvasArea() const; protected: virtual PRIntn GetSkipSides() const; // Data members PRPackedBool mDoPaintFocus; nsCOMPtr mViewManager; private: NS_IMETHOD_(nsrefcnt) AddRef() { return NS_OK; } NS_IMETHOD_(nsrefcnt) Release() { return NS_OK; } }; //---------------------------------------------------------------------- nsIFrame* NS_NewCanvasFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell)CanvasFrame(aContext); } //-------------------------------------------------------------- // Frames are not refcounted, no need to AddRef NS_IMETHODIMP CanvasFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { NS_PRECONDITION(0 != aInstancePtr, "null ptr"); if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(NS_GET_IID(nsIScrollPositionListener))) { *aInstancePtr = (void*) ((nsIScrollPositionListener*) this); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsICanvasFrame))) { *aInstancePtr = (void*) ((nsICanvasFrame*) this); return NS_OK; } return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP CanvasFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow) { nsresult rv = nsHTMLContainerFrame::Init(aContent, aParent, aPrevInFlow); mViewManager = GetPresContext()->GetViewManager(); nsIScrollableView* scrollingView = nsnull; mViewManager->GetRootScrollableView(&scrollingView); if (scrollingView) { scrollingView->AddScrollPositionListener(this); } return rv; } NS_IMETHODIMP CanvasFrame::Destroy(nsPresContext* aPresContext) { nsIScrollableView* scrollingView = nsnull; mViewManager->GetRootScrollableView(&scrollingView); if (scrollingView) { scrollingView->RemoveScrollPositionListener(this); } return nsHTMLContainerFrame::Destroy(aPresContext); } NS_IMETHODIMP CanvasFrame::ScrollPositionWillChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY) { #ifdef DEBUG_CANVAS_FOCUS { PRBool hasFocus = PR_FALSE; nsCOMPtr observer; mViewManager->GetViewObserver(*getter_AddRefs(observer)); nsCOMPtr shell = do_QueryInterface(observer); nsCOMPtr context; shell->GetPresContext(getter_AddRefs(context)); nsCOMPtr container; context->GetContainer(getter_AddRefs(container)); nsCOMPtr docShell(do_QueryInterface(container)); if (docShell) { docShell->GetHasFocus(&hasFocus); } printf("SPWC: %p HF: %s mDoPaintFocus: %s\n", docShell.get(), hasFocus?"Y":"N", mDoPaintFocus?"Y":"N"); } #endif if (mDoPaintFocus) { mDoPaintFocus = PR_FALSE; mViewManager->UpdateAllViews(NS_VMREFRESH_NO_SYNC); } return NS_OK; } NS_IMETHODIMP CanvasFrame::ScrollPositionDidChange(nsIScrollableView* aScrollable, nscoord aX, nscoord aY) { return NS_OK; } NS_IMETHODIMP CanvasFrame::SetHasFocus(PRBool aHasFocus) { if (mDoPaintFocus != aHasFocus) { mDoPaintFocus = aHasFocus; nsIViewManager* vm = GetPresContext()->PresShell()->GetViewManager(); if (vm) { vm->UpdateAllViews(NS_VMREFRESH_NO_SYNC); } } return NS_OK; } NS_IMETHODIMP CanvasFrame::AppendFrames(nsIAtom* aListName, nsIFrame* aFrameList) { nsresult rv; NS_ASSERTION(!aListName, "unexpected child list name"); NS_PRECONDITION(mFrames.IsEmpty(), "already have a child frame"); if (aListName) { // We only support unnamed principal child list rv = NS_ERROR_INVALID_ARG; } else if (!mFrames.IsEmpty()) { // We only allow a single child frame rv = NS_ERROR_FAILURE; } else { // Insert the new frames #ifdef NS_DEBUG nsFrame::VerifyDirtyBitSet(aFrameList); #endif mFrames.AppendFrame(nsnull, aFrameList); // Generate a reflow command to reflow the newly inserted frame rv = GetPresContext()->PresShell()-> AppendReflowCommand(this, eReflowType_ReflowDirty, nsnull); } return rv; } NS_IMETHODIMP CanvasFrame::InsertFrames(nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList) { nsresult rv; // Because we only support a single child frame inserting is the same // as appending NS_PRECONDITION(!aPrevFrame, "unexpected previous sibling frame"); if (aPrevFrame) { rv = NS_ERROR_UNEXPECTED; } else { rv = AppendFrames(aListName, aFrameList); } return rv; } NS_IMETHODIMP CanvasFrame::RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame) { nsresult rv; NS_ASSERTION(!aListName, "unexpected child list name"); if (aListName) { // We only support the unnamed principal child list rv = NS_ERROR_INVALID_ARG; } else if (aOldFrame == mFrames.FirstChild()) { // It's our one and only child frame // Damage the area occupied by the deleted frame // The child of the canvas probably can't have an outline, but why bother // thinking about that? Invalidate(aOldFrame->GetOverflowRect() + aOldFrame->GetPosition(), PR_FALSE); // Remove the frame and destroy it mFrames.DestroyFrame(GetPresContext(), aOldFrame); // Generate a reflow command so we get reflowed rv = GetPresContext()->PresShell()-> AppendReflowCommand(this, eReflowType_ReflowDirty, nsnull); } else { rv = NS_ERROR_FAILURE; } return rv; } nsRect CanvasFrame::CanvasArea() const { nsRect result(GetOverflowRect()); nsIScrollableFrame *scrollableFrame; CallQueryInterface(GetParent(), &scrollableFrame); if (scrollableFrame) { nsIScrollableView* scrollableView = scrollableFrame->GetScrollableView(); nsRect vcr = scrollableView->View()->GetBounds(); result.UnionRect(result, nsRect(nsPoint(0, 0), vcr.Size())); } return result; } /* * Override nsDisplayBackground methods so that we pass aBGClipRect to * PaintBackground, covering the whole overflow area. */ class nsDisplayCanvasBackground : public nsDisplayBackground { public: nsDisplayCanvasBackground(nsIFrame *aFrame) : nsDisplayBackground(aFrame) { } virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) { CanvasFrame* frame = NS_STATIC_CAST(CanvasFrame*, mFrame); return frame->CanvasArea() + aBuilder->ToReferenceFrame(mFrame); } virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, const nsRect& aDirtyRect) { CanvasFrame* frame = NS_STATIC_CAST(CanvasFrame*, mFrame); nsPoint offset = aBuilder->ToReferenceFrame(mFrame); nsRect bgClipRect = frame->CanvasArea() + offset; nsCSSRendering::PaintBackground(mFrame->GetPresContext(), *aCtx, mFrame, aDirtyRect, nsRect(offset, mFrame->GetSize()), *mFrame->GetStyleBorder(), *mFrame->GetStylePadding(), mFrame->HonorPrintBackgroundSettings(), &bgClipRect); } NS_DISPLAY_DECL_NAME("CanvasBackground") }; /** * A display item to paint the focus ring for the document. * * The only reason this can't use nsDisplayGeneric is overriding GetBounds. */ class nsDisplayCanvasFocus : public nsDisplayItem { public: nsDisplayCanvasFocus(CanvasFrame *aFrame) : nsDisplayItem(aFrame) { } virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) { // This is an overestimate, but that's not a problem. CanvasFrame* frame = NS_STATIC_CAST(CanvasFrame*, mFrame); return frame->CanvasArea() + aBuilder->ToReferenceFrame(mFrame); } virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, const nsRect& aDirtyRect) { CanvasFrame* frame = NS_STATIC_CAST(CanvasFrame*, mFrame); frame->PaintFocus(*aCtx, aBuilder->ToReferenceFrame(mFrame)); } NS_DISPLAY_DECL_NAME("CanvasFocus") }; NS_IMETHODIMP CanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { nsresult rv; // Force a background to be shown. We may have a background propagated to us, // in which case GetStyleBackground wouldn't have the right background // and the code in nsFrame::DisplayBorderBackgroundOutline might not give us // a background. // We don't have any border or outline, and our background draws over // the overflow area, so just add nsDisplayCanvasBackground instead of // calling DisplayBorderBackgroundOutline. if (IsVisibleForPainting(aBuilder)) { rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder) nsDisplayCanvasBackground(this)); NS_ENSURE_SUCCESS(rv, rv); } nsIFrame* kid = GetFirstChild(nsnull); if (kid) { // Put our child into its own pseudo-stack. rv = BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists, DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT); NS_ENSURE_SUCCESS(rv, rv); } #ifdef DEBUG_CANVAS_FOCUS nsCOMPtr focusContent; aPresContext->EventStateManager()-> GetFocusedContent(getter_AddRefs(focusContent)); PRBool hasFocus = PR_FALSE; nsCOMPtr container; aPresContext->GetContainer(getter_AddRefs(container)); nsCOMPtr docShell(do_QueryInterface(container)); if (docShell) { docShell->GetHasFocus(&hasFocus); printf("%p - CanvasFrame::Paint R:%d,%d,%d,%d DR: %d,%d,%d,%d\n", this, mRect.x, mRect.y, mRect.width, mRect.height, aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); } printf("%p - Focus: %s c: %p DoPaint:%s\n", docShell.get(), hasFocus?"Y":"N", focusContent.get(), mDoPaintFocus?"Y":"N"); #endif if (!mDoPaintFocus) return NS_OK; // Only paint the focus if we're visible if (!GetStyleVisibility()->IsVisible()) return NS_OK; return aLists.Outlines()->AppendNewToTop(new (aBuilder) nsDisplayCanvasFocus(this)); } void CanvasFrame::PaintFocus(nsIRenderingContext& aRenderingContext, nsPoint aPt) { nsRect focusRect(aPt, GetSize()); nsIScrollableFrame *scrollableFrame; CallQueryInterface(GetParent(), &scrollableFrame); if (scrollableFrame) { nsIScrollableView* scrollableView = scrollableFrame->GetScrollableView(); nsRect vcr = scrollableView->View()->GetBounds(); focusRect.width = vcr.width; focusRect.height = vcr.height; nscoord x,y; scrollableView->GetScrollPosition(x, y); focusRect.x += x; focusRect.y += y; } nsStyleOutline outlineStyle(GetPresContext()); outlineStyle.SetOutlineStyle(NS_STYLE_BORDER_STYLE_DOTTED); outlineStyle.SetOutlineInvert(); float p2t = GetPresContext()->PixelsToTwips(); // XXX the CSS border for links is specified as 2px, but it // is only drawn as 1px. Match this here. nscoord onePixel = NSIntPixelsToTwips(1, p2t); nsRect borderInside(focusRect.x + onePixel, focusRect.y + onePixel, focusRect.width - 2 * onePixel, focusRect.height - 2 * onePixel); nsCSSRendering::DrawDashedSides(0, aRenderingContext, focusRect, nsnull, nsnull, &outlineStyle, PR_TRUE, focusRect, borderInside, 0, nsnull); } NS_IMETHODIMP CanvasFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { DO_GLOBAL_REFLOW_COUNT("CanvasFrame", aReflowState.reason); DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); NS_FRAME_TRACE_REFLOW_IN("CanvasFrame::Reflow"); //NS_PRECONDITION(!aDesiredSize.mComputeMEW, "unexpected request"); // Initialize OUT parameter aStatus = NS_FRAME_COMPLETE; PRBool isStyleChange = PR_FALSE; PRBool isDirtyChildReflow = PR_FALSE; // Check for an incremental reflow if (eReflowReason_Incremental == aReflowState.reason) { // See if we're the target frame nsHTMLReflowCommand *command = aReflowState.path->mReflowCommand; if (command) { // Get the reflow type switch (command->Type()) { case eReflowType_ReflowDirty: isDirtyChildReflow = PR_TRUE; break; case eReflowType_StyleChanged: // Remember it's a style change so we can set the reflow reason below isStyleChange = PR_TRUE; break; default: NS_ASSERTION(PR_FALSE, "unexpected reflow command type"); } } else { #ifdef DEBUG nsReflowPath::iterator iter = aReflowState.path->FirstChild(); NS_ASSERTION(*iter == mFrames.FirstChild(), "unexpected next reflow command frame"); #endif } } // Reflow our one and only child frame nsHTMLReflowMetrics kidDesiredSize(nsnull); if (mFrames.IsEmpty()) { // We have no child frame, so return an empty size aDesiredSize.width = aDesiredSize.height = 0; aDesiredSize.ascent = aDesiredSize.descent = 0; } else { nsIFrame* kidFrame = mFrames.FirstChild(); nsReflowReason reason; if (isDirtyChildReflow) { // Note: the only reason the frame would be dirty would be if it had // just been inserted or appended reason = eReflowReason_Initial; } else if (isStyleChange) { reason = eReflowReason_StyleChange; } else { reason = aReflowState.reason; } // We must specify an unconstrained available height, because constrained // is only for when we're paginated... nsHTMLReflowState kidReflowState(aPresContext, aReflowState, kidFrame, nsSize(aReflowState.availableWidth, NS_UNCONSTRAINEDSIZE), reason); // Reflow the frame ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowState, kidReflowState.mComputedMargin.left, kidReflowState.mComputedMargin.top, 0, aStatus); // Complete the reflow and position and size the child frame FinishReflowChild(kidFrame, aPresContext, &kidReflowState, kidDesiredSize, kidReflowState.mComputedMargin.left, kidReflowState.mComputedMargin.top, 0); // If the child frame was just inserted, then we're responsible for making sure // it repaints if (isDirtyChildReflow) { // But we have a new child, which will affect our background, so // invalidate our whole rect. // Note: Even though we request to be sized to our child's size, our // scroll frame ensures that we are always the size of the viewport. // Also note: GetPosition() on a CanvasFrame is always going to return // (0, 0). We only want to invalidate GetRect() since GetOverflowRect() // could also include overflow to our top and left (out of the viewport) // which doesn't need to be painted. Invalidate(GetRect(), PR_FALSE); } // Return our desired size (which doesn't matter) aDesiredSize.width = aReflowState.availableWidth; aDesiredSize.height = kidDesiredSize.height + kidReflowState.mComputedMargin.TopBottom(); aDesiredSize.mOverflowArea.UnionRect( nsRect(0, 0, aDesiredSize.width, aDesiredSize.height), kidDesiredSize.mOverflowArea + nsPoint(kidReflowState.mComputedMargin.left, kidReflowState.mComputedMargin.top)); FinishAndStoreOverflow(&aDesiredSize); aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; } NS_FRAME_TRACE_REFLOW_OUT("CanvasFrame::Reflow", aStatus); NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); return NS_OK; } PRIntn CanvasFrame::GetSkipSides() const { return 0; } nsIAtom* CanvasFrame::GetType() const { return nsLayoutAtoms::canvasFrame; } NS_IMETHODIMP CanvasFrame::GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, nsIContent** aContent) { NS_ENSURE_ARG_POINTER(aContent); nsresult rv = nsFrame::GetContentForEvent(aPresContext, aEvent, aContent); if (NS_FAILED(rv) || !*aContent) { nsIFrame* kid = mFrames.FirstChild(); if (kid) { rv = kid->GetContentForEvent(aPresContext, aEvent, aContent); } } return rv; } #ifdef DEBUG NS_IMETHODIMP CanvasFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("Canvas"), aResult); } #endif