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/SVGUtils.h"
11 #include "mozilla/dom/CharacterData.h"
12 #include "mozilla/dom/DOMPointBinding.h"
13 #include "mozilla/dom/GeometryUtilsBinding.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/Text.h"
16 #include "mozilla/dom/DocumentInlines.h"
17 #include "mozilla/dom/DOMPoint.h"
18 #include "mozilla/dom/DOMQuad.h"
19 #include "mozilla/dom/DOMRect.h"
20 #include "mozilla/dom/BrowserChild.h"
22 #include "nsContainerFrame.h"
23 #include "nsContentUtils.h"
24 #include "nsCSSFrameConstructor.h"
25 #include "nsLayoutUtils.h"
27 using namespace mozilla
;
28 using namespace mozilla::dom
;
32 enum GeometryNodeType
{
33 GEOMETRY_NODE_ELEMENT
,
35 GEOMETRY_NODE_DOCUMENT
38 static nsIFrame
* GetFrameForNode(nsINode
* aNode
, GeometryNodeType aType
,
39 bool aCreateFramesForSuppressedWhitespace
) {
40 Document
* doc
= aNode
->OwnerDoc();
41 if (aType
== GEOMETRY_NODE_TEXT
&& aCreateFramesForSuppressedWhitespace
) {
42 if (PresShell
* presShell
= doc
->GetPresShell()) {
43 presShell
->FrameConstructor()->EnsureFrameForTextNodeIsCreatedAfterFlush(
44 static_cast<CharacterData
*>(aNode
));
47 doc
->FlushPendingNotifications(FlushType::Layout
);
50 case GEOMETRY_NODE_TEXT
:
51 case GEOMETRY_NODE_ELEMENT
:
52 return aNode
->AsContent()->GetPrimaryFrame();
53 case GEOMETRY_NODE_DOCUMENT
: {
54 PresShell
* presShell
= doc
->GetPresShell();
55 return presShell
? presShell
->GetRootFrame() : nullptr;
58 MOZ_ASSERT(false, "Unknown GeometryNodeType");
63 static nsIFrame
* GetFrameForGeometryNode(
64 const Optional
<OwningGeometryNode
>& aGeometryNode
, nsINode
* aDefaultNode
,
65 bool aCreateFramesForSuppressedWhitespace
) {
66 if (!aGeometryNode
.WasPassed()) {
67 return GetFrameForNode(aDefaultNode
->OwnerDoc(), GEOMETRY_NODE_DOCUMENT
,
68 aCreateFramesForSuppressedWhitespace
);
71 const OwningGeometryNode
& value
= aGeometryNode
.Value();
72 if (value
.IsElement()) {
73 return GetFrameForNode(value
.GetAsElement(), GEOMETRY_NODE_ELEMENT
,
74 aCreateFramesForSuppressedWhitespace
);
76 if (value
.IsDocument()) {
77 return GetFrameForNode(value
.GetAsDocument(), GEOMETRY_NODE_DOCUMENT
,
78 aCreateFramesForSuppressedWhitespace
);
80 return GetFrameForNode(value
.GetAsText(), GEOMETRY_NODE_TEXT
,
81 aCreateFramesForSuppressedWhitespace
);
84 static nsIFrame
* GetFrameForGeometryNode(const GeometryNode
& aGeometryNode
) {
85 // This will create frames for suppressed whitespace nodes.
86 if (aGeometryNode
.IsElement()) {
87 return GetFrameForNode(&aGeometryNode
.GetAsElement(), GEOMETRY_NODE_ELEMENT
,
90 if (aGeometryNode
.IsDocument()) {
91 return GetFrameForNode(&aGeometryNode
.GetAsDocument(),
92 GEOMETRY_NODE_DOCUMENT
, true);
94 return GetFrameForNode(&aGeometryNode
.GetAsText(), GEOMETRY_NODE_TEXT
, true);
97 static nsIFrame
* GetFrameForNode(nsINode
* aNode
,
98 bool aCreateFramesForSuppressedWhitespace
) {
99 if (aNode
->IsElement()) {
100 return GetFrameForNode(aNode
, GEOMETRY_NODE_ELEMENT
,
101 aCreateFramesForSuppressedWhitespace
);
103 if (aNode
== aNode
->OwnerDoc()) {
104 return GetFrameForNode(aNode
, GEOMETRY_NODE_DOCUMENT
,
105 aCreateFramesForSuppressedWhitespace
);
107 NS_ASSERTION(aNode
->IsText(), "Unknown node type");
108 return GetFrameForNode(aNode
, GEOMETRY_NODE_TEXT
,
109 aCreateFramesForSuppressedWhitespace
);
112 static nsIFrame
* GetFirstNonAnonymousFrameForGeometryNode(
113 const Optional
<OwningGeometryNode
>& aNode
, nsINode
* aDefaultNode
,
114 bool aCreateFramesForSuppressedWhitespace
) {
115 nsIFrame
* f
= GetFrameForGeometryNode(aNode
, aDefaultNode
,
116 aCreateFramesForSuppressedWhitespace
);
118 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
123 static nsIFrame
* GetFirstNonAnonymousFrameForGeometryNode(
124 const GeometryNode
& aNode
) {
125 // This will create frames for suppressed whitespace nodes.
126 nsIFrame
* f
= GetFrameForGeometryNode(aNode
);
128 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
133 static nsIFrame
* GetFirstNonAnonymousFrameForNode(nsINode
* aNode
) {
134 // This will create frames for suppressed whitespace nodes.
135 nsIFrame
* f
= GetFrameForNode(aNode
, true);
137 f
= nsLayoutUtils::GetFirstNonAnonymousFrame(f
);
143 * This can modify aFrame to point to a different frame. This is needed to
144 * handle SVG, where SVG elements can only compute a rect that's valid with
145 * respect to the "outer SVG" frame.
147 static nsRect
GetBoxRectForFrame(nsIFrame
** aFrame
, CSSBoxType aType
) {
149 nsIFrame
* f
= SVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame
, &r
);
150 if (f
&& f
!= *aFrame
) {
151 // For non-outer SVG frames, the BoxType is ignored.
158 case CSSBoxType::Content
:
159 r
= f
->GetContentRectRelativeToSelf();
161 case CSSBoxType::Padding
:
162 r
= f
->GetPaddingRectRelativeToSelf();
164 case CSSBoxType::Border
:
165 r
= nsRect(nsPoint(0, 0), f
->GetSize());
167 case CSSBoxType::Margin
:
168 r
= f
->GetMarginRectRelativeToSelf();
171 MOZ_ASSERT(false, "unknown box type");
178 class AccumulateQuadCallback
: public nsLayoutUtils::BoxCallback
{
180 AccumulateQuadCallback(Document
* aParentObject
,
181 nsTArray
<RefPtr
<DOMQuad
> >& aResult
,
182 nsIFrame
* aRelativeToFrame
,
183 const nsPoint
& aRelativeToBoxTopLeft
,
185 : mParentObject(ToSupports(aParentObject
)),
187 mRelativeToFrame(aRelativeToFrame
),
188 mRelativeToBoxTopLeft(aRelativeToBoxTopLeft
),
190 if (mBoxType
== CSSBoxType::Margin
) {
191 // Don't include the caption margin when computing margins for a
193 mIncludeCaptionBoxForTable
= false;
197 void AddBox(nsIFrame
* aFrame
) override
{
198 nsIFrame
* f
= aFrame
;
199 if (mBoxType
== CSSBoxType::Margin
&& f
->IsTableFrame()) {
200 // Margin boxes for table frames should be taken from the table wrapper
201 // frame, since that has the margin.
204 nsRect box
= GetBoxRectForFrame(&f
, mBoxType
);
205 nsPoint appUnits
[4] = {box
.TopLeft(), box
.TopRight(), box
.BottomRight(),
208 for (uint32_t i
= 0; i
< 4; ++i
) {
210 CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits
[i
].x
),
211 nsPresContext::AppUnitsToFloatCSSPixels(appUnits
[i
].y
));
213 nsLayoutUtils::TransformResult rv
= nsLayoutUtils::TransformPoints(
214 RelativeTo
{f
}, RelativeTo
{mRelativeToFrame
}, 4, points
);
215 if (rv
== nsLayoutUtils::TRANSFORM_SUCCEEDED
) {
217 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft
.x
),
218 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft
.y
));
219 for (uint32_t i
= 0; i
< 4; ++i
) {
223 PodArrayZero(points
);
225 mResult
.AppendElement(new DOMQuad(mParentObject
, points
));
228 nsISupports
* mParentObject
;
229 nsTArray
<RefPtr
<DOMQuad
> >& mResult
;
230 nsIFrame
* mRelativeToFrame
;
231 nsPoint mRelativeToBoxTopLeft
;
235 static nsPresContext
* FindTopLevelPresContext(nsPresContext
* aPC
) {
236 bool isChrome
= aPC
->IsChrome();
237 nsPresContext
* pc
= aPC
;
239 nsPresContext
* parent
= pc
->GetParentPresContext();
240 if (!parent
|| parent
->IsChrome() != isChrome
) {
247 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame
* aFrame1
,
249 CallerType aCallerType
) {
250 nsPresContext
* pc1
= aFrame1
->PresContext();
251 nsPresContext
* pc2
= aFrame2
->PresContext();
255 if (aCallerType
== CallerType::System
) {
258 if (FindTopLevelPresContext(pc1
) == FindTopLevelPresContext(pc2
)) {
264 void GetBoxQuads(nsINode
* aNode
, const dom::BoxQuadOptions
& aOptions
,
265 nsTArray
<RefPtr
<DOMQuad
> >& aResult
, CallerType aCallerType
,
268 GetFrameForNode(aNode
, aOptions
.mCreateFramesForSuppressedWhitespace
);
270 // No boxes to return
273 AutoWeakFrame
weakFrame(frame
);
274 Document
* ownerDoc
= aNode
->OwnerDoc();
275 nsIFrame
* relativeToFrame
= GetFirstNonAnonymousFrameForGeometryNode(
276 aOptions
.mRelativeTo
, ownerDoc
,
277 aOptions
.mCreateFramesForSuppressedWhitespace
);
278 // The first frame might be destroyed now if the above call lead to an
279 // EnsureFrameForTextNode call. We need to get the first frame again
280 // when that happens and re-check it.
281 if (!weakFrame
.IsAlive()) {
283 GetFrameForNode(aNode
, aOptions
.mCreateFramesForSuppressedWhitespace
);
285 // No boxes to return
289 if (!relativeToFrame
) {
290 // XXXbz There's no spec for this.
291 return aRv
.ThrowNotFoundError("No box to get quads relative to");
293 if (!CheckFramesInSameTopLevelBrowsingContext(frame
, relativeToFrame
,
295 aRv
.ThrowNotFoundError(
296 "Can't get quads relative to a box in a different toplevel browsing "
300 // GetBoxRectForFrame can modify relativeToFrame so call it first.
301 nsPoint relativeToTopLeft
=
302 GetBoxRectForFrame(&relativeToFrame
, CSSBoxType::Border
).TopLeft();
303 AccumulateQuadCallback
callback(ownerDoc
, aResult
, relativeToFrame
,
304 relativeToTopLeft
, aOptions
.mBox
);
306 // Bug 1624653: Refactor this to get boxes in layer pixels, which we will
307 // then convert into CSS units.
308 nsLayoutUtils::GetAllInFlowBoxes(frame
, &callback
);
311 void GetBoxQuadsFromWindowOrigin(nsINode
* aNode
,
312 const dom::BoxQuadOptions
& aOptions
,
313 nsTArray
<RefPtr
<DOMQuad
> >& aResult
,
315 // We want the quads relative to the window. To do this, we ignore the
316 // provided aOptions.mRelativeTo and instead use the document node of
317 // the top-most in-process document. Later, we'll check if there is a
318 // browserChild associated with that document, and if so, transform the
319 // calculated quads with the browserChild's to-parent matrix, which
320 // will get us to top-level coordinates.
321 if (aOptions
.mRelativeTo
.WasPassed()) {
322 return aRv
.ThrowNotSupportedError(
323 "Can't request quads in window origin space relative to another "
327 // We're going to call GetBoxQuads with our parameters, but we supply
328 // a new BoxQuadOptions object that uses the top in-process document
329 // as the relativeTo target.
330 BoxQuadOptions
bqo(aOptions
);
332 RefPtr
<Document
> topInProcessDoc
=
333 nsContentUtils::GetInProcessSubtreeRootDocument(aNode
->OwnerDoc());
335 OwningGeometryNode ogn
;
336 ogn
.SetAsDocument() = topInProcessDoc
;
337 bqo
.mRelativeTo
.Construct(ogn
);
339 // Bug 1624653: Refactor this to get boxes in layer pixels, which we can
340 // transform directly with the GetChildToParentConversionMatrix below,
341 // and convert to CSS units as a final step.
342 GetBoxQuads(aNode
, bqo
, aResult
, CallerType::System
, aRv
);
347 // Now we have aResult filled with DOMQuads with values relative to the
348 // top in-process document. See if topInProcessDoc is associated with a
349 // BrowserChild, and if it is, get its transformation matrix and use that
350 // to transform the DOMQuads in place to make them relative to the window
352 nsIDocShell
* docShell
= topInProcessDoc
->GetDocShell();
354 return aRv
.ThrowInvalidStateError(
355 "Returning untranslated quads because top in process document has "
359 BrowserChild
* browserChild
= BrowserChild::GetFrom(docShell
);
364 nsPresContext
* presContext
= docShell
->GetPresContext();
368 int32_t appUnitsPerDevPixel
= presContext
->AppUnitsPerDevPixel();
370 LayoutDeviceToLayoutDeviceMatrix4x4 matrix
=
371 browserChild
->GetChildToParentConversionMatrix();
373 // For each DOMQuad in aResult, change the css units into layer pixels,
374 // then transform them by matrix, then change them back into css units
375 // and overwrite the original points.
376 LayoutDeviceToCSSScale
ld2cScale((float)appUnitsPerDevPixel
/
377 (float)AppUnitsPerCSSPixel());
378 CSSToLayoutDeviceScale c2ldScale
= ld2cScale
.Inverse();
380 for (auto& quad
: aResult
) {
381 for (uint32_t i
= 0; i
< 4; i
++) {
382 DOMPoint
* p
= quad
->Point(i
);
383 CSSPoint
cp(p
->X(), p
->Y());
385 LayoutDevicePoint windowLdp
= matrix
.TransformPoint(cp
* c2ldScale
);
387 CSSPoint windowCp
= windowLdp
* ld2cScale
;
394 static void TransformPoints(nsINode
* aTo
, const GeometryNode
& aFrom
,
395 uint32_t aPointCount
, CSSPoint
* aPoints
,
396 const ConvertCoordinateOptions
& aOptions
,
397 CallerType aCallerType
, ErrorResult
& aRv
) {
398 nsIFrame
* fromFrame
= GetFirstNonAnonymousFrameForGeometryNode(aFrom
);
399 AutoWeakFrame
weakFrame(fromFrame
);
400 nsIFrame
* toFrame
= GetFirstNonAnonymousFrameForNode(aTo
);
401 // The first frame might be destroyed now if the above call lead to an
402 // EnsureFrameForTextNode call. We need to get the first frame again
403 // when that happens.
404 if (fromFrame
&& !weakFrame
.IsAlive()) {
405 fromFrame
= GetFirstNonAnonymousFrameForGeometryNode(aFrom
);
407 if (!fromFrame
|| !toFrame
) {
408 aRv
.ThrowNotFoundError(
409 "Can't transform coordinates between nonexistent boxes");
412 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame
, toFrame
,
414 aRv
.ThrowNotFoundError(
415 "Can't transform coordinates between boxes in different toplevel "
416 "browsing contexts");
421 GetBoxRectForFrame(&fromFrame
, aOptions
.mFromBox
).TopLeft();
422 nsPoint toOffset
= GetBoxRectForFrame(&toFrame
, aOptions
.mToBox
).TopLeft();
423 CSSPoint
fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset
.x
),
424 nsPresContext::AppUnitsToFloatCSSPixels(fromOffset
.y
));
425 for (uint32_t i
= 0; i
< aPointCount
; ++i
) {
426 aPoints
[i
] += fromOffsetGfx
;
428 nsLayoutUtils::TransformResult rv
= nsLayoutUtils::TransformPoints(
429 RelativeTo
{fromFrame
}, RelativeTo
{toFrame
}, aPointCount
, aPoints
);
430 if (rv
== nsLayoutUtils::TRANSFORM_SUCCEEDED
) {
431 CSSPoint
toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset
.x
),
432 nsPresContext::AppUnitsToFloatCSSPixels(toOffset
.y
));
433 for (uint32_t i
= 0; i
< aPointCount
; ++i
) {
434 aPoints
[i
] -= toOffsetGfx
;
437 PodZero(aPoints
, aPointCount
);
441 already_AddRefed
<DOMQuad
> ConvertQuadFromNode(
442 nsINode
* aTo
, dom::DOMQuad
& aQuad
, const GeometryNode
& aFrom
,
443 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
446 for (uint32_t i
= 0; i
< 4; ++i
) {
447 DOMPoint
* p
= aQuad
.Point(i
);
448 if (p
->W() != 1.0 || p
->Z() != 0.0) {
449 aRv
.ThrowInvalidStateError("Point is not 2d");
452 points
[i
] = CSSPoint(p
->X(), p
->Y());
454 TransformPoints(aTo
, aFrom
, 4, points
, aOptions
, aCallerType
, aRv
);
458 RefPtr
<DOMQuad
> result
= new DOMQuad(aTo
->GetParentObject().mObject
, points
);
459 return result
.forget();
462 already_AddRefed
<DOMQuad
> ConvertRectFromNode(
463 nsINode
* aTo
, dom::DOMRectReadOnly
& aRect
, const GeometryNode
& aFrom
,
464 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
467 double x
= aRect
.X(), y
= aRect
.Y(), w
= aRect
.Width(), h
= aRect
.Height();
468 points
[0] = CSSPoint(x
, y
);
469 points
[1] = CSSPoint(x
+ w
, y
);
470 points
[2] = CSSPoint(x
+ w
, y
+ h
);
471 points
[3] = CSSPoint(x
, y
+ h
);
472 TransformPoints(aTo
, aFrom
, 4, points
, aOptions
, aCallerType
, aRv
);
476 RefPtr
<DOMQuad
> result
= new DOMQuad(aTo
->GetParentObject().mObject
, points
);
477 return result
.forget();
480 already_AddRefed
<DOMPoint
> ConvertPointFromNode(
481 nsINode
* aTo
, const dom::DOMPointInit
& aPoint
, const GeometryNode
& aFrom
,
482 const dom::ConvertCoordinateOptions
& aOptions
, CallerType aCallerType
,
484 if (aPoint
.mW
!= 1.0 || aPoint
.mZ
!= 0.0) {
485 aRv
.ThrowInvalidStateError("Point is not 2d");
488 CSSPoint
point(aPoint
.mX
, aPoint
.mY
);
489 TransformPoints(aTo
, aFrom
, 1, &point
, aOptions
, aCallerType
, aRv
);
493 RefPtr
<DOMPoint
> result
=
494 new DOMPoint(aTo
->GetParentObject().mObject
, point
.x
, point
.y
);
495 return result
.forget();
498 } // namespace mozilla