diff options
Diffstat (limited to 'layout/base/GeometryUtils.cpp')
-rw-r--r-- | layout/base/GeometryUtils.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/layout/base/GeometryUtils.cpp b/layout/base/GeometryUtils.cpp new file mode 100644 index 000000000..8bbb8fbcd --- /dev/null +++ b/layout/base/GeometryUtils.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GeometryUtils.h" + +#include "mozilla/dom/DOMPointBinding.h" +#include "mozilla/dom/GeometryUtilsBinding.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Text.h" +#include "mozilla/dom/DOMPoint.h" +#include "mozilla/dom/DOMQuad.h" +#include "mozilla/dom/DOMRect.h" +#include "nsContentUtils.h" +#include "nsIFrame.h" +#include "nsGenericDOMDataNode.h" +#include "nsCSSFrameConstructor.h" +#include "nsLayoutUtils.h" +#include "nsSVGUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +enum GeometryNodeType { + GEOMETRY_NODE_ELEMENT, + GEOMETRY_NODE_TEXT, + GEOMETRY_NODE_DOCUMENT +}; + +static nsIFrame* +GetFrameForNode(nsINode* aNode, GeometryNodeType aType) +{ + nsIDocument* doc = aNode->OwnerDoc(); + doc->FlushPendingNotifications(Flush_Layout); + switch (aType) { + case GEOMETRY_NODE_ELEMENT: + return aNode->AsContent()->GetPrimaryFrame(); + case GEOMETRY_NODE_TEXT: { + nsIPresShell* presShell = doc->GetShell(); + if (presShell) { + return presShell->FrameConstructor()->EnsureFrameForTextNode( + static_cast<nsGenericDOMDataNode*>(aNode)); + } + return nullptr; + } + case GEOMETRY_NODE_DOCUMENT: { + nsIPresShell* presShell = doc->GetShell(); + return presShell ? presShell->GetRootFrame() : nullptr; + } + default: + MOZ_ASSERT(false, "Unknown GeometryNodeType"); + return nullptr; + } +} + +static nsIFrame* +GetFrameForGeometryNode(const Optional<OwningGeometryNode>& aGeometryNode, + nsINode* aDefaultNode) +{ + if (!aGeometryNode.WasPassed()) { + return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT); + } + + const OwningGeometryNode& value = aGeometryNode.Value(); + if (value.IsElement()) { + return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT); + } + if (value.IsDocument()) { + return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); + } + return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT); +} + +static nsIFrame* +GetFrameForGeometryNode(const GeometryNode& aGeometryNode) +{ + if (aGeometryNode.IsElement()) { + return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT); + } + if (aGeometryNode.IsDocument()) { + return GetFrameForNode(&aGeometryNode.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); + } + return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT); +} + +static nsIFrame* +GetFrameForNode(nsINode* aNode) +{ + if (aNode->IsElement()) { + return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT); + } + if (aNode == aNode->OwnerDoc()) { + return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT); + } + NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type"); + return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT); +} + +static nsIFrame* +GetFirstNonAnonymousFrameForGeometryNode(const Optional<OwningGeometryNode>& aNode, + nsINode* aDefaultNode) +{ + nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode); + if (f) { + f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); + } + return f; +} + +static nsIFrame* +GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode& aNode) +{ + nsIFrame* f = GetFrameForGeometryNode(aNode); + if (f) { + f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); + } + return f; +} + +static nsIFrame* +GetFirstNonAnonymousFrameForNode(nsINode* aNode) +{ + nsIFrame* f = GetFrameForNode(aNode); + if (f) { + f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); + } + return f; +} + +/** + * This can modify aFrame to point to a different frame. This is needed to + * handle SVG, where SVG elements can only compute a rect that's valid with + * respect to the "outer SVG" frame. + */ +static nsRect +GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType) +{ + nsRect r; + nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r); + if (f && f != *aFrame) { + // For non-outer SVG frames, the BoxType is ignored. + *aFrame = f; + return r; + } + + f = *aFrame; + switch (aType) { + case CSSBoxType::Content: r = f->GetContentRectRelativeToSelf(); break; + case CSSBoxType::Padding: r = f->GetPaddingRectRelativeToSelf(); break; + case CSSBoxType::Border: r = nsRect(nsPoint(0, 0), f->GetSize()); break; + case CSSBoxType::Margin: { + r = nsRect(nsPoint(0, 0), f->GetSize()); + r.Inflate(f->GetUsedMargin()); + break; + } + default: MOZ_ASSERT(false, "unknown box type"); return r; + } + + return r; +} + +class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback { +public: + AccumulateQuadCallback(nsISupports* aParentObject, + nsTArray<RefPtr<DOMQuad> >& aResult, + nsIFrame* aRelativeToFrame, + const nsPoint& aRelativeToBoxTopLeft, + CSSBoxType aBoxType) + : mParentObject(aParentObject) + , mResult(aResult) + , mRelativeToFrame(aRelativeToFrame) + , mRelativeToBoxTopLeft(aRelativeToBoxTopLeft) + , mBoxType(aBoxType) + { + if (mBoxType == CSSBoxType::Margin) { + // Don't include the caption margin when computing margins for a + // table + mIncludeCaptionBoxForTable = false; + } + } + + virtual void AddBox(nsIFrame* aFrame) override + { + nsIFrame* f = aFrame; + if (mBoxType == CSSBoxType::Margin && + f->GetType() == nsGkAtoms::tableFrame) { + // Margin boxes for table frames should be taken from the table wrapper + // frame, since that has the margin. + f = f->GetParent(); + } + nsRect box = GetBoxRectForFrame(&f, mBoxType); + nsPoint appUnits[4] = + { box.TopLeft(), box.TopRight(), box.BottomRight(), box.BottomLeft() }; + CSSPoint points[4]; + for (uint32_t i = 0; i < 4; ++i) { + points[i] = CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x), + nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y)); + } + nsLayoutUtils::TransformResult rv = + nsLayoutUtils::TransformPoints(f, mRelativeToFrame, 4, points); + if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { + CSSPoint delta(nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x), + nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y)); + for (uint32_t i = 0; i < 4; ++i) { + points[i] -= delta; + } + } else { + PodArrayZero(points); + } + mResult.AppendElement(new DOMQuad(mParentObject, points)); + } + + nsISupports* mParentObject; + nsTArray<RefPtr<DOMQuad> >& mResult; + nsIFrame* mRelativeToFrame; + nsPoint mRelativeToBoxTopLeft; + CSSBoxType mBoxType; +}; + +static nsPresContext* +FindTopLevelPresContext(nsPresContext* aPC) +{ + bool isChrome = aPC->IsChrome(); + nsPresContext* pc = aPC; + for (;;) { + nsPresContext* parent = pc->GetParentPresContext(); + if (!parent || parent->IsChrome() != isChrome) { + return pc; + } + pc = parent; + } +} + +static bool +CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1, nsIFrame* aFrame2) +{ + nsPresContext* pc1 = aFrame1->PresContext(); + nsPresContext* pc2 = aFrame2->PresContext(); + if (pc1 == pc2) { + return true; + } + if (nsContentUtils::IsCallerChrome()) { + return true; + } + if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) { + return true; + } + return false; +} + +void GetBoxQuads(nsINode* aNode, + const dom::BoxQuadOptions& aOptions, + nsTArray<RefPtr<DOMQuad> >& aResult, + ErrorResult& aRv) +{ + nsIFrame* frame = GetFrameForNode(aNode); + if (!frame) { + // No boxes to return + return; + } + nsWeakFrame weakFrame(frame); + nsIDocument* ownerDoc = aNode->OwnerDoc(); + nsIFrame* relativeToFrame = + GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc); + // The first frame might be destroyed now if the above call lead to an + // EnsureFrameForTextNode call. We need to get the first frame again + // when that happens and re-check it. + if (!weakFrame.IsAlive()) { + frame = GetFrameForNode(aNode); + if (!frame) { + // No boxes to return + return; + } + } + if (!relativeToFrame) { + aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); + return; + } + if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame)) { + aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); + return; + } + // GetBoxRectForFrame can modify relativeToFrame so call it first. + nsPoint relativeToTopLeft = + GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft(); + AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame, + relativeToTopLeft, aOptions.mBox); + nsLayoutUtils::GetAllInFlowBoxes(frame, &callback); +} + +static void +TransformPoints(nsINode* aTo, const GeometryNode& aFrom, + uint32_t aPointCount, CSSPoint* aPoints, + const ConvertCoordinateOptions& aOptions, ErrorResult& aRv) +{ + nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom); + nsWeakFrame weakFrame(fromFrame); + nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo); + // The first frame might be destroyed now if the above call lead to an + // EnsureFrameForTextNode call. We need to get the first frame again + // when that happens. + if (fromFrame && !weakFrame.IsAlive()) { + fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom); + } + if (!fromFrame || !toFrame) { + aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); + return; + } + if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame)) { + aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); + return; + } + + nsPoint fromOffset = GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft(); + nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft(); + CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x), + nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y)); + for (uint32_t i = 0; i < aPointCount; ++i) { + aPoints[i] += fromOffsetGfx; + } + nsLayoutUtils::TransformResult rv = + nsLayoutUtils::TransformPoints(fromFrame, toFrame, aPointCount, aPoints); + if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { + CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x), + nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y)); + for (uint32_t i = 0; i < aPointCount; ++i) { + aPoints[i] -= toOffsetGfx; + } + } else { + PodZero(aPoints, aPointCount); + } +} + +already_AddRefed<DOMQuad> +ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad, + const GeometryNode& aFrom, + const dom::ConvertCoordinateOptions& aOptions, + ErrorResult& aRv) +{ + CSSPoint points[4]; + for (uint32_t i = 0; i < 4; ++i) { + DOMPoint* p = aQuad.Point(i); + if (p->W() != 1.0 || p->Z() != 0.0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + points[i] = CSSPoint(p->X(), p->Y()); + } + TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); + if (aRv.Failed()) { + return nullptr; + } + RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points); + return result.forget(); +} + +already_AddRefed<DOMQuad> +ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect, + const GeometryNode& aFrom, + const dom::ConvertCoordinateOptions& aOptions, + ErrorResult& aRv) +{ + CSSPoint points[4]; + double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height(); + points[0] = CSSPoint(x, y); + points[1] = CSSPoint(x + w, y); + points[2] = CSSPoint(x + w, y + h); + points[3] = CSSPoint(x, y + h); + TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); + if (aRv.Failed()) { + return nullptr; + } + RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points); + return result.forget(); +} + +already_AddRefed<DOMPoint> +ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint, + const GeometryNode& aFrom, + const dom::ConvertCoordinateOptions& aOptions, + ErrorResult& aRv) +{ + if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + CSSPoint point(aPoint.mX, aPoint.mY); + TransformPoints(aTo, aFrom, 1, &point, aOptions, aRv); + if (aRv.Failed()) { + return nullptr; + } + RefPtr<DOMPoint> result = new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y); + return result.forget(); +} + +} // namespace mozilla |