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