Bug 1879449 [wpt PR 44489] - [wptrunner] Add `infrastructure/expected-fail/` test...
[gecko.git] / layout / base / GeometryUtils.cpp
blobdac899f7558b26d6848da8b98ed8a93555c8751a
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"
21 #include "nsIFrame.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;
30 namespace mozilla {
32 enum GeometryNodeType {
33 GEOMETRY_NODE_ELEMENT,
34 GEOMETRY_NODE_TEXT,
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);
49 switch (aType) {
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;
57 default:
58 MOZ_ASSERT(false, "Unknown GeometryNodeType");
59 return nullptr;
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,
88 true);
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);
117 if (f) {
118 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
120 return f;
123 static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
124 const GeometryNode& aNode) {
125 // This will create frames for suppressed whitespace nodes.
126 nsIFrame* f = GetFrameForGeometryNode(aNode);
127 if (f) {
128 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
130 return f;
133 static nsIFrame* GetFirstNonAnonymousFrameForNode(nsINode* aNode) {
134 // This will create frames for suppressed whitespace nodes.
135 nsIFrame* f = GetFrameForNode(aNode, true);
136 if (f) {
137 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
139 return 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) {
148 nsRect r;
149 nsIFrame* f = SVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
150 if (f && f != *aFrame) {
151 // For non-outer SVG frames, the BoxType is ignored.
152 *aFrame = f;
153 return r;
156 f = *aFrame;
157 switch (aType) {
158 case CSSBoxType::Content:
159 r = f->GetContentRectRelativeToSelf();
160 break;
161 case CSSBoxType::Padding:
162 r = f->GetPaddingRectRelativeToSelf();
163 break;
164 case CSSBoxType::Border:
165 r = nsRect(nsPoint(0, 0), f->GetSize());
166 break;
167 case CSSBoxType::Margin:
168 r = f->GetMarginRectRelativeToSelf();
169 break;
170 default:
171 MOZ_ASSERT(false, "unknown box type");
172 return r;
175 return r;
178 class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
179 public:
180 AccumulateQuadCallback(Document* aParentObject,
181 nsTArray<RefPtr<DOMQuad> >& aResult,
182 nsIFrame* aRelativeToFrame,
183 const nsPoint& aRelativeToBoxTopLeft,
184 CSSBoxType aBoxType)
185 : mParentObject(ToSupports(aParentObject)),
186 mResult(aResult),
187 mRelativeToFrame(aRelativeToFrame),
188 mRelativeToBoxTopLeft(aRelativeToBoxTopLeft),
189 mBoxType(aBoxType) {
190 if (mBoxType == CSSBoxType::Margin) {
191 // Don't include the caption margin when computing margins for a
192 // table
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.
202 f = f->GetParent();
204 nsRect box = GetBoxRectForFrame(&f, mBoxType);
205 nsPoint appUnits[4] = {box.TopLeft(), box.TopRight(), box.BottomRight(),
206 box.BottomLeft()};
207 CSSPoint points[4];
208 for (uint32_t i = 0; i < 4; ++i) {
209 points[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) {
216 CSSPoint delta(
217 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
218 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
219 for (uint32_t i = 0; i < 4; ++i) {
220 points[i] -= delta;
222 } else {
223 PodArrayZero(points);
225 mResult.AppendElement(new DOMQuad(mParentObject, points));
228 nsISupports* mParentObject;
229 nsTArray<RefPtr<DOMQuad> >& mResult;
230 nsIFrame* mRelativeToFrame;
231 nsPoint mRelativeToBoxTopLeft;
232 CSSBoxType mBoxType;
235 static nsPresContext* FindTopLevelPresContext(nsPresContext* aPC) {
236 bool isChrome = aPC->IsChrome();
237 nsPresContext* pc = aPC;
238 for (;;) {
239 nsPresContext* parent = pc->GetParentPresContext();
240 if (!parent || parent->IsChrome() != isChrome) {
241 return pc;
243 pc = parent;
247 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1,
248 nsIFrame* aFrame2,
249 CallerType aCallerType) {
250 nsPresContext* pc1 = aFrame1->PresContext();
251 nsPresContext* pc2 = aFrame2->PresContext();
252 if (pc1 == pc2) {
253 return true;
255 if (aCallerType == CallerType::System) {
256 return true;
258 if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
259 return true;
261 return false;
264 void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
265 nsTArray<RefPtr<DOMQuad> >& aResult, CallerType aCallerType,
266 ErrorResult& aRv) {
267 nsIFrame* frame =
268 GetFrameForNode(aNode, aOptions.mCreateFramesForSuppressedWhitespace);
269 if (!frame) {
270 // No boxes to return
271 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()) {
282 frame =
283 GetFrameForNode(aNode, aOptions.mCreateFramesForSuppressedWhitespace);
284 if (!frame) {
285 // No boxes to return
286 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,
294 aCallerType)) {
295 aRv.ThrowNotFoundError(
296 "Can't get quads relative to a box in a different toplevel browsing "
297 "context");
298 return;
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,
314 ErrorResult& aRv) {
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 "
324 "node.");
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);
343 if (aRv.Failed()) {
344 return;
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
351 // origin.
352 nsIDocShell* docShell = topInProcessDoc->GetDocShell();
353 if (!docShell) {
354 return aRv.ThrowInvalidStateError(
355 "Returning untranslated quads because top in process document has "
356 "no docshell.");
359 BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
360 if (!browserChild) {
361 return;
364 nsPresContext* presContext = docShell->GetPresContext();
365 if (!presContext) {
366 return;
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;
388 p->SetX(windowCp.x);
389 p->SetY(windowCp.y);
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");
410 return;
412 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame,
413 aCallerType)) {
414 aRv.ThrowNotFoundError(
415 "Can't transform coordinates between boxes in different toplevel "
416 "browsing contexts");
417 return;
420 nsPoint fromOffset =
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;
436 } else {
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,
444 ErrorResult& aRv) {
445 CSSPoint points[4];
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");
450 return nullptr;
452 points[i] = CSSPoint(p->X(), p->Y());
454 TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
455 if (aRv.Failed()) {
456 return nullptr;
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,
465 ErrorResult& aRv) {
466 CSSPoint points[4];
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);
473 if (aRv.Failed()) {
474 return nullptr;
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,
483 ErrorResult& aRv) {
484 if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
485 aRv.ThrowInvalidStateError("Point is not 2d");
486 return nullptr;
488 CSSPoint point(aPoint.mX, aPoint.mY);
489 TransformPoints(aTo, aFrom, 1, &point, aOptions, aCallerType, aRv);
490 if (aRv.Failed()) {
491 return nullptr;
493 RefPtr<DOMPoint> result =
494 new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
495 return result.forget();
498 } // namespace mozilla