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