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/. */
6
7 #include "GeometryUtils.h"
8
9 #include "mozilla/dom/DOMPointBinding.h"
10 #include "mozilla/dom/GeometryUtilsBinding.h"
11 #include "mozilla/dom/Element.h"
12 #include "mozilla/dom/Text.h"
13 #include "mozilla/dom/DOMPoint.h"
14 #include "mozilla/dom/DOMQuad.h"
15 #include "mozilla/dom/DOMRect.h"
16 #include "nsContentUtils.h"
17 #include "nsIFrame.h"
18 #include "nsGenericDOMDataNode.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsLayoutUtils.h"
21 #include "nsSVGUtils.h"
22
23 using namespace mozilla;
24 using namespace mozilla::dom;
25
26 namespace mozilla {
27
28 enum GeometryNodeType {
29 GEOMETRY_NODE_ELEMENT,
30 GEOMETRY_NODE_TEXT,
31 GEOMETRY_NODE_DOCUMENT
32 };
33
GetFrameForNode(nsINode * aNode,GeometryNodeType aType)34 static nsIFrame* GetFrameForNode(nsINode* aNode, GeometryNodeType aType) {
35 nsIDocument* doc = aNode->OwnerDoc();
36 if (aType == GEOMETRY_NODE_TEXT) {
37 if (nsIPresShell* shell = doc->GetShell()) {
38 shell->FrameConstructor()->EnsureFrameForTextNodeIsCreatedAfterFlush(
39 static_cast<nsGenericDOMDataNode*>(aNode));
40 }
41 }
42 doc->FlushPendingNotifications(FlushType::Layout);
43
44 switch (aType) {
45 case GEOMETRY_NODE_TEXT:
46 case GEOMETRY_NODE_ELEMENT:
47 return aNode->AsContent()->GetPrimaryFrame();
48 case GEOMETRY_NODE_DOCUMENT: {
49 nsIPresShell* presShell = doc->GetShell();
50 return presShell ? presShell->GetRootFrame() : nullptr;
51 }
52 default:
53 MOZ_ASSERT(false, "Unknown GeometryNodeType");
54 return nullptr;
55 }
56 }
57
GetFrameForGeometryNode(const Optional<OwningGeometryNode> & aGeometryNode,nsINode * aDefaultNode)58 static nsIFrame* GetFrameForGeometryNode(
59 const Optional<OwningGeometryNode>& aGeometryNode, nsINode* aDefaultNode) {
60 if (!aGeometryNode.WasPassed()) {
61 return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT);
62 }
63
64 const OwningGeometryNode& value = aGeometryNode.Value();
65 if (value.IsElement()) {
66 return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT);
67 }
68 if (value.IsDocument()) {
69 return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT);
70 }
71 return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT);
72 }
73
GetFrameForGeometryNode(const GeometryNode & aGeometryNode)74 static nsIFrame* GetFrameForGeometryNode(const GeometryNode& aGeometryNode) {
75 if (aGeometryNode.IsElement()) {
76 return GetFrameForNode(&aGeometryNode.GetAsElement(),
77 GEOMETRY_NODE_ELEMENT);
78 }
79 if (aGeometryNode.IsDocument()) {
80 return GetFrameForNode(&aGeometryNode.GetAsDocument(),
81 GEOMETRY_NODE_DOCUMENT);
82 }
83 return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT);
84 }
85
GetFrameForNode(nsINode * aNode)86 static nsIFrame* GetFrameForNode(nsINode* aNode) {
87 if (aNode->IsElement()) {
88 return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT);
89 }
90 if (aNode == aNode->OwnerDoc()) {
91 return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT);
92 }
93 NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type");
94 return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT);
95 }
96
GetFirstNonAnonymousFrameForGeometryNode(const Optional<OwningGeometryNode> & aNode,nsINode * aDefaultNode)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);
102 }
103 return f;
104 }
105
GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode & aNode)106 static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
107 const GeometryNode& aNode) {
108 nsIFrame* f = GetFrameForGeometryNode(aNode);
109 if (f) {
110 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
111 }
112 return f;
113 }
114
GetFirstNonAnonymousFrameForNode(nsINode * aNode)115 static nsIFrame* GetFirstNonAnonymousFrameForNode(nsINode* aNode) {
116 nsIFrame* f = GetFrameForNode(aNode);
117 if (f) {
118 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
119 }
120 return f;
121 }
122
123 /**
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.
127 */
GetBoxRectForFrame(nsIFrame ** aFrame,CSSBoxType aType)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;
135 }
136
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 = nsRect(nsPoint(0, 0), f->GetSize());
150 r.Inflate(f->GetUsedMargin());
151 break;
152 }
153 default:
154 MOZ_ASSERT(false, "unknown box type");
155 return r;
156 }
157
158 return r;
159 }
160
161 class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
162 public:
AccumulateQuadCallback(nsISupports * aParentObject,nsTArray<RefPtr<DOMQuad>> & aResult,nsIFrame * aRelativeToFrame,const nsPoint & aRelativeToBoxTopLeft,CSSBoxType aBoxType)163 AccumulateQuadCallback(nsISupports* aParentObject,
164 nsTArray<RefPtr<DOMQuad> >& aResult,
165 nsIFrame* aRelativeToFrame,
166 const nsPoint& aRelativeToBoxTopLeft,
167 CSSBoxType aBoxType)
168 : mParentObject(aParentObject),
169 mResult(aResult),
170 mRelativeToFrame(aRelativeToFrame),
171 mRelativeToBoxTopLeft(aRelativeToBoxTopLeft),
172 mBoxType(aBoxType) {
173 if (mBoxType == CSSBoxType::Margin) {
174 // Don't include the caption margin when computing margins for a
175 // table
176 mIncludeCaptionBoxForTable = false;
177 }
178 }
179
AddBox(nsIFrame * aFrame)180 virtual void AddBox(nsIFrame* aFrame) override {
181 nsIFrame* f = aFrame;
182 if (mBoxType == CSSBoxType::Margin && f->IsTableFrame()) {
183 // Margin boxes for table frames should be taken from the table wrapper
184 // frame, since that has the margin.
185 f = f->GetParent();
186 }
187 nsRect box = GetBoxRectForFrame(&f, mBoxType);
188 nsPoint appUnits[4] = {box.TopLeft(), box.TopRight(), box.BottomRight(),
189 box.BottomLeft()};
190 CSSPoint points[4];
191 for (uint32_t i = 0; i < 4; ++i) {
192 points[i] =
193 CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x),
194 nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y));
195 }
196 nsLayoutUtils::TransformResult rv =
197 nsLayoutUtils::TransformPoints(f, mRelativeToFrame, 4, points);
198 if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
199 CSSPoint delta(
200 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
201 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
202 for (uint32_t i = 0; i < 4; ++i) {
203 points[i] -= delta;
204 }
205 } else {
206 PodArrayZero(points);
207 }
208 mResult.AppendElement(new DOMQuad(mParentObject, points));
209 }
210
211 nsISupports* mParentObject;
212 nsTArray<RefPtr<DOMQuad> >& mResult;
213 nsIFrame* mRelativeToFrame;
214 nsPoint mRelativeToBoxTopLeft;
215 CSSBoxType mBoxType;
216 };
217
FindTopLevelPresContext(nsPresContext * aPC)218 static nsPresContext* FindTopLevelPresContext(nsPresContext* aPC) {
219 bool isChrome = aPC->IsChrome();
220 nsPresContext* pc = aPC;
221 for (;;) {
222 nsPresContext* parent = pc->GetParentPresContext();
223 if (!parent || parent->IsChrome() != isChrome) {
224 return pc;
225 }
226 pc = parent;
227 }
228 }
229
CheckFramesInSameTopLevelBrowsingContext(nsIFrame * aFrame1,nsIFrame * aFrame2,CallerType aCallerType)230 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1,
231 nsIFrame* aFrame2,
232 CallerType aCallerType) {
233 nsPresContext* pc1 = aFrame1->PresContext();
234 nsPresContext* pc2 = aFrame2->PresContext();
235 if (pc1 == pc2) {
236 return true;
237 }
238 if (aCallerType == CallerType::System) {
239 return true;
240 }
241 if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
242 return true;
243 }
244 return false;
245 }
246
GetBoxQuads(nsINode * aNode,const dom::BoxQuadOptions & aOptions,nsTArray<RefPtr<DOMQuad>> & aResult,CallerType aCallerType,ErrorResult & aRv)247 void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
248 nsTArray<RefPtr<DOMQuad> >& aResult, CallerType aCallerType,
249 ErrorResult& aRv) {
250 nsIFrame* frame = GetFrameForNode(aNode);
251 if (!frame) {
252 // No boxes to return
253 return;
254 }
255 AutoWeakFrame weakFrame(frame);
256 nsIDocument* ownerDoc = aNode->OwnerDoc();
257 nsIFrame* relativeToFrame =
258 GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc);
259 // The first frame might be destroyed now if the above call lead to an
260 // EnsureFrameForTextNode call. We need to get the first frame again
261 // when that happens and re-check it.
262 if (!weakFrame.IsAlive()) {
263 frame = GetFrameForNode(aNode);
264 if (!frame) {
265 // No boxes to return
266 return;
267 }
268 }
269 if (!relativeToFrame) {
270 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
271 return;
272 }
273 if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame,
274 aCallerType)) {
275 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
276 return;
277 }
278 // GetBoxRectForFrame can modify relativeToFrame so call it first.
279 nsPoint relativeToTopLeft =
280 GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft();
281 AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame,
282 relativeToTopLeft, aOptions.mBox);
283 nsLayoutUtils::GetAllInFlowBoxes(frame, &callback);
284 }
285
TransformPoints(nsINode * aTo,const GeometryNode & aFrom,uint32_t aPointCount,CSSPoint * aPoints,const ConvertCoordinateOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)286 static void TransformPoints(nsINode* aTo, const GeometryNode& aFrom,
287 uint32_t aPointCount, CSSPoint* aPoints,
288 const ConvertCoordinateOptions& aOptions,
289 CallerType aCallerType, ErrorResult& aRv) {
290 nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
291 AutoWeakFrame weakFrame(fromFrame);
292 nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo);
293 // The first frame might be destroyed now if the above call lead to an
294 // EnsureFrameForTextNode call. We need to get the first frame again
295 // when that happens.
296 if (fromFrame && !weakFrame.IsAlive()) {
297 fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
298 }
299 if (!fromFrame || !toFrame) {
300 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
301 return;
302 }
303 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame,
304 aCallerType)) {
305 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
306 return;
307 }
308
309 nsPoint fromOffset =
310 GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft();
311 nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft();
312 CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x),
313 nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y));
314 for (uint32_t i = 0; i < aPointCount; ++i) {
315 aPoints[i] += fromOffsetGfx;
316 }
317 nsLayoutUtils::TransformResult rv =
318 nsLayoutUtils::TransformPoints(fromFrame, toFrame, aPointCount, aPoints);
319 if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
320 CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x),
321 nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y));
322 for (uint32_t i = 0; i < aPointCount; ++i) {
323 aPoints[i] -= toOffsetGfx;
324 }
325 } else {
326 PodZero(aPoints, aPointCount);
327 }
328 }
329
ConvertQuadFromNode(nsINode * aTo,dom::DOMQuad & aQuad,const GeometryNode & aFrom,const dom::ConvertCoordinateOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)330 already_AddRefed<DOMQuad> ConvertQuadFromNode(
331 nsINode* aTo, dom::DOMQuad& aQuad, const GeometryNode& aFrom,
332 const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
333 ErrorResult& aRv) {
334 CSSPoint points[4];
335 for (uint32_t i = 0; i < 4; ++i) {
336 DOMPoint* p = aQuad.Point(i);
337 if (p->W() != 1.0 || p->Z() != 0.0) {
338 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
339 return nullptr;
340 }
341 points[i] = CSSPoint(p->X(), p->Y());
342 }
343 TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
344 if (aRv.Failed()) {
345 return nullptr;
346 }
347 RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
348 return result.forget();
349 }
350
ConvertRectFromNode(nsINode * aTo,dom::DOMRectReadOnly & aRect,const GeometryNode & aFrom,const dom::ConvertCoordinateOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)351 already_AddRefed<DOMQuad> ConvertRectFromNode(
352 nsINode* aTo, dom::DOMRectReadOnly& aRect, const GeometryNode& aFrom,
353 const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
354 ErrorResult& aRv) {
355 CSSPoint points[4];
356 double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height();
357 points[0] = CSSPoint(x, y);
358 points[1] = CSSPoint(x + w, y);
359 points[2] = CSSPoint(x + w, y + h);
360 points[3] = CSSPoint(x, y + h);
361 TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
362 if (aRv.Failed()) {
363 return nullptr;
364 }
365 RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
366 return result.forget();
367 }
368
ConvertPointFromNode(nsINode * aTo,const dom::DOMPointInit & aPoint,const GeometryNode & aFrom,const dom::ConvertCoordinateOptions & aOptions,CallerType aCallerType,ErrorResult & aRv)369 already_AddRefed<DOMPoint> ConvertPointFromNode(
370 nsINode* aTo, const dom::DOMPointInit& aPoint, const GeometryNode& aFrom,
371 const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
372 ErrorResult& aRv) {
373 if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
374 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
375 return nullptr;
376 }
377 CSSPoint point(aPoint.mX, aPoint.mY);
378 TransformPoints(aTo, aFrom, 1, &point, aOptions, aCallerType, aRv);
379 if (aRv.Failed()) {
380 return nullptr;
381 }
382 RefPtr<DOMPoint> result =
383 new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
384 return result.forget();
385 }
386
387 } // namespace mozilla
388