1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "GeometryUtils.h"
9 #include "mozilla/PresShell.h"
10 #include "mozilla/dom/CharacterData.h"
11 #include "mozilla/dom/DOMPointBinding.h"
12 #include "mozilla/dom/GeometryUtilsBinding.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/Text.h"
15 #include "mozilla/dom/DOMPoint.h"
16 #include "mozilla/dom/DOMQuad.h"
17 #include "mozilla/dom/DOMRect.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsLayoutUtils.h"
21 #include "nsSVGUtils.h"
23 using namespace mozilla
;
24 using namespace mozilla::dom
;
28 enum GeometryNodeType
{
29 GEOMETRY_NODE_ELEMENT
,
31 GEOMETRY_NODE_DOCUMENT
34 static nsIFrame
* GetFrameForNode(nsINode
* aNode
, GeometryNodeType aType
) {
35 Document
* doc
= aNode
->OwnerDoc();
36 if (aType
== GEOMETRY_NODE_TEXT
) {
37 if (PresShell
* presShell
= doc
->GetPresShell()) {
38 presShell
->FrameConstructor()->EnsureFrameForTextNodeIsCreatedAfterFlush(
39 static_cast<CharacterData
*>(aNode
));
42 doc
->FlushPendingNotifications(FlushType::Layout
);
45 case GEOMETRY_NODE_TEXT
:
46 case GEOMETRY_NODE_ELEMENT
:
47 return aNode
->AsContent()->GetPrimaryFrame();
48 case GEOMETRY_NODE_DOCUMENT
: {
49 PresShell
* presShell
= doc
->GetPresShell();
50 return presShell
? presShell
->GetRootFrame() : nullptr;
53 MOZ_ASSERT(false, "Unknown GeometryNodeType");
58 static nsIFrame
* GetFrameForGeometryNode(
59 const Optional
<OwningGeometryNode
>& aGeometryNode
, nsINode
* aDefaultNode
) {
60 if (!aGeometryNode
.WasPassed()) {
61 return GetFrameForNode(aDefaultNode
->OwnerDoc(), GEOMETRY_NODE_DOCUMENT
);
64 const OwningGeometryNode
& value
= aGeometryNode
.Value();
65 if (value
.IsElement()) {
66 return GetFrameForNode(value
.GetAsElement(), GEOMETRY_NODE_ELEMENT
);
68 if (value
.IsDocument()) {
69 return GetFrameForNode(value
.GetAsDocument(), GEOMETRY_NODE_DOCUMENT
);
71 return GetFrameForNode(value
.GetAsText(), GEOMETRY_NODE_TEXT
);
74 static nsIFrame
* GetFrameForGeometryNode(const GeometryNode
& aGeometryNode
) {
75 if (aGeometryNode
.IsElement()) {
76 return GetFrameForNode(&aGeometryNode
.GetAsElement(),
77 GEOMETRY_NODE_ELEMENT
);
79 if (aGeometryNode
.IsDocument()) {
80 return GetFrameForNode(&aGeometryNode
.GetAsDocument(),
81 GEOMETRY_NODE_DOCUMENT
);
83 return GetFrameForNode(&aGeometryNode
.GetAsText(), GEOMETRY_NODE_TEXT
);
86 static nsIFrame
* GetFrameForNode(nsINode
* aNode
) {
87 if (aNode
->IsElement()) {
88 return GetFrameForNode(aNode
, GEOMETRY_NODE_ELEMENT
);
90 if (aNode
== aNode
->OwnerDoc()) {
91 return GetFrameForNode(aNode
, GEOMETRY_NODE_DOCUMENT
);
93 NS_ASSERTION(aNode
->IsText(), "Unknown node type");
94 return GetFrameForNode(aNode
, GEOMETRY_NODE_TEXT
);
97 static nsIFrame
* GetFirstNonAnonymousFrameForGeometryNode(
98 const Optional
<OwningGeometryNode
>& aNode
, nsINode
* aDefaultNode
) {
99 nsIFrame
* f
= GetFrameForGeometryNode(aNode
, aDefaultNode
);
101 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
106 static nsIFrame
* GetFirstNonAnonymousFrameForGeometryNode(
107 const GeometryNode
& aNode
) {
108 nsIFrame
* f
= GetFrameForGeometryNode(aNode
);
110 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
115 static nsIFrame
* GetFirstNonAnonymousFrameForNode(nsINode
* aNode
) {
116 nsIFrame
* f
= GetFrameForNode(aNode
);
118 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
124 * This can modify aFrame to point to a different frame. This is needed to
125 * handle SVG, where SVG elements can only compute a rect that's valid with
126 * respect to the "outer SVG" frame.
128 static nsRect
GetBoxRectForFrame(nsIFrame
** aFrame
, CSSBoxType aType
) {
130 nsIFrame
* f
= nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame
, &r
);
131 if (f
&& f
!= *aFrame
) {
132 // For non-outer SVG frames, the BoxType is ignored.
139 case CSSBoxType::Content
:
140 r
= f
->GetContentRectRelativeToSelf();
142 case CSSBoxType::Padding
:
143 r
= f
->GetPaddingRectRelativeToSelf();
145 case CSSBoxType::Border
:
146 r
= nsRect(nsPoint(0, 0), f
->GetSize());
148 case CSSBoxType::Margin
:
149 r
= f
->GetMarginRectRelativeToSelf();
152 MOZ_ASSERT(false, "unknown box type");
159 class AccumulateQuadCallback
: public nsLayoutUtils::BoxCallback
{
161 AccumulateQuadCallback(Document
* aParentObject
,
162 nsTArray
<RefPtr
<DOMQuad
> >& aResult
,
163 nsIFrame
* aRelativeToFrame
,
164 const nsPoint
& aRelativeToBoxTopLeft
,
166 : mParentObject(ToSupports(aParentObject
)),
168 mRelativeToFrame(aRelativeToFrame
),
169 mRelativeToBoxTopLeft(aRelativeToBoxTopLeft
),
171 if (mBoxType
== CSSBoxType::Margin
) {
172 // Don't include the caption margin when computing margins for a
174 mIncludeCaptionBoxForTable
= false;
178 virtual void AddBox(nsIFrame
* aFrame
) override
{
179 nsIFrame
* f
= aFrame
;
180 if (mBoxType
== CSSBoxType::Margin
&& f
->IsTableFrame()) {
181 // Margin boxes for table frames should be taken from the table wrapper
182 // frame, since that has the margin.
185 nsRect box
= GetBoxRectForFrame(&f
, mBoxType
);
186 nsPoint appUnits
[4] = {box
.TopLeft(), box
.TopRight(), box
.BottomRight(),
189 for (uint32_t i
= 0; i
< 4; ++i
) {
191 CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits
[i
].x
),
192 nsPresContext::AppUnitsToFloatCSSPixels(appUnits
[i
].y
));
194 nsLayoutUtils::TransformResult rv
=
195 nsLayoutUtils::TransformPoints(f
, mRelativeToFrame
, 4, points
);
196 if (rv
== nsLayoutUtils::TRANSFORM_SUCCEEDED
) {
198 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft
.x
),
199 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft
.y
));
200 for (uint32_t i
= 0; i
< 4; ++i
) {
204 PodArrayZero(points
);
206 mResult
.AppendElement(new DOMQuad(mParentObject
, points
));
209 nsISupports
* mParentObject
;
210 nsTArray
<RefPtr
<DOMQuad
> >& mResult
;
211 nsIFrame
* mRelativeToFrame
;
212 nsPoint mRelativeToBoxTopLeft
;
216 static nsPresContext
* FindTopLevelPresContext(nsPresContext
* aPC
) {
217 bool isChrome
= aPC
->IsChrome();
218 nsPresContext
* pc
= aPC
;
220 nsPresContext
* parent
= pc
->GetParentPresContext();
221 if (!parent
|| parent
->IsChrome() != isChrome
) {
228 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame
* aFrame1
,
230 CallerType aCallerType
) {
231 nsPresContext
* pc1
= aFrame1
->PresContext();
232 nsPresContext
* pc2
= aFrame2
->PresContext();
236 if (aCallerType
== CallerType::System
) {
239 if (FindTopLevelPresContext(pc1
) == FindTopLevelPresContext(pc2
)) {
245 void GetBoxQuads(nsINode
* aNode
, const dom::BoxQuadOptions
& aOptions
,
246 nsTArray
<RefPtr
<DOMQuad
> >& aResult
, CallerType aCallerType
,
248 nsIFrame
* frame
= GetFrameForNode(aNode
);
250 // No boxes to return
253 AutoWeakFrame
weakFrame(frame
);
254 Document
* ownerDoc
= aNode
->OwnerDoc();
255 nsIFrame
* relativeToFrame
=
256 GetFirstNonAnonymousFrameForGeometryNode(aOptions
.mRelativeTo
, ownerDoc
);
257 // The first frame might be destroyed now if the above call lead to an
258 // EnsureFrameForTextNode call. We need to get the first frame again
259 // when that happens and re-check it.
260 if (!weakFrame
.IsAlive()) {
261 frame
= GetFrameForNode(aNode
);
263 // No boxes to return
267 if (!relativeToFrame
) {
268 aRv
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
271 if (!CheckFramesInSameTopLevelBrowsingContext(frame
, relativeToFrame
,
273 aRv
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
276 // GetBoxRectForFrame can modify relativeToFrame so call it first.
277 nsPoint relativeToTopLeft
=
278 GetBoxRectForFrame(&relativeToFrame
, CSSBoxType::Border
).TopLeft();
279 AccumulateQuadCallback
callback(ownerDoc
, aResult
, relativeToFrame
,
280 relativeToTopLeft
, aOptions
.mBox
);
281 nsLayoutUtils::GetAllInFlowBoxes(frame
, &callback
);
284 static void TransformPoints(nsINode
* aTo
, const GeometryNode
& aFrom
,
285 uint32_t aPointCount
, CSSPoint
* aPoints
,
286 const ConvertCoordinateOptions
& aOptions
,
287 CallerType aCallerType
, ErrorResult
& aRv
) {
288 nsIFrame
* fromFrame
= GetFirstNonAnonymousFrameForGeometryNode(aFrom
);
289 AutoWeakFrame
weakFrame(fromFrame
);
290 nsIFrame
* toFrame
= GetFirstNonAnonymousFrameForNode(aTo
);
291 // The first frame might be destroyed now if the above call lead to an
292 // EnsureFrameForTextNode call. We need to get the first frame again
293 // when that happens.
294 if (fromFrame
&& !weakFrame
.IsAlive()) {
295 fromFrame
= GetFirstNonAnonymousFrameForGeometryNode(aFrom
);
297 if (!fromFrame
|| !toFrame
) {
298 aRv
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
301 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame
, toFrame
,
303 aRv
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
308 GetBoxRectForFrame(&fromFrame
, aOptions
.mFromBox
).TopLeft();
309 nsPoint toOffset
= GetBoxRectForFrame(&toFrame
, aOptions
.mToBox
).TopLeft();
310 CSSPoint
fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset
.x
),
311 nsPresContext::AppUnitsToFloatCSSPixels(fromOffset
.y
));
312 for (uint32_t i
= 0; i
< aPointCount
; ++i
) {
313 aPoints
[i
] += fromOffsetGfx
;
315 nsLayoutUtils::TransformResult rv
=
316 nsLayoutUtils::TransformPoints(fromFrame
, toFrame
, aPointCount
, aPoints
);
317 if (rv
== nsLayoutUtils::TRANSFORM_SUCCEEDED
) {
318 CSSPoint
toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset
.x
),
319 nsPresContext::AppUnitsToFloatCSSPixels(toOffset
.y
));
320 for (uint32_t i
= 0; i
< aPointCount
; ++i
) {
321 aPoints
[i
] -= toOffsetGfx
;
324 PodZero(aPoints
, aPointCount
);
328 already_AddRefed
<DOMQuad
> ConvertQuadFromNode(
329 nsINode
* aTo
, dom::DOMQuad
& aQuad
, const GeometryNode
& aFrom
,
330 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
333 for (uint32_t i
= 0; i
< 4; ++i
) {
334 DOMPoint
* p
= aQuad
.Point(i
);
335 if (p
->W() != 1.0 || p
->Z() != 0.0) {
336 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
339 points
[i
] = CSSPoint(p
->X(), p
->Y());
341 TransformPoints(aTo
, aFrom
, 4, points
, aOptions
, aCallerType
, aRv
);
345 RefPtr
<DOMQuad
> result
= new DOMQuad(aTo
->GetParentObject().mObject
, points
);
346 return result
.forget();
349 already_AddRefed
<DOMQuad
> ConvertRectFromNode(
350 nsINode
* aTo
, dom::DOMRectReadOnly
& aRect
, const GeometryNode
& aFrom
,
351 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
354 double x
= aRect
.X(), y
= aRect
.Y(), w
= aRect
.Width(), h
= aRect
.Height();
355 points
[0] = CSSPoint(x
, y
);
356 points
[1] = CSSPoint(x
+ w
, y
);
357 points
[2] = CSSPoint(x
+ w
, y
+ h
);
358 points
[3] = CSSPoint(x
, y
+ h
);
359 TransformPoints(aTo
, aFrom
, 4, points
, aOptions
, aCallerType
, aRv
);
363 RefPtr
<DOMQuad
> result
= new DOMQuad(aTo
->GetParentObject().mObject
, points
);
364 return result
.forget();
367 already_AddRefed
<DOMPoint
> ConvertPointFromNode(
368 nsINode
* aTo
, const dom::DOMPointInit
& aPoint
, const GeometryNode
& aFrom
,
369 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
371 if (aPoint
.mW
!= 1.0 || aPoint
.mZ
!= 0.0) {
372 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
375 CSSPoint
point(aPoint
.mX
, aPoint
.mY
);
376 TransformPoints(aTo
, aFrom
, 1, &point
, aOptions
, aCallerType
, aRv
);
380 RefPtr
<DOMPoint
> result
=
381 new DOMPoint(aTo
->GetParentObject().mObject
, point
.x
, point
.y
);
382 return result
.forget();
385 } // namespace mozilla