|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "GeometryUtils.h" |
|
7 |
|
8 #include "mozilla/dom/DOMPointBinding.h" |
|
9 #include "mozilla/dom/GeometryUtilsBinding.h" |
|
10 #include "mozilla/dom/Element.h" |
|
11 #include "mozilla/dom/Text.h" |
|
12 #include "mozilla/dom/DOMPoint.h" |
|
13 #include "mozilla/dom/DOMQuad.h" |
|
14 #include "mozilla/dom/DOMRect.h" |
|
15 #include "nsIFrame.h" |
|
16 #include "nsGenericDOMDataNode.h" |
|
17 #include "nsCSSFrameConstructor.h" |
|
18 #include "nsLayoutUtils.h" |
|
19 #include "nsSVGUtils.h" |
|
20 |
|
21 using namespace mozilla; |
|
22 using namespace mozilla::dom; |
|
23 |
|
24 namespace mozilla { |
|
25 |
|
26 enum GeometryNodeType { |
|
27 GEOMETRY_NODE_ELEMENT, |
|
28 GEOMETRY_NODE_TEXT, |
|
29 GEOMETRY_NODE_DOCUMENT |
|
30 }; |
|
31 |
|
32 static nsIFrame* |
|
33 GetFrameForNode(nsINode* aNode, GeometryNodeType aType) |
|
34 { |
|
35 nsIDocument* doc = aNode->OwnerDoc(); |
|
36 doc->FlushPendingNotifications(Flush_Layout); |
|
37 switch (aType) { |
|
38 case GEOMETRY_NODE_ELEMENT: |
|
39 return aNode->AsContent()->GetPrimaryFrame(); |
|
40 case GEOMETRY_NODE_TEXT: { |
|
41 nsIPresShell* presShell = doc->GetShell(); |
|
42 if (presShell) { |
|
43 return presShell->FrameConstructor()->EnsureFrameForTextNode( |
|
44 static_cast<nsGenericDOMDataNode*>(aNode)); |
|
45 } |
|
46 return nullptr; |
|
47 } |
|
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 |
|
58 static nsIFrame* |
|
59 GetFrameForGeometryNode(const Optional<OwningGeometryNode>& aGeometryNode, |
|
60 nsINode* aDefaultNode) |
|
61 { |
|
62 if (!aGeometryNode.WasPassed()) { |
|
63 return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT); |
|
64 } |
|
65 |
|
66 const OwningGeometryNode& value = aGeometryNode.Value(); |
|
67 if (value.IsElement()) { |
|
68 return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT); |
|
69 } |
|
70 if (value.IsDocument()) { |
|
71 return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); |
|
72 } |
|
73 return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT); |
|
74 } |
|
75 |
|
76 static nsIFrame* |
|
77 GetFrameForGeometryNode(const GeometryNode& aGeometryNode) |
|
78 { |
|
79 if (aGeometryNode.IsElement()) { |
|
80 return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT); |
|
81 } |
|
82 if (aGeometryNode.IsDocument()) { |
|
83 return GetFrameForNode(&aGeometryNode.GetAsDocument(), GEOMETRY_NODE_DOCUMENT); |
|
84 } |
|
85 return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT); |
|
86 } |
|
87 |
|
88 static nsIFrame* |
|
89 GetFrameForNode(nsINode* aNode) |
|
90 { |
|
91 if (aNode->IsElement()) { |
|
92 return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT); |
|
93 } |
|
94 if (aNode == aNode->OwnerDoc()) { |
|
95 return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT); |
|
96 } |
|
97 NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type"); |
|
98 return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT); |
|
99 } |
|
100 |
|
101 static nsIFrame* |
|
102 GetFirstNonAnonymousFrameForGeometryNode(const Optional<OwningGeometryNode>& aNode, |
|
103 nsINode* aDefaultNode) |
|
104 { |
|
105 nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode); |
|
106 if (f) { |
|
107 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); |
|
108 } |
|
109 return f; |
|
110 } |
|
111 |
|
112 static nsIFrame* |
|
113 GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode& aNode) |
|
114 { |
|
115 nsIFrame* f = GetFrameForGeometryNode(aNode); |
|
116 if (f) { |
|
117 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); |
|
118 } |
|
119 return f; |
|
120 } |
|
121 |
|
122 static nsIFrame* |
|
123 GetFirstNonAnonymousFrameForNode(nsINode* aNode) |
|
124 { |
|
125 nsIFrame* f = GetFrameForNode(aNode); |
|
126 if (f) { |
|
127 f = nsLayoutUtils::GetFirstNonAnonymousFrame(f); |
|
128 } |
|
129 return f; |
|
130 } |
|
131 |
|
132 /** |
|
133 * This can modify aFrame to point to a different frame. This is needed to |
|
134 * handle SVG, where SVG elements can only compute a rect that's valid with |
|
135 * respect to the "outer SVG" frame. |
|
136 */ |
|
137 static nsRect |
|
138 GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType) |
|
139 { |
|
140 nsRect r; |
|
141 nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r); |
|
142 if (f) { |
|
143 // For SVG, the BoxType is ignored. |
|
144 *aFrame = f; |
|
145 return r; |
|
146 } |
|
147 |
|
148 f = *aFrame; |
|
149 switch (aType) { |
|
150 case CSSBoxType::Content: r = f->GetContentRectRelativeToSelf(); break; |
|
151 case CSSBoxType::Padding: r = f->GetPaddingRectRelativeToSelf(); break; |
|
152 case CSSBoxType::Border: r = nsRect(nsPoint(0, 0), f->GetSize()); break; |
|
153 case CSSBoxType::Margin: { |
|
154 r = nsRect(nsPoint(0, 0), f->GetSize()); |
|
155 r.Inflate(f->GetUsedMargin()); |
|
156 break; |
|
157 } |
|
158 default: MOZ_ASSERT(false, "unknown box type"); return r; |
|
159 } |
|
160 |
|
161 return r; |
|
162 } |
|
163 |
|
164 class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback { |
|
165 public: |
|
166 AccumulateQuadCallback(nsISupports* aParentObject, |
|
167 nsTArray<nsRefPtr<DOMQuad> >& aResult, |
|
168 nsIFrame* aRelativeToFrame, |
|
169 const nsPoint& aRelativeToBoxTopLeft, |
|
170 CSSBoxType aBoxType) |
|
171 : mParentObject(aParentObject) |
|
172 , mResult(aResult) |
|
173 , mRelativeToFrame(aRelativeToFrame) |
|
174 , mRelativeToBoxTopLeft(aRelativeToBoxTopLeft) |
|
175 , mBoxType(aBoxType) |
|
176 { |
|
177 } |
|
178 |
|
179 virtual void AddBox(nsIFrame* aFrame) MOZ_OVERRIDE |
|
180 { |
|
181 nsIFrame* f = aFrame; |
|
182 nsRect box = GetBoxRectForFrame(&f, mBoxType); |
|
183 nsPoint appUnits[4] = |
|
184 { box.TopLeft(), box.TopRight(), box.BottomRight(), box.BottomLeft() }; |
|
185 CSSPoint points[4]; |
|
186 for (uint32_t i = 0; i < 4; ++i) { |
|
187 points[i] = CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x), |
|
188 nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y)); |
|
189 } |
|
190 nsLayoutUtils::TransformResult rv = |
|
191 nsLayoutUtils::TransformPoints(f, mRelativeToFrame, 4, points); |
|
192 if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { |
|
193 CSSPoint delta(nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x), |
|
194 nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y)); |
|
195 for (uint32_t i = 0; i < 4; ++i) { |
|
196 points[i] -= delta; |
|
197 } |
|
198 } else { |
|
199 PodArrayZero(points); |
|
200 } |
|
201 mResult.AppendElement(new DOMQuad(mParentObject, points)); |
|
202 } |
|
203 |
|
204 nsISupports* mParentObject; |
|
205 nsTArray<nsRefPtr<DOMQuad> >& mResult; |
|
206 nsIFrame* mRelativeToFrame; |
|
207 nsPoint mRelativeToBoxTopLeft; |
|
208 CSSBoxType mBoxType; |
|
209 }; |
|
210 |
|
211 static nsPresContext* |
|
212 FindTopLevelPresContext(nsPresContext* aPC) |
|
213 { |
|
214 bool isChrome = aPC->IsChrome(); |
|
215 nsPresContext* pc = aPC; |
|
216 for (;;) { |
|
217 nsPresContext* parent = pc->GetParentPresContext(); |
|
218 if (!parent || parent->IsChrome() != isChrome) { |
|
219 return pc; |
|
220 } |
|
221 pc = parent; |
|
222 } |
|
223 } |
|
224 |
|
225 static bool |
|
226 CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1, nsIFrame* aFrame2) |
|
227 { |
|
228 nsPresContext* pc1 = aFrame1->PresContext(); |
|
229 nsPresContext* pc2 = aFrame2->PresContext(); |
|
230 if (pc1 == pc2) { |
|
231 return true; |
|
232 } |
|
233 if (nsContentUtils::IsCallerChrome()) { |
|
234 return true; |
|
235 } |
|
236 if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) { |
|
237 return true; |
|
238 } |
|
239 return false; |
|
240 } |
|
241 |
|
242 void GetBoxQuads(nsINode* aNode, |
|
243 const dom::BoxQuadOptions& aOptions, |
|
244 nsTArray<nsRefPtr<DOMQuad> >& aResult, |
|
245 ErrorResult& aRv) |
|
246 { |
|
247 nsIFrame* frame = GetFrameForNode(aNode); |
|
248 if (!frame) { |
|
249 // No boxes to return |
|
250 return; |
|
251 } |
|
252 nsIDocument* ownerDoc = aNode->OwnerDoc(); |
|
253 nsIFrame* relativeToFrame = |
|
254 GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc); |
|
255 if (!relativeToFrame) { |
|
256 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); |
|
257 return; |
|
258 } |
|
259 if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame)) { |
|
260 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); |
|
261 return; |
|
262 } |
|
263 // GetBoxRectForFrame can modify relativeToFrame so call it first. |
|
264 nsPoint relativeToTopLeft = |
|
265 GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft(); |
|
266 AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame, |
|
267 relativeToTopLeft, aOptions.mBox); |
|
268 nsLayoutUtils::GetAllInFlowBoxes(frame, &callback); |
|
269 } |
|
270 |
|
271 static void |
|
272 TransformPoints(nsINode* aTo, const GeometryNode& aFrom, |
|
273 uint32_t aPointCount, CSSPoint* aPoints, |
|
274 const ConvertCoordinateOptions& aOptions, ErrorResult& aRv) |
|
275 { |
|
276 nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom); |
|
277 nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo); |
|
278 if (!fromFrame || !toFrame) { |
|
279 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); |
|
280 return; |
|
281 } |
|
282 if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame)) { |
|
283 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); |
|
284 return; |
|
285 } |
|
286 |
|
287 nsPoint fromOffset = GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft(); |
|
288 nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft(); |
|
289 CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x), |
|
290 nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y)); |
|
291 for (uint32_t i = 0; i < aPointCount; ++i) { |
|
292 aPoints[i] += fromOffsetGfx; |
|
293 } |
|
294 nsLayoutUtils::TransformResult rv = |
|
295 nsLayoutUtils::TransformPoints(fromFrame, toFrame, aPointCount, aPoints); |
|
296 if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) { |
|
297 CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x), |
|
298 nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y)); |
|
299 for (uint32_t i = 0; i < aPointCount; ++i) { |
|
300 aPoints[i] -= toOffsetGfx; |
|
301 } |
|
302 } else { |
|
303 PodZero(aPoints, aPointCount); |
|
304 } |
|
305 } |
|
306 |
|
307 already_AddRefed<DOMQuad> |
|
308 ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad, |
|
309 const GeometryNode& aFrom, |
|
310 const dom::ConvertCoordinateOptions& aOptions, |
|
311 ErrorResult& aRv) |
|
312 { |
|
313 CSSPoint points[4]; |
|
314 for (uint32_t i = 0; i < 4; ++i) { |
|
315 DOMPoint* p = aQuad.Point(i); |
|
316 if (p->W() != 1.0 || p->Z() != 0.0) { |
|
317 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
318 return nullptr; |
|
319 } |
|
320 points[i] = CSSPoint(p->X(), p->Y()); |
|
321 } |
|
322 TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); |
|
323 if (aRv.Failed()) { |
|
324 return nullptr; |
|
325 } |
|
326 nsRefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points); |
|
327 return result.forget(); |
|
328 } |
|
329 |
|
330 already_AddRefed<DOMQuad> |
|
331 ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect, |
|
332 const GeometryNode& aFrom, |
|
333 const dom::ConvertCoordinateOptions& aOptions, |
|
334 ErrorResult& aRv) |
|
335 { |
|
336 CSSPoint points[4]; |
|
337 double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height(); |
|
338 points[0] = CSSPoint(x, y); |
|
339 points[1] = CSSPoint(x + w, y); |
|
340 points[2] = CSSPoint(x + w, y + h); |
|
341 points[3] = CSSPoint(x, y + h); |
|
342 TransformPoints(aTo, aFrom, 4, points, aOptions, aRv); |
|
343 if (aRv.Failed()) { |
|
344 return nullptr; |
|
345 } |
|
346 nsRefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points); |
|
347 return result.forget(); |
|
348 } |
|
349 |
|
350 already_AddRefed<DOMPoint> |
|
351 ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint, |
|
352 const GeometryNode& aFrom, |
|
353 const dom::ConvertCoordinateOptions& aOptions, |
|
354 ErrorResult& aRv) |
|
355 { |
|
356 if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) { |
|
357 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
358 return nullptr; |
|
359 } |
|
360 CSSPoint point(aPoint.mX, aPoint.mY); |
|
361 TransformPoints(aTo, aFrom, 1, &point, aOptions, aRv); |
|
362 if (aRv.Failed()) { |
|
363 return nullptr; |
|
364 } |
|
365 nsRefPtr<DOMPoint> result = new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y); |
|
366 return result.forget(); |
|
367 } |
|
368 |
|
369 } |