Bug 1468402 - Part 3: Add test for subgrids in the grid list. r=pbro
[gecko.git] / layout / base / GeometryUtils.cpp
blobea178fabc5eaea0aeaa5b07a72699e45b22029b4
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"
18 #include "nsIFrame.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsLayoutUtils.h"
21 #include "nsSVGUtils.h"
23 using namespace mozilla;
24 using namespace mozilla::dom;
26 namespace mozilla {
28 enum GeometryNodeType {
29 GEOMETRY_NODE_ELEMENT,
30 GEOMETRY_NODE_TEXT,
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);
44 switch (aType) {
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;
52 default:
53 MOZ_ASSERT(false, "Unknown GeometryNodeType");
54 return nullptr;
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);
100 if (f) {
101 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
103 return f;
106 static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
107 const GeometryNode& aNode) {
108 nsIFrame* f = GetFrameForGeometryNode(aNode);
109 if (f) {
110 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
112 return f;
115 static nsIFrame* GetFirstNonAnonymousFrameForNode(nsINode* aNode) {
116 nsIFrame* f = GetFrameForNode(aNode);
117 if (f) {
118 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
120 return 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) {
129 nsRect r;
130 nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
131 if (f && f != *aFrame) {
132 // For non-outer SVG frames, the BoxType is ignored.
133 *aFrame = f;
134 return r;
137 f = *aFrame;
138 switch (aType) {
139 case CSSBoxType::Content:
140 r = f->GetContentRectRelativeToSelf();
141 break;
142 case CSSBoxType::Padding:
143 r = f->GetPaddingRectRelativeToSelf();
144 break;
145 case CSSBoxType::Border:
146 r = nsRect(nsPoint(0, 0), f->GetSize());
147 break;
148 case CSSBoxType::Margin:
149 r = f->GetMarginRectRelativeToSelf();
150 break;
151 default:
152 MOZ_ASSERT(false, "unknown box type");
153 return r;
156 return r;
159 class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
160 public:
161 AccumulateQuadCallback(Document* aParentObject,
162 nsTArray<RefPtr<DOMQuad> >& aResult,
163 nsIFrame* aRelativeToFrame,
164 const nsPoint& aRelativeToBoxTopLeft,
165 CSSBoxType aBoxType)
166 : mParentObject(ToSupports(aParentObject)),
167 mResult(aResult),
168 mRelativeToFrame(aRelativeToFrame),
169 mRelativeToBoxTopLeft(aRelativeToBoxTopLeft),
170 mBoxType(aBoxType) {
171 if (mBoxType == CSSBoxType::Margin) {
172 // Don't include the caption margin when computing margins for a
173 // table
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.
183 f = f->GetParent();
185 nsRect box = GetBoxRectForFrame(&f, mBoxType);
186 nsPoint appUnits[4] = {box.TopLeft(), box.TopRight(), box.BottomRight(),
187 box.BottomLeft()};
188 CSSPoint points[4];
189 for (uint32_t i = 0; i < 4; ++i) {
190 points[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) {
197 CSSPoint delta(
198 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
199 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
200 for (uint32_t i = 0; i < 4; ++i) {
201 points[i] -= delta;
203 } else {
204 PodArrayZero(points);
206 mResult.AppendElement(new DOMQuad(mParentObject, points));
209 nsISupports* mParentObject;
210 nsTArray<RefPtr<DOMQuad> >& mResult;
211 nsIFrame* mRelativeToFrame;
212 nsPoint mRelativeToBoxTopLeft;
213 CSSBoxType mBoxType;
216 static nsPresContext* FindTopLevelPresContext(nsPresContext* aPC) {
217 bool isChrome = aPC->IsChrome();
218 nsPresContext* pc = aPC;
219 for (;;) {
220 nsPresContext* parent = pc->GetParentPresContext();
221 if (!parent || parent->IsChrome() != isChrome) {
222 return pc;
224 pc = parent;
228 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1,
229 nsIFrame* aFrame2,
230 CallerType aCallerType) {
231 nsPresContext* pc1 = aFrame1->PresContext();
232 nsPresContext* pc2 = aFrame2->PresContext();
233 if (pc1 == pc2) {
234 return true;
236 if (aCallerType == CallerType::System) {
237 return true;
239 if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
240 return true;
242 return false;
245 void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
246 nsTArray<RefPtr<DOMQuad> >& aResult, CallerType aCallerType,
247 ErrorResult& aRv) {
248 nsIFrame* frame = GetFrameForNode(aNode);
249 if (!frame) {
250 // No boxes to return
251 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);
262 if (!frame) {
263 // No boxes to return
264 return;
267 if (!relativeToFrame) {
268 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
269 return;
271 if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame,
272 aCallerType)) {
273 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
274 return;
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);
299 return;
301 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame,
302 aCallerType)) {
303 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
304 return;
307 nsPoint fromOffset =
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;
323 } else {
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,
331 ErrorResult& aRv) {
332 CSSPoint points[4];
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);
337 return nullptr;
339 points[i] = CSSPoint(p->X(), p->Y());
341 TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
342 if (aRv.Failed()) {
343 return nullptr;
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,
352 ErrorResult& aRv) {
353 CSSPoint points[4];
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);
360 if (aRv.Failed()) {
361 return nullptr;
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,
370 ErrorResult& aRv) {
371 if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
372 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
373 return nullptr;
375 CSSPoint point(aPoint.mX, aPoint.mY);
376 TransformPoints(aTo, aFrom, 1, &point, aOptions, aCallerType, aRv);
377 if (aRv.Failed()) {
378 return nullptr;
380 RefPtr<DOMPoint> result =
381 new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
382 return result.forget();
385 } // namespace mozilla