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