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: #include "GeometryUtils.h" michael@0: michael@0: #include "mozilla/dom/DOMPointBinding.h" michael@0: #include "mozilla/dom/GeometryUtilsBinding.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Text.h" michael@0: #include "mozilla/dom/DOMPoint.h" michael@0: #include "mozilla/dom/DOMQuad.h" michael@0: #include "mozilla/dom/DOMRect.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsGenericDOMDataNode.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsSVGUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace mozilla { michael@0: michael@0: enum GeometryNodeType { michael@0: GEOMETRY_NODE_ELEMENT, michael@0: GEOMETRY_NODE_TEXT, michael@0: GEOMETRY_NODE_DOCUMENT michael@0: }; michael@0: michael@0: static nsIFrame* michael@0: GetFrameForNode(nsINode* aNode, GeometryNodeType aType) michael@0: { michael@0: nsIDocument* doc = aNode->OwnerDoc(); michael@0: doc->FlushPendingNotifications(Flush_Layout); michael@0: switch (aType) { michael@0: case GEOMETRY_NODE_ELEMENT: michael@0: return aNode->AsContent()->GetPrimaryFrame(); michael@0: case GEOMETRY_NODE_TEXT: { michael@0: nsIPresShell* presShell = doc->GetShell(); michael@0: if (presShell) { michael@0: return presShell->FrameConstructor()->EnsureFrameForTextNode( michael@0: static_cast(aNode)); michael@0: } michael@0: return nullptr; michael@0: } michael@0: case GEOMETRY_NODE_DOCUMENT: { michael@0: nsIPresShell* presShell = doc->GetShell(); michael@0: return presShell ? presShell->GetRootFrame() : nullptr; michael@0: } michael@0: default: michael@0: MOZ_ASSERT(false, "Unknown GeometryNodeType"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFrameForGeometryNode(const Optional& aGeometryNode, michael@0: nsINode* aDefaultNode) michael@0: { michael@0: if (!aGeometryNode.WasPassed()) { michael@0: return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT); michael@0: } michael@0: michael@0: const OwningGeometryNode& value = aGeometryNode.Value(); michael@0: if (value.IsElement()) { michael@0: return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT); michael@0: } michael@0: if (value.IsDocument()) { michael@0: return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); michael@0: } michael@0: return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT); michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFrameForGeometryNode(const GeometryNode& aGeometryNode) michael@0: { michael@0: if (aGeometryNode.IsElement()) { michael@0: return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT); michael@0: } michael@0: if (aGeometryNode.IsDocument()) { michael@0: return GetFrameForNode(&aGeometryNode.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); michael@0: } michael@0: return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT); michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFrameForNode(nsINode* aNode) michael@0: { michael@0: if (aNode->IsElement()) { michael@0: return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT); michael@0: } michael@0: if (aNode == aNode->OwnerDoc()) { michael@0: return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT); michael@0: } michael@0: NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type"); michael@0: return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT); michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFirstNonAnonymousFrameForGeometryNode(const Optional& aNode, michael@0: nsINode* aDefaultNode) michael@0: { michael@0: nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode); michael@0: if (f) { michael@0: f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); michael@0: } michael@0: return f; michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode& aNode) michael@0: { michael@0: nsIFrame* f = GetFrameForGeometryNode(aNode); michael@0: if (f) { michael@0: f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); michael@0: } michael@0: return f; michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetFirstNonAnonymousFrameForNode(nsINode* aNode) michael@0: { michael@0: nsIFrame* f = GetFrameForNode(aNode); michael@0: if (f) { michael@0: f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); michael@0: } michael@0: return f; michael@0: } michael@0: michael@0: /** michael@0: * This can modify aFrame to point to a different frame. This is needed to michael@0: * handle SVG, where SVG elements can only compute a rect that's valid with michael@0: * respect to the "outer SVG" frame. michael@0: */ michael@0: static nsRect michael@0: GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType) michael@0: { michael@0: nsRect r; michael@0: nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r); michael@0: if (f) { michael@0: // For SVG, the BoxType is ignored. michael@0: *aFrame = f; michael@0: return r; michael@0: } michael@0: michael@0: f = *aFrame; michael@0: switch (aType) { michael@0: case CSSBoxType::Content: r = f->GetContentRectRelativeToSelf(); break; michael@0: case CSSBoxType::Padding: r = f->GetPaddingRectRelativeToSelf(); break; michael@0: case CSSBoxType::Border: r = nsRect(nsPoint(0, 0), f->GetSize()); break; michael@0: case CSSBoxType::Margin: { michael@0: r = nsRect(nsPoint(0, 0), f->GetSize()); michael@0: r.Inflate(f->GetUsedMargin()); michael@0: break; michael@0: } michael@0: default: MOZ_ASSERT(false, "unknown box type"); return r; michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback { michael@0: public: michael@0: AccumulateQuadCallback(nsISupports* aParentObject, michael@0: nsTArray >& aResult, michael@0: nsIFrame* aRelativeToFrame, michael@0: const nsPoint& aRelativeToBoxTopLeft, michael@0: CSSBoxType aBoxType) michael@0: : mParentObject(aParentObject) michael@0: , mResult(aResult) michael@0: , mRelativeToFrame(aRelativeToFrame) michael@0: , mRelativeToBoxTopLeft(aRelativeToBoxTopLeft) michael@0: , mBoxType(aBoxType) michael@0: { michael@0: } michael@0: michael@0: virtual void AddBox(nsIFrame* aFrame) MOZ_OVERRIDE michael@0: { michael@0: nsIFrame* f = aFrame; michael@0: nsRect box = GetBoxRectForFrame(&f, mBoxType); michael@0: nsPoint appUnits[4] = michael@0: { box.TopLeft(), box.TopRight(), box.BottomRight(), box.BottomLeft() }; michael@0: CSSPoint points[4]; michael@0: for (uint32_t i = 0; i < 4; ++i) { michael@0: points[i] = CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x), michael@0: nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y)); michael@0: } michael@0: nsLayoutUtils::TransformResult rv = michael@0: nsLayoutUtils::TransformPoints(f, mRelativeToFrame, 4, points); michael@0: if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { michael@0: CSSPoint delta(nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x), michael@0: nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y)); michael@0: for (uint32_t i = 0; i < 4; ++i) { michael@0: points[i] -= delta; michael@0: } michael@0: } else { michael@0: PodArrayZero(points); michael@0: } michael@0: mResult.AppendElement(new DOMQuad(mParentObject, points)); michael@0: } michael@0: michael@0: nsISupports* mParentObject; michael@0: nsTArray >& mResult; michael@0: nsIFrame* mRelativeToFrame; michael@0: nsPoint mRelativeToBoxTopLeft; michael@0: CSSBoxType mBoxType; michael@0: }; michael@0: michael@0: static nsPresContext* michael@0: FindTopLevelPresContext(nsPresContext* aPC) michael@0: { michael@0: bool isChrome = aPC->IsChrome(); michael@0: nsPresContext* pc = aPC; michael@0: for (;;) { michael@0: nsPresContext* parent = pc->GetParentPresContext(); michael@0: if (!parent || parent->IsChrome() != isChrome) { michael@0: return pc; michael@0: } michael@0: pc = parent; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1, nsIFrame* aFrame2) michael@0: { michael@0: nsPresContext* pc1 = aFrame1->PresContext(); michael@0: nsPresContext* pc2 = aFrame2->PresContext(); michael@0: if (pc1 == pc2) { michael@0: return true; michael@0: } michael@0: if (nsContentUtils::IsCallerChrome()) { michael@0: return true; michael@0: } michael@0: if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void GetBoxQuads(nsINode* aNode, michael@0: const dom::BoxQuadOptions& aOptions, michael@0: nsTArray >& aResult, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsIFrame* frame = GetFrameForNode(aNode); michael@0: if (!frame) { michael@0: // No boxes to return michael@0: return; michael@0: } michael@0: nsIDocument* ownerDoc = aNode->OwnerDoc(); michael@0: nsIFrame* relativeToFrame = michael@0: GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc); michael@0: if (!relativeToFrame) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return; michael@0: } michael@0: if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame)) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return; michael@0: } michael@0: // GetBoxRectForFrame can modify relativeToFrame so call it first. michael@0: nsPoint relativeToTopLeft = michael@0: GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft(); michael@0: AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame, michael@0: relativeToTopLeft, aOptions.mBox); michael@0: nsLayoutUtils::GetAllInFlowBoxes(frame, &callback); michael@0: } michael@0: michael@0: static void michael@0: TransformPoints(nsINode* aTo, const GeometryNode& aFrom, michael@0: uint32_t aPointCount, CSSPoint* aPoints, michael@0: const ConvertCoordinateOptions& aOptions, ErrorResult& aRv) michael@0: { michael@0: nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom); michael@0: nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo); michael@0: if (!fromFrame || !toFrame) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return; michael@0: } michael@0: if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame)) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsPoint fromOffset = GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft(); michael@0: nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft(); michael@0: CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x), michael@0: nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y)); michael@0: for (uint32_t i = 0; i < aPointCount; ++i) { michael@0: aPoints[i] += fromOffsetGfx; michael@0: } michael@0: nsLayoutUtils::TransformResult rv = michael@0: nsLayoutUtils::TransformPoints(fromFrame, toFrame, aPointCount, aPoints); michael@0: if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { michael@0: CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x), michael@0: nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y)); michael@0: for (uint32_t i = 0; i < aPointCount; ++i) { michael@0: aPoints[i] -= toOffsetGfx; michael@0: } michael@0: } else { michael@0: PodZero(aPoints, aPointCount); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad, michael@0: const GeometryNode& aFrom, michael@0: const dom::ConvertCoordinateOptions& aOptions, michael@0: ErrorResult& aRv) michael@0: { michael@0: CSSPoint points[4]; michael@0: for (uint32_t i = 0; i < 4; ++i) { michael@0: DOMPoint* p = aQuad.Point(i); michael@0: if (p->W() != 1.0 || p->Z() != 0.0) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: points[i] = CSSPoint(p->X(), p->Y()); michael@0: } michael@0: TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: nsRefPtr result = new DOMQuad(aTo->GetParentObject().mObject, points); michael@0: return result.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect, michael@0: const GeometryNode& aFrom, michael@0: const dom::ConvertCoordinateOptions& aOptions, michael@0: ErrorResult& aRv) michael@0: { michael@0: CSSPoint points[4]; michael@0: double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height(); michael@0: points[0] = CSSPoint(x, y); michael@0: points[1] = CSSPoint(x + w, y); michael@0: points[2] = CSSPoint(x + w, y + h); michael@0: points[3] = CSSPoint(x, y + h); michael@0: TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: nsRefPtr result = new DOMQuad(aTo->GetParentObject().mObject, points); michael@0: return result.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint, michael@0: const GeometryNode& aFrom, michael@0: const dom::ConvertCoordinateOptions& aOptions, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: CSSPoint point(aPoint.mX, aPoint.mY); michael@0: TransformPoints(aTo, aFrom, 1, &point, aOptions, aRv); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: nsRefPtr result = new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y); michael@0: return result.forget(); michael@0: } michael@0: michael@0: }