зеркало из https://github.com/mozilla/pjs.git
995 строки
32 KiB
C++
995 строки
32 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 sw=2 et tw=78: */
|
|
/* ***** 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 ***** */
|
|
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsIFontMetrics.h"
|
|
#include "nsIFormControlFrame.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsIContent.h"
|
|
#include "nsFrameList.h"
|
|
#include "nsLayoutAtoms.h"
|
|
#include "nsIAtom.h"
|
|
#include "nsCSSPseudoElements.h"
|
|
#include "nsIView.h"
|
|
#include "nsIScrollableView.h"
|
|
#include "nsPlaceholderFrame.h"
|
|
#include "nsIScrollableFrame.h"
|
|
#include "nsCSSFrameConstructor.h"
|
|
#include "nsIPrivateDOMEvent.h"
|
|
#include "nsIDOMEvent.h"
|
|
#include "nsGUIEvent.h"
|
|
#include "nsDisplayList.h"
|
|
#include "nsRegion.h"
|
|
|
|
#ifdef MOZ_SVG_FOREIGNOBJECT
|
|
#include "nsSVGForeignObjectFrame.h"
|
|
#include "nsSVGUtils.h"
|
|
#include "nsSVGOuterSVGFrame.h"
|
|
#endif
|
|
|
|
/**
|
|
* A namespace class for static layout utilities.
|
|
*/
|
|
|
|
/**
|
|
* GetFirstChildFrame returns the first "real" child frame of a
|
|
* given frame. It will descend down into pseudo-frames (unless the
|
|
* pseudo-frame is the :before generated frame).
|
|
* @param aFrame the frame
|
|
* @param aFrame the frame's content node
|
|
*/
|
|
static nsIFrame*
|
|
GetFirstChildFrame(nsIFrame* aFrame,
|
|
nsIContent* aContent)
|
|
{
|
|
NS_PRECONDITION(aFrame, "NULL frame pointer");
|
|
|
|
// Get the first child frame
|
|
nsIFrame* childFrame = aFrame->GetFirstChild(nsnull);
|
|
|
|
// If the child frame is a pseudo-frame, then return its first child.
|
|
// Note that the frame we create for the generated content is also a
|
|
// pseudo-frame and so don't drill down in that case
|
|
if (childFrame &&
|
|
childFrame->IsPseudoFrame(aContent) &&
|
|
!childFrame->IsGeneratedContentFrame()) {
|
|
return GetFirstChildFrame(childFrame, aContent);
|
|
}
|
|
|
|
return childFrame;
|
|
}
|
|
|
|
/**
|
|
* GetLastChildFrame returns the last "real" child frame of a
|
|
* given frame. It will descend down into pseudo-frames (unless the
|
|
* pseudo-frame is the :after generated frame).
|
|
* @param aFrame the frame
|
|
* @param aFrame the frame's content node
|
|
*/
|
|
static nsIFrame*
|
|
GetLastChildFrame(nsIFrame* aFrame,
|
|
nsIContent* aContent)
|
|
{
|
|
NS_PRECONDITION(aFrame, "NULL frame pointer");
|
|
|
|
// Get the last in flow frame
|
|
nsIFrame* lastInFlow = aFrame->GetLastInFlow();
|
|
|
|
// Get the last child frame
|
|
nsIFrame* firstChildFrame = lastInFlow->GetFirstChild(nsnull);
|
|
if (firstChildFrame) {
|
|
nsFrameList frameList(firstChildFrame);
|
|
nsIFrame* lastChildFrame = frameList.LastChild();
|
|
|
|
NS_ASSERTION(lastChildFrame, "unexpected error");
|
|
|
|
// Get the frame's first-in-flow. This matters in case the frame has
|
|
// been continuted across multiple lines
|
|
lastChildFrame = lastChildFrame->GetFirstInFlow();
|
|
|
|
// If the last child frame is a pseudo-frame, then return its last child.
|
|
// Note that the frame we create for the generated content is also a
|
|
// pseudo-frame and so don't drill down in that case
|
|
if (lastChildFrame &&
|
|
lastChildFrame->IsPseudoFrame(aContent) &&
|
|
!lastChildFrame->IsGeneratedContentFrame()) {
|
|
return GetLastChildFrame(lastChildFrame, aContent);
|
|
}
|
|
|
|
return lastChildFrame;
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
// static
|
|
nsIFrame*
|
|
nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame)
|
|
{
|
|
NS_PRECONDITION(aFrame, "NULL frame pointer");
|
|
NS_ASSERTION(!aFrame->GetPrevInFlow(), "aFrame must be first-in-flow");
|
|
|
|
nsIFrame* firstFrame = GetFirstChildFrame(aFrame, aFrame->GetContent());
|
|
|
|
if (firstFrame && IsGeneratedContentFor(nsnull, firstFrame,
|
|
nsCSSPseudoElements::before)) {
|
|
return firstFrame;
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
// static
|
|
nsIFrame*
|
|
nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
|
|
{
|
|
NS_PRECONDITION(aFrame, "NULL frame pointer");
|
|
|
|
nsIFrame* lastFrame = GetLastChildFrame(aFrame, aFrame->GetContent());
|
|
|
|
if (lastFrame && IsGeneratedContentFor(nsnull, lastFrame,
|
|
nsCSSPseudoElements::after)) {
|
|
return lastFrame;
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
// static
|
|
nsIFrame*
|
|
nsLayoutUtils::GetPageFrame(nsIFrame* aFrame)
|
|
{
|
|
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
|
|
if (frame->GetType() == nsLayoutAtoms::pageFrame) {
|
|
return frame;
|
|
}
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
nsIFrame*
|
|
nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
|
|
if (nsLayoutAtoms::placeholderFrame != aFrame->GetType()) {
|
|
return nsnull;
|
|
}
|
|
|
|
nsIFrame *outOfFlowFrame =
|
|
nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
|
|
if (outOfFlowFrame->GetStyleDisplay()->IsFloating()) {
|
|
return outOfFlowFrame;
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
// static
|
|
PRBool
|
|
nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent,
|
|
nsIFrame* aFrame,
|
|
nsIAtom* aPseudoElement)
|
|
{
|
|
NS_PRECONDITION(aFrame, "Must have a frame");
|
|
NS_PRECONDITION(aPseudoElement, "Must have a pseudo name");
|
|
|
|
if (!aFrame->IsGeneratedContentFrame()) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
if (aContent && aFrame->GetContent() != aContent) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
return aFrame->GetStyleContext()->GetPseudoType() == aPseudoElement;
|
|
}
|
|
|
|
// static
|
|
nsIFrame*
|
|
nsLayoutUtils::GetCrossDocParentFrame(nsIFrame* aFrame)
|
|
{
|
|
nsIFrame* p = aFrame->GetParent();
|
|
if (p)
|
|
return p;
|
|
|
|
nsIView* v = aFrame->GetView();
|
|
if (!v)
|
|
return nsnull;
|
|
v = v->GetParent(); // anonymous inner view
|
|
if (!v)
|
|
return nsnull;
|
|
v = v->GetParent(); // subdocumentframe's view
|
|
if (!v)
|
|
return nsnull;
|
|
return NS_STATIC_CAST(nsIFrame*, v->GetClientData());
|
|
}
|
|
|
|
// static
|
|
PRBool
|
|
nsLayoutUtils::IsProperAncestorFrameCrossDoc(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
|
|
nsIFrame* aCommonAncestor)
|
|
{
|
|
if (aFrame == aCommonAncestor)
|
|
return PR_FALSE;
|
|
|
|
nsIFrame* parentFrame = GetCrossDocParentFrame(aFrame);
|
|
|
|
while (parentFrame != aCommonAncestor) {
|
|
if (parentFrame == aAncestorFrame)
|
|
return PR_TRUE;
|
|
|
|
parentFrame = GetCrossDocParentFrame(parentFrame);
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
// static
|
|
PRBool
|
|
nsLayoutUtils::IsProperAncestorFrame(nsIFrame* aAncestorFrame, nsIFrame* aFrame,
|
|
nsIFrame* aCommonAncestor)
|
|
{
|
|
if (aFrame == aCommonAncestor) {
|
|
return PR_FALSE;
|
|
}
|
|
|
|
nsIFrame* parentFrame = aFrame->GetParent();
|
|
|
|
while (parentFrame != aCommonAncestor) {
|
|
if (parentFrame == aAncestorFrame) {
|
|
return PR_TRUE;
|
|
}
|
|
|
|
parentFrame = parentFrame->GetParent();
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
// static
|
|
PRInt32
|
|
nsLayoutUtils::DoCompareTreePosition(nsIContent* aContent1,
|
|
nsIContent* aContent2,
|
|
PRInt32 aIf1Ancestor,
|
|
PRInt32 aIf2Ancestor,
|
|
nsIContent* aCommonAncestor)
|
|
{
|
|
NS_PRECONDITION(aContent1, "aContent1 must not be null");
|
|
NS_PRECONDITION(aContent2, "aContent2 must not be null");
|
|
|
|
nsAutoVoidArray content1Ancestors;
|
|
nsINode* c1;
|
|
for (c1 = aContent1; c1 && c1 != aCommonAncestor; c1 = c1->GetNodeParent()) {
|
|
content1Ancestors.AppendElement(c1);
|
|
}
|
|
if (!c1 && aCommonAncestor) {
|
|
// So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
|
|
// Never mind. We can continue as if aCommonAncestor was null.
|
|
aCommonAncestor = nsnull;
|
|
}
|
|
|
|
nsAutoVoidArray content2Ancestors;
|
|
nsINode* c2;
|
|
for (c2 = aContent2; c2 && c2 != aCommonAncestor; c2 = c2->GetNodeParent()) {
|
|
content2Ancestors.AppendElement(c2);
|
|
}
|
|
if (!c2 && aCommonAncestor) {
|
|
// So, it turns out aCommonAncestor was not an ancestor of c2.
|
|
// We need to retry with no common ancestor hint.
|
|
return DoCompareTreePosition(aContent1, aContent2,
|
|
aIf1Ancestor, aIf2Ancestor, nsnull);
|
|
}
|
|
|
|
int last1 = content1Ancestors.Count() - 1;
|
|
int last2 = content2Ancestors.Count() - 1;
|
|
nsINode* content1Ancestor = nsnull;
|
|
nsINode* content2Ancestor = nsnull;
|
|
while (last1 >= 0 && last2 >= 0
|
|
&& ((content1Ancestor = NS_STATIC_CAST(nsINode*, content1Ancestors.ElementAt(last1)))
|
|
== (content2Ancestor = NS_STATIC_CAST(nsINode*, content2Ancestors.ElementAt(last2))))) {
|
|
last1--;
|
|
last2--;
|
|
}
|
|
|
|
if (last1 < 0) {
|
|
if (last2 < 0) {
|
|
NS_ASSERTION(aContent1 == aContent2, "internal error?");
|
|
return 0;
|
|
}
|
|
// aContent1 is an ancestor of aContent2
|
|
return aIf1Ancestor;
|
|
}
|
|
|
|
if (last2 < 0) {
|
|
// aContent2 is an ancestor of aContent1
|
|
return aIf2Ancestor;
|
|
}
|
|
|
|
// content1Ancestor != content2Ancestor, so they must be siblings with the same parent
|
|
nsINode* parent = content1Ancestor->GetNodeParent();
|
|
NS_ASSERTION(parent, "no common ancestor at all???");
|
|
if (!parent) { // different documents??
|
|
return 0;
|
|
}
|
|
|
|
PRInt32 index1 = parent->IndexOf(content1Ancestor);
|
|
PRInt32 index2 = parent->IndexOf(content2Ancestor);
|
|
if (index1 < 0 || index2 < 0) {
|
|
// one of them must be anonymous; we can't determine the order
|
|
return 0;
|
|
}
|
|
|
|
return index1 - index2;
|
|
}
|
|
|
|
// static
|
|
nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
|
|
if (!aFrame) {
|
|
return nsnull;
|
|
}
|
|
|
|
nsIFrame* next;
|
|
while ((next = aFrame->GetNextSibling()) != nsnull) {
|
|
aFrame = next;
|
|
}
|
|
return aFrame;
|
|
}
|
|
|
|
// static
|
|
nsIView*
|
|
nsLayoutUtils::FindSiblingViewFor(nsIView* aParentView, nsIFrame* aFrame) {
|
|
nsIFrame* parentViewFrame = NS_STATIC_CAST(nsIFrame*, aParentView->GetClientData());
|
|
nsIContent* parentViewContent = parentViewFrame ? parentViewFrame->GetContent() : nsnull;
|
|
for (nsIView* insertBefore = aParentView->GetFirstChild(); insertBefore;
|
|
insertBefore = insertBefore->GetNextSibling()) {
|
|
nsIFrame* f = NS_STATIC_CAST(nsIFrame*, insertBefore->GetClientData());
|
|
if (!f) {
|
|
// this view could be some anonymous view attached to a meaningful parent
|
|
for (nsIView* searchView = insertBefore->GetParent(); searchView;
|
|
searchView = searchView->GetParent()) {
|
|
f = NS_STATIC_CAST(nsIFrame*, searchView->GetClientData());
|
|
if (f) {
|
|
break;
|
|
}
|
|
}
|
|
NS_ASSERTION(f, "Can't find a frame anywhere!");
|
|
}
|
|
if (!f || !aFrame->GetContent() || !f->GetContent() ||
|
|
CompareTreePosition(aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) {
|
|
// aFrame's content is after f's content (or we just don't know),
|
|
// so put our view before f's view
|
|
return insertBefore;
|
|
}
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
//static
|
|
nsIScrollableFrame*
|
|
nsLayoutUtils::GetScrollableFrameFor(nsIFrame *aScrolledFrame)
|
|
{
|
|
nsIFrame *frame = aScrolledFrame->GetParent();
|
|
if (!frame) {
|
|
return nsnull;
|
|
}
|
|
nsIScrollableFrame *sf;
|
|
CallQueryInterface(frame, &sf);
|
|
return sf;
|
|
}
|
|
|
|
//static
|
|
nsIScrollableFrame*
|
|
nsLayoutUtils::GetScrollableFrameFor(nsIScrollableView *aScrollableView)
|
|
{
|
|
nsIFrame *frame = GetFrameFor(aScrollableView->View()->GetParent());
|
|
if (frame) {
|
|
nsIScrollableFrame *sf;
|
|
CallQueryInterface(frame, &sf);
|
|
return sf;
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
//static
|
|
nsPresContext::ScrollbarStyles
|
|
nsLayoutUtils::ScrollbarStylesOfView(nsIScrollableView *aScrollableView)
|
|
{
|
|
nsIScrollableFrame *sf = GetScrollableFrameFor(aScrollableView);
|
|
return sf ? sf->GetScrollbarStyles() :
|
|
nsPresContext::ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN,
|
|
NS_STYLE_OVERFLOW_HIDDEN);
|
|
}
|
|
|
|
// static
|
|
nsIScrollableView*
|
|
nsLayoutUtils::GetNearestScrollingView(nsIView* aView, Direction aDirection)
|
|
{
|
|
// If aDirection is eEither, find first view with a scrolllable frame.
|
|
// Otherwise, find the first view that has a scrollable frame whose
|
|
// ScrollbarStyles is not NS_STYLE_OVERFLOW_HIDDEN in aDirection
|
|
// and where there is something currently not visible
|
|
// that can be scrolled to in aDirection.
|
|
NS_ASSERTION(aView, "GetNearestScrollingView expects a non-null view");
|
|
nsIScrollableView* scrollableView = nsnull;
|
|
for (; aView; aView = aView->GetParent()) {
|
|
scrollableView = aView->ToScrollableView();
|
|
if (scrollableView) {
|
|
nsPresContext::ScrollbarStyles ss =
|
|
nsLayoutUtils::ScrollbarStylesOfView(scrollableView);
|
|
nsIScrollableFrame *scrollableFrame = GetScrollableFrameFor(scrollableView);
|
|
NS_ASSERTION(scrollableFrame, "Must have scrollable frame for view!");
|
|
nsMargin margin = scrollableFrame->GetActualScrollbarSizes();
|
|
// Get size of total scrollable area
|
|
nscoord totalWidth, totalHeight;
|
|
scrollableView->GetContainerSize(&totalWidth, &totalHeight);
|
|
// Get size of currently visible area
|
|
nsSize visibleSize = aView->GetBounds().Size();
|
|
// aDirection can be eHorizontal, eVertical, or eEither
|
|
// If scrolling in a specific direction, require visible scrollbars or
|
|
// something to scroll to in that direction.
|
|
if (aDirection != eHorizontal &&
|
|
ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN &&
|
|
(aDirection == eEither || totalHeight > visibleSize.height || margin.LeftRight()))
|
|
break;
|
|
if (aDirection != eVertical &&
|
|
ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN &&
|
|
(aDirection == eEither || totalWidth > visibleSize.width || margin.TopBottom()))
|
|
break;
|
|
}
|
|
}
|
|
return scrollableView;
|
|
}
|
|
|
|
nsPoint
|
|
nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(nsIDOMEvent* aDOMEvent, nsIFrame* aFrame)
|
|
{
|
|
nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aDOMEvent));
|
|
NS_ASSERTION(privateEvent, "bad implementation");
|
|
if (!privateEvent)
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
nsEvent* event;
|
|
nsresult rv = privateEvent->GetInternalNSEvent(&event);
|
|
if (NS_FAILED(rv))
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
return GetEventCoordinatesRelativeTo(event, aFrame);
|
|
}
|
|
|
|
nsPoint
|
|
nsLayoutUtils::GetEventCoordinatesRelativeTo(nsEvent* aEvent, nsIFrame* aFrame)
|
|
{
|
|
if (!aEvent || (aEvent->eventStructType != NS_MOUSE_EVENT &&
|
|
aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT))
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
nsGUIEvent* GUIEvent = NS_STATIC_CAST(nsGUIEvent*, aEvent);
|
|
if (!GUIEvent->widget)
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
// If it is, or is a descendant of, an SVG foreignobject frame,
|
|
// then we need to do extra work
|
|
nsIFrame* rootFrame = aFrame;
|
|
for (nsIFrame* f = aFrame; f; f = GetCrossDocParentFrame(f)) {
|
|
#ifdef MOZ_SVG_FOREIGNOBJECT
|
|
if (f->IsFrameOfType(nsIFrame::eSVGForeignObject) && f->GetFirstChild(nsnull)) {
|
|
nsSVGForeignObjectFrame* fo = NS_STATIC_CAST(nsSVGForeignObjectFrame*, f);
|
|
nsIFrame* outer = nsSVGUtils::GetOuterSVGFrame(fo);
|
|
return fo->TransformPointFromOuter(
|
|
GetEventCoordinatesRelativeTo(aEvent, outer)) -
|
|
aFrame->GetOffsetTo(fo->GetFirstChild(nsnull));
|
|
}
|
|
#endif
|
|
rootFrame = f;
|
|
}
|
|
|
|
nsIView* rootView = rootFrame->GetView();
|
|
if (!rootView)
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
nsPoint widgetToView = TranslateWidgetToView(rootFrame->GetPresContext(),
|
|
GUIEvent->widget, GUIEvent->refPoint,
|
|
rootView);
|
|
|
|
if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
return widgetToView - aFrame->GetOffsetTo(rootFrame);
|
|
}
|
|
|
|
nsPoint
|
|
nsLayoutUtils::GetEventCoordinatesForNearestView(nsEvent* aEvent,
|
|
nsIFrame* aFrame,
|
|
nsIView** aView)
|
|
{
|
|
if (!aEvent || (aEvent->eventStructType != NS_MOUSE_EVENT &&
|
|
aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT))
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
nsGUIEvent* GUIEvent = NS_STATIC_CAST(nsGUIEvent*, aEvent);
|
|
if (!GUIEvent->widget)
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
|
|
nsPoint viewToFrame;
|
|
nsIView* frameView;
|
|
aFrame->GetOffsetFromView(viewToFrame, &frameView);
|
|
if (aView)
|
|
*aView = frameView;
|
|
|
|
return TranslateWidgetToView(aFrame->GetPresContext(), GUIEvent->widget,
|
|
GUIEvent->refPoint, frameView);
|
|
}
|
|
|
|
nsPoint
|
|
nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext,
|
|
nsIWidget* aWidget, nsIntPoint aPt,
|
|
nsIView* aView)
|
|
{
|
|
nsIView* baseView = nsIView::GetViewFor(aWidget);
|
|
if (!baseView)
|
|
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
|
|
nsPoint viewToWidget;
|
|
nsIWidget* wid = baseView->GetNearestWidget(&viewToWidget);
|
|
NS_ASSERTION(aWidget == wid, "Clashing widgets");
|
|
float pixelsToTwips = aPresContext->PixelsToTwips();
|
|
nsPoint refPointTwips(NSIntPixelsToTwips(aPt.x, pixelsToTwips),
|
|
NSIntPixelsToTwips(aPt.y, pixelsToTwips));
|
|
return refPointTwips - viewToWidget - aView->GetOffsetTo(baseView);
|
|
}
|
|
|
|
// Combine aNewBreakType with aOrigBreakType, but limit the break types
|
|
// to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT.
|
|
PRUint8
|
|
nsLayoutUtils::CombineBreakType(PRUint8 aOrigBreakType,
|
|
PRUint8 aNewBreakType)
|
|
{
|
|
PRUint8 breakType = aOrigBreakType;
|
|
switch(breakType) {
|
|
case NS_STYLE_CLEAR_LEFT:
|
|
if ((NS_STYLE_CLEAR_RIGHT == aNewBreakType) ||
|
|
(NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) {
|
|
breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT;
|
|
}
|
|
break;
|
|
case NS_STYLE_CLEAR_RIGHT:
|
|
if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) ||
|
|
(NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) {
|
|
breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT;
|
|
}
|
|
break;
|
|
case NS_STYLE_CLEAR_NONE:
|
|
if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) ||
|
|
(NS_STYLE_CLEAR_RIGHT == aNewBreakType) ||
|
|
(NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) {
|
|
breakType = aNewBreakType;
|
|
}
|
|
}
|
|
return breakType;
|
|
}
|
|
|
|
PRBool
|
|
nsLayoutUtils::IsInitialContainingBlock(nsIFrame* aFrame)
|
|
{
|
|
return aFrame ==
|
|
aFrame->GetPresContext()->PresShell()->FrameConstructor()->GetInitialContainingBlock();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
#include <stdio.h>
|
|
|
|
static PRBool gDumpPaintList = PR_FALSE;
|
|
static PRBool gDumpEventList = PR_FALSE;
|
|
static PRBool gDumpRepaintRegionForCopy = PR_FALSE;
|
|
#endif
|
|
|
|
nsIFrame*
|
|
nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt)
|
|
{
|
|
nsDisplayListBuilder builder(aFrame, PR_TRUE, PR_FALSE);
|
|
nsDisplayList list;
|
|
nsRect target(aPt, nsSize(1, 1));
|
|
|
|
builder.EnterPresShell(aFrame, target);
|
|
|
|
nsresult rv =
|
|
aFrame->BuildDisplayListForStackingContext(&builder, target, &list);
|
|
|
|
builder.LeavePresShell(aFrame, target);
|
|
NS_ENSURE_SUCCESS(rv, nsnull);
|
|
|
|
#ifdef DEBUG
|
|
if (gDumpEventList) {
|
|
fprintf(stderr, "Event handling --- (%d,%d):\n", aPt.x, aPt.y);
|
|
nsIFrameDebug::PrintDisplayList(&builder, list);
|
|
}
|
|
#endif
|
|
|
|
nsIFrame* result = list.HitTest(&builder, aPt);
|
|
list.DeleteAll();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A simple display item that just renders a solid color across the entire
|
|
* visible area.
|
|
*/
|
|
class nsDisplaySolidColor : public nsDisplayItem {
|
|
public:
|
|
nsDisplaySolidColor(nsIFrame* aFrame, nscolor aColor)
|
|
: nsDisplayItem(aFrame), mColor(aColor) {
|
|
MOZ_COUNT_CTOR(nsDisplaySolidColor);
|
|
}
|
|
#ifdef NS_BUILD_REFCNT_LOGGING
|
|
virtual ~nsDisplaySolidColor() {
|
|
MOZ_COUNT_DTOR(nsDisplaySolidColor);
|
|
}
|
|
#endif
|
|
|
|
virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
|
|
const nsRect& aDirtyRect);
|
|
NS_DISPLAY_DECL_NAME("SolidColor")
|
|
private:
|
|
nscolor mColor;
|
|
};
|
|
|
|
void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder,
|
|
nsIRenderingContext* aCtx, const nsRect& aDirtyRect)
|
|
{
|
|
aCtx->SetColor(mColor);
|
|
aCtx->FillRect(aDirtyRect);
|
|
}
|
|
|
|
nsresult
|
|
nsLayoutUtils::PaintFrame(nsIRenderingContext* aRenderingContext, nsIFrame* aFrame,
|
|
const nsRegion& aDirtyRegion, nscolor aBackground)
|
|
{
|
|
nsDisplayListBuilder builder(aFrame, PR_FALSE, PR_TRUE);
|
|
nsDisplayList list;
|
|
nsRect dirtyRect = aDirtyRegion.GetBounds();
|
|
|
|
builder.EnterPresShell(aFrame, dirtyRect);
|
|
|
|
nsresult rv =
|
|
aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list);
|
|
|
|
builder.LeavePresShell(aFrame, dirtyRect);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (NS_GET_A(aBackground) > 0) {
|
|
// Fill the visible area with a background color. In the common case,
|
|
// the visible area is entirely covered by the background of the root
|
|
// document (at least!) so this will be removed by the optimizer. In some
|
|
// cases we might not have a root frame, so this will prevent garbage
|
|
// from being drawn.
|
|
rv = list.AppendNewToBottom(new (&builder) nsDisplaySolidColor(aFrame, aBackground));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (gDumpPaintList) {
|
|
fprintf(stderr, "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
|
|
dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
|
|
nsIFrameDebug::PrintDisplayList(&builder, list);
|
|
}
|
|
#endif
|
|
|
|
nsRegion visibleRegion = aDirtyRegion;
|
|
list.OptimizeVisibility(&builder, &visibleRegion);
|
|
|
|
#ifdef DEBUG
|
|
if (gDumpPaintList) {
|
|
fprintf(stderr, "Painting --- after optimization:\n");
|
|
nsIFrameDebug::PrintDisplayList(&builder, list);
|
|
}
|
|
#endif
|
|
|
|
list.Paint(&builder, aRenderingContext, aDirtyRegion.GetBounds());
|
|
// Flush the list so we don't trigger the IsEmpty-on-destruction assertion
|
|
list.DeleteAll();
|
|
return NS_OK;
|
|
}
|
|
|
|
static void
|
|
AccumulateItemInRegion(nsRegion* aRegion, const nsRect& aAreaRect,
|
|
const nsRect& aItemRect, nsDisplayItem* aItem)
|
|
{
|
|
nsRect damageRect;
|
|
if (damageRect.IntersectRect(aAreaRect, aItemRect)) {
|
|
#ifdef DEBUG
|
|
if (gDumpRepaintRegionForCopy) {
|
|
fprintf(stderr, "Adding rect %d,%d,%d,%d for frame %p\n",
|
|
damageRect.x, damageRect.y, damageRect.width, damageRect.height,
|
|
(void*)aItem->GetUnderlyingFrame());
|
|
}
|
|
#endif
|
|
aRegion->Or(*aRegion, damageRect);
|
|
}
|
|
}
|
|
|
|
static void
|
|
AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
|
|
const nsRect& aRect, const nsRect& aClipRect, nsPoint aDelta,
|
|
nsRegion* aRegion)
|
|
{
|
|
for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
|
|
nsDisplayList* sublist = item->GetList();
|
|
if (sublist) {
|
|
if (item->GetType() == nsDisplayItem::TYPE_CLIP) {
|
|
nsRect clip;
|
|
clip.IntersectRect(aClipRect, NS_STATIC_CAST(nsDisplayClip*, item)->GetClipRect());
|
|
AddItemsToRegion(aBuilder, sublist, aRect, clip, aDelta, aRegion);
|
|
} else {
|
|
// opacity, or a generic sublist
|
|
AddItemsToRegion(aBuilder, sublist, aRect, aClipRect, aDelta, aRegion);
|
|
}
|
|
} else {
|
|
// Items left in the list are either IsVaryingRelativeToFrame
|
|
// or !IsMovingFrame (i.e., not in the moving subtree)
|
|
nsRect r;
|
|
if (r.IntersectRect(aClipRect, item->GetBounds(aBuilder))) {
|
|
PRBool inMovingSubtree = PR_FALSE;
|
|
if (item->IsVaryingRelativeToFrame(aBuilder, aBuilder->GetRootMovingFrame())) {
|
|
nsIFrame* f = item->GetUnderlyingFrame();
|
|
NS_ASSERTION(f, "Must have an underlying frame for leaf item");
|
|
inMovingSubtree = aBuilder->IsMovingFrame(f);
|
|
AccumulateItemInRegion(aRegion, aRect + aDelta, r, item);
|
|
}
|
|
|
|
if (!inMovingSubtree) {
|
|
// if it's uniform and it includes both the old and new areas, then
|
|
// we don't need to paint it
|
|
PRBool skip = r.Contains(aRect) && r.Contains(aRect + aDelta) &&
|
|
item->IsUniform(aBuilder);
|
|
if (!skip) {
|
|
// area where a non-moving element is visible must be repainted
|
|
AccumulateItemInRegion(aRegion, aRect + aDelta, r, item);
|
|
// we may have bitblitted an area that was painted by a non-moving
|
|
// element. This bitblitted data is invalid and was copied to
|
|
// "r + aDelta".
|
|
AccumulateItemInRegion(aRegion, aRect + aDelta, r + aDelta, item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
|
|
nsIFrame* aMovingFrame,
|
|
nsPoint aDelta,
|
|
const nsRect& aCopyRect,
|
|
nsRegion* aRepaintRegion)
|
|
{
|
|
// Build the 'after' display list over the whole area of interest.
|
|
// Frames under aMovingFrame will not be allowed to affect (clip or cover)
|
|
// non-moving frame display items ... then we can be sure the non-moving
|
|
// frame display items we get are the same ones we would have gotten if
|
|
// we had constructed the 'before' display list.
|
|
// (We have to build the 'after' display list because the frame/view
|
|
// hierarchy has already been updated for the move.)
|
|
nsRect rect;
|
|
rect.UnionRect(aCopyRect, aCopyRect + aDelta);
|
|
nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE, aMovingFrame);
|
|
nsDisplayList list;
|
|
|
|
builder.EnterPresShell(aRootFrame, rect);
|
|
|
|
nsresult rv =
|
|
aRootFrame->BuildDisplayListForStackingContext(&builder, rect, &list);
|
|
|
|
builder.LeavePresShell(aRootFrame, rect);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
#ifdef DEBUG
|
|
if (gDumpRepaintRegionForCopy) {
|
|
fprintf(stderr,
|
|
"Repaint region for copy --- before optimization (area %d,%d,%d,%d, frame %p):\n",
|
|
rect.x, rect.y, rect.width, rect.height, (void*)aMovingFrame);
|
|
nsIFrameDebug::PrintDisplayList(&builder, list);
|
|
}
|
|
#endif
|
|
|
|
// Optimize for visibility, but frames under aMovingFrame will not be
|
|
// considered opaque, so they don't cover non-moving frames.
|
|
nsRegion visibleRegion(aCopyRect);
|
|
visibleRegion.Or(visibleRegion, aCopyRect + aDelta);
|
|
list.OptimizeVisibility(&builder, &visibleRegion);
|
|
|
|
#ifdef DEBUG
|
|
if (gDumpRepaintRegionForCopy) {
|
|
fprintf(stderr, "Repaint region for copy --- after optimization:\n");
|
|
nsIFrameDebug::PrintDisplayList(&builder, list);
|
|
}
|
|
#endif
|
|
|
|
aRepaintRegion->SetEmpty();
|
|
// Any visible non-moving display items get added to the repaint region
|
|
// a) at their current location and b) offset by -aPt (their position in
|
|
// the 'before' display list) (unless they're uniform and we can exclude them).
|
|
// Also, any visible position-varying display items get added to the
|
|
// repaint region. All these areas are confined to aCopyRect+aDelta.
|
|
// We could do more work here: e.g., do another optimize-visibility pass
|
|
// with the moving items taken into account, either on the before-list
|
|
// or the after-list, or even both if we cloned the display lists ... but
|
|
// it's probably not worth it.
|
|
AddItemsToRegion(&builder, &list, aCopyRect, rect, aDelta, aRepaintRegion);
|
|
// Flush the list so we don't trigger the IsEmpty-on-destruction assertion
|
|
list.DeleteAll();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsLayoutUtils::CreateOffscreenContext(nsIDeviceContext* deviceContext, nsIDrawingSurface* surface,
|
|
const nsRect& aRect, nsIRenderingContext** aResult)
|
|
{
|
|
nsresult rv;
|
|
nsIRenderingContext *context = nsnull;
|
|
|
|
rv = deviceContext->CreateRenderingContext(surface, context);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// always initialize clipping, linux won't draw images otherwise.
|
|
nsRect clip(0, 0, aRect.width, aRect.height);
|
|
context->SetClipRect(clip, nsClipCombine_kReplace);
|
|
|
|
context->Translate(-aRect.x, -aRect.y);
|
|
|
|
*aResult = context;
|
|
return NS_OK;
|
|
}
|
|
|
|
PRInt32
|
|
nsLayoutUtils::GetZIndex(nsIFrame* aFrame) {
|
|
if (!aFrame->GetStyleDisplay()->IsPositioned())
|
|
return 0;
|
|
|
|
const nsStylePosition* position =
|
|
aFrame->GetStylePosition();
|
|
if (position->mZIndex.GetUnit() == eStyleUnit_Integer)
|
|
return position->mZIndex.GetIntValue();
|
|
|
|
// sort the auto and 0 elements together
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Uses a binary search for find where the cursor falls in the line of text
|
|
* It also keeps track of the part of the string that has already been measured
|
|
* so it doesn't have to keep measuring the same text over and over
|
|
*
|
|
* @param "aBaseWidth" contains the width in twips of the portion
|
|
* of the text that has already been measured, and aBaseInx contains
|
|
* the index of the text that has already been measured.
|
|
*
|
|
* @param aTextWidth returns the (in twips) the length of the text that falls
|
|
* before the cursor aIndex contains the index of the text where the cursor falls
|
|
*/
|
|
PRBool
|
|
nsLayoutUtils::BinarySearchForPosition(nsIRenderingContext* aRendContext,
|
|
const PRUnichar* aText,
|
|
PRInt32 aBaseWidth,
|
|
PRInt32 aBaseInx,
|
|
PRInt32 aStartInx,
|
|
PRInt32 aEndInx,
|
|
PRInt32 aCursorPos,
|
|
PRInt32& aIndex,
|
|
PRInt32& aTextWidth)
|
|
{
|
|
PRInt32 range = aEndInx - aStartInx;
|
|
if ((range == 1) || (range == 2 && IS_HIGH_SURROGATE(aText[aStartInx]))) {
|
|
aIndex = aStartInx + aBaseInx;
|
|
aRendContext->GetWidth(aText, aIndex, aTextWidth);
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRInt32 inx = aStartInx + (range / 2);
|
|
|
|
// Make sure we don't leave a dangling low surrogate
|
|
if (IS_HIGH_SURROGATE(aText[inx-1]))
|
|
inx++;
|
|
|
|
PRInt32 textWidth = 0;
|
|
aRendContext->GetWidth(aText, inx, textWidth);
|
|
|
|
PRInt32 fullWidth = aBaseWidth + textWidth;
|
|
if (fullWidth == aCursorPos) {
|
|
aTextWidth = textWidth;
|
|
aIndex = inx;
|
|
return PR_TRUE;
|
|
} else if (aCursorPos < fullWidth) {
|
|
aTextWidth = aBaseWidth;
|
|
if (BinarySearchForPosition(aRendContext, aText, aBaseWidth, aBaseInx, aStartInx, inx, aCursorPos, aIndex, aTextWidth)) {
|
|
return PR_TRUE;
|
|
}
|
|
} else {
|
|
aTextWidth = fullWidth;
|
|
if (BinarySearchForPosition(aRendContext, aText, aBaseWidth, aBaseInx, inx, aEndInx, aCursorPos, aIndex, aTextWidth)) {
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
void
|
|
nsLayoutUtils::ScrollIntoView(nsIFormControlFrame* aFormFrame)
|
|
{
|
|
NS_ASSERTION(aFormFrame, "Null frame passed into ScrollIntoView");
|
|
nsIFrame* frame = nsnull;
|
|
CallQueryInterface(aFormFrame, &frame);
|
|
NS_ASSERTION(frame, "Form frame did not implement nsIFrame.");
|
|
if (frame) {
|
|
nsIPresShell* presShell = frame->GetPresContext()->GetPresShell();
|
|
if (presShell) {
|
|
presShell->ScrollFrameIntoView(frame,
|
|
NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
|
|
NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsRect
|
|
nsLayoutUtils::GetAllInFlowBoundingRect(nsIFrame* aFrame)
|
|
{
|
|
// Get the union of all rectangles in this and continuation frames
|
|
nsRect r = aFrame->GetRect();
|
|
nsIFrame* parent = aFrame->GetParent();
|
|
if (!parent)
|
|
return r;
|
|
|
|
for (nsIFrame* f = aFrame->GetNextContinuation(); f; f = f->GetNextContinuation()) {
|
|
r.UnionRect(r, f->GetRect() + f->GetOffsetTo(parent));
|
|
}
|
|
|
|
if (r.IsEmpty()) {
|
|
// It could happen that all the rects are empty (eg zero-width or
|
|
// zero-height). In that case, use the first rect for the frame.
|
|
r = aFrame->GetRect();
|
|
}
|
|
|
|
return r - aFrame->GetPosition();
|
|
}
|
|
|
|
nsresult
|
|
nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame,
|
|
nsIFontMetrics** aFontMetrics)
|
|
{
|
|
nsStyleContext* sc = aFrame->GetStyleContext();
|
|
return aFrame->GetPresContext()->DeviceContext()->
|
|
GetMetricsFor(sc->GetStyleFont()->mFont,
|
|
sc->GetStyleVisibility()->mLangGroup,
|
|
*aFontMetrics);
|
|
}
|