/src/mozilla-central/layout/base/nsFrameTraversal.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 "nsFrameTraversal.h" |
8 | | |
9 | | #include "nsCOMPtr.h" |
10 | | #include "nsGkAtoms.h" |
11 | | |
12 | | #include "nsFrameList.h" |
13 | | #include "nsPlaceholderFrame.h" |
14 | | #include "nsContainerFrame.h" |
15 | | |
16 | | using namespace mozilla; |
17 | | |
18 | | class nsFrameIterator : public nsIFrameEnumerator |
19 | | { |
20 | | public: |
21 | | typedef nsIFrame::ChildListID ChildListID; |
22 | | |
23 | | NS_DECL_ISUPPORTS |
24 | | |
25 | | virtual void First() override; |
26 | | virtual void Next() override; |
27 | | virtual nsIFrame* CurrentItem() override; |
28 | | virtual bool IsDone() override; |
29 | | |
30 | | virtual void Last() override; |
31 | | virtual void Prev() override; |
32 | | |
33 | | nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart, |
34 | | nsIteratorType aType, bool aLockScroll, bool aFollowOOFs, |
35 | | bool aSkipPopupChecks); |
36 | | |
37 | | protected: |
38 | 0 | virtual ~nsFrameIterator() {} |
39 | | |
40 | 0 | void setCurrent(nsIFrame *aFrame){mCurrent = aFrame;} |
41 | 0 | nsIFrame *getCurrent(){return mCurrent;} |
42 | 0 | nsIFrame *getStart(){return mStart;} |
43 | 0 | nsIFrame *getLast(){return mLast;} |
44 | 0 | void setLast(nsIFrame *aFrame){mLast = aFrame;} |
45 | 0 | int8_t getOffEdge(){return mOffEdge;} |
46 | 0 | void setOffEdge(int8_t aOffEdge){mOffEdge = aOffEdge;} |
47 | | |
48 | | /* |
49 | | Our own versions of the standard frame tree navigation |
50 | | methods, which, if the iterator is following out-of-flows, |
51 | | apply the following rules for placeholder frames: |
52 | | |
53 | | - If a frame HAS a placeholder frame, getting its parent |
54 | | gets the placeholder's parent. |
55 | | |
56 | | - If a frame's first child or next/prev sibling IS a |
57 | | placeholder frame, then we instead return the real frame. |
58 | | |
59 | | - If a frame HAS a placeholder frame, getting its next/prev |
60 | | sibling gets the placeholder frame's next/prev sibling. |
61 | | |
62 | | These are all applied recursively to support multiple levels of |
63 | | placeholders. |
64 | | */ |
65 | | |
66 | | nsIFrame* GetParentFrame(nsIFrame* aFrame); |
67 | | // like GetParentFrame but returns null once a popup frame is reached |
68 | | nsIFrame* GetParentFrameNotPopup(nsIFrame* aFrame); |
69 | | |
70 | | nsIFrame* GetFirstChild(nsIFrame* aFrame); |
71 | | nsIFrame* GetLastChild(nsIFrame* aFrame); |
72 | | |
73 | | nsIFrame* GetNextSibling(nsIFrame* aFrame); |
74 | | nsIFrame* GetPrevSibling(nsIFrame* aFrame); |
75 | | |
76 | | /* |
77 | | These methods are overridden by the bidi visual iterator to have the |
78 | | semantics of "get first child in visual order", "get last child in visual |
79 | | order", "get next sibling in visual order" and "get previous sibling in visual |
80 | | order". |
81 | | */ |
82 | | |
83 | | virtual nsIFrame* GetFirstChildInner(nsIFrame* aFrame); |
84 | | virtual nsIFrame* GetLastChildInner(nsIFrame* aFrame); |
85 | | |
86 | | virtual nsIFrame* GetNextSiblingInner(nsIFrame* aFrame); |
87 | | virtual nsIFrame* GetPrevSiblingInner(nsIFrame* aFrame); |
88 | | |
89 | | /** |
90 | | * Return the placeholder frame for aFrame if it has one, otherwise return |
91 | | * aFrame itself. |
92 | | */ |
93 | | nsIFrame* GetPlaceholderFrame(nsIFrame* aFrame); |
94 | | bool IsPopupFrame(nsIFrame* aFrame); |
95 | | |
96 | | nsPresContext* const mPresContext; |
97 | | const bool mLockScroll; |
98 | | const bool mFollowOOFs; |
99 | | const bool mSkipPopupChecks; |
100 | | const nsIteratorType mType; |
101 | | |
102 | | private: |
103 | | nsIFrame* const mStart; |
104 | | nsIFrame* mCurrent; |
105 | | nsIFrame* mLast; //the last one that was in current; |
106 | | int8_t mOffEdge; //0= no -1 to far prev, 1 to far next; |
107 | | }; |
108 | | |
109 | | |
110 | | |
111 | | // Bidi visual iterator |
112 | | class nsVisualIterator: public nsFrameIterator |
113 | | { |
114 | | public: |
115 | | nsVisualIterator(nsPresContext* aPresContext, nsIFrame *aStart, |
116 | | nsIteratorType aType, bool aLockScroll, |
117 | | bool aFollowOOFs, bool aSkipPopupChecks) : |
118 | | nsFrameIterator(aPresContext, aStart, aType, aLockScroll, |
119 | 0 | aFollowOOFs, aSkipPopupChecks) {} |
120 | | |
121 | | protected: |
122 | | nsIFrame* GetFirstChildInner(nsIFrame* aFrame) override; |
123 | | nsIFrame* GetLastChildInner(nsIFrame* aFrame) override; |
124 | | |
125 | | nsIFrame* GetNextSiblingInner(nsIFrame* aFrame) override; |
126 | | nsIFrame* GetPrevSiblingInner(nsIFrame* aFrame) override; |
127 | | }; |
128 | | |
129 | | /************IMPLEMENTATIONS**************/ |
130 | | |
131 | | nsresult |
132 | | NS_CreateFrameTraversal(nsIFrameTraversal** aResult) |
133 | 0 | { |
134 | 0 | NS_ENSURE_ARG_POINTER(aResult); |
135 | 0 |
|
136 | 0 | nsCOMPtr<nsIFrameTraversal> t = new nsFrameTraversal(); |
137 | 0 | t.forget(aResult); |
138 | 0 |
|
139 | 0 | return NS_OK; |
140 | 0 | } |
141 | | |
142 | | nsresult |
143 | | NS_NewFrameTraversal(nsIFrameEnumerator **aEnumerator, |
144 | | nsPresContext* aPresContext, |
145 | | nsIFrame *aStart, |
146 | | nsIteratorType aType, |
147 | | bool aVisual, |
148 | | bool aLockInScrollView, |
149 | | bool aFollowOOFs, |
150 | | bool aSkipPopupChecks) |
151 | 0 | { |
152 | 0 | if (!aEnumerator || !aStart) |
153 | 0 | return NS_ERROR_NULL_POINTER; |
154 | 0 | |
155 | 0 | if (aFollowOOFs) { |
156 | 0 | aStart = nsPlaceholderFrame::GetRealFrameFor(aStart); |
157 | 0 | } |
158 | 0 |
|
159 | 0 | nsCOMPtr<nsIFrameEnumerator> trav; |
160 | 0 | if (aVisual) { |
161 | 0 | trav = new nsVisualIterator(aPresContext, aStart, aType, |
162 | 0 | aLockInScrollView, aFollowOOFs, |
163 | 0 | aSkipPopupChecks); |
164 | 0 | } else { |
165 | 0 | trav = new nsFrameIterator(aPresContext, aStart, aType, |
166 | 0 | aLockInScrollView, aFollowOOFs, |
167 | 0 | aSkipPopupChecks); |
168 | 0 | } |
169 | 0 | trav.forget(aEnumerator); |
170 | 0 | return NS_OK; |
171 | 0 | } |
172 | | |
173 | | |
174 | | nsFrameTraversal::nsFrameTraversal() |
175 | 0 | { |
176 | 0 | } |
177 | | |
178 | | nsFrameTraversal::~nsFrameTraversal() |
179 | 0 | { |
180 | 0 | } |
181 | | |
182 | | NS_IMPL_ISUPPORTS(nsFrameTraversal,nsIFrameTraversal) |
183 | | |
184 | | NS_IMETHODIMP |
185 | | nsFrameTraversal::NewFrameTraversal(nsIFrameEnumerator **aEnumerator, |
186 | | nsPresContext* aPresContext, |
187 | | nsIFrame *aStart, |
188 | | int32_t aType, |
189 | | bool aVisual, |
190 | | bool aLockInScrollView, |
191 | | bool aFollowOOFs, |
192 | | bool aSkipPopupChecks) |
193 | 0 | { |
194 | 0 | return NS_NewFrameTraversal(aEnumerator, aPresContext, aStart, |
195 | 0 | static_cast<nsIteratorType>(aType), |
196 | 0 | aVisual, aLockInScrollView, aFollowOOFs, |
197 | 0 | aSkipPopupChecks); |
198 | 0 | } |
199 | | |
200 | | // nsFrameIterator implementation |
201 | | |
202 | | NS_IMPL_ISUPPORTS(nsFrameIterator, nsIFrameEnumerator) |
203 | | |
204 | | nsFrameIterator::nsFrameIterator(nsPresContext* aPresContext, nsIFrame *aStart, |
205 | | nsIteratorType aType, bool aLockInScrollView, |
206 | | bool aFollowOOFs, bool aSkipPopupChecks) |
207 | | : mPresContext(aPresContext), |
208 | | mLockScroll(aLockInScrollView), |
209 | | mFollowOOFs(aFollowOOFs), |
210 | | mSkipPopupChecks(aSkipPopupChecks), |
211 | | mType(aType), |
212 | | mStart(aStart), |
213 | | mCurrent(aStart), |
214 | | mLast(aStart), |
215 | | mOffEdge(0) |
216 | 0 | { |
217 | 0 | MOZ_ASSERT(!aFollowOOFs || !aStart->IsPlaceholderFrame(), |
218 | 0 | "Caller should have resolved placeholder frame"); |
219 | 0 | } |
220 | | |
221 | | |
222 | | |
223 | | nsIFrame* |
224 | | nsFrameIterator::CurrentItem() |
225 | 0 | { |
226 | 0 | if (mOffEdge) |
227 | 0 | return nullptr; |
228 | 0 | |
229 | 0 | return mCurrent; |
230 | 0 | } |
231 | | |
232 | | |
233 | | |
234 | | bool |
235 | | nsFrameIterator::IsDone() |
236 | 0 | { |
237 | 0 | return mOffEdge != 0; |
238 | 0 | } |
239 | | |
240 | | void |
241 | | nsFrameIterator::First() |
242 | 0 | { |
243 | 0 | mCurrent = mStart; |
244 | 0 | } |
245 | | |
246 | | static bool |
247 | | IsRootFrame(nsIFrame* aFrame) |
248 | 0 | { |
249 | 0 | return aFrame->IsCanvasFrame() || aFrame->IsRootFrame(); |
250 | 0 | } |
251 | | |
252 | | void |
253 | | nsFrameIterator::Last() |
254 | 0 | { |
255 | 0 | nsIFrame* result; |
256 | 0 | nsIFrame* parent = getCurrent(); |
257 | 0 | // If the current frame is a popup, don't move farther up the tree. |
258 | 0 | // Otherwise, get the nearest root frame or popup. |
259 | 0 | if (mSkipPopupChecks || !parent->IsMenuPopupFrame()) { |
260 | 0 | while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent))) |
261 | 0 | parent = result; |
262 | 0 | } |
263 | 0 |
|
264 | 0 | while ((result = GetLastChild(parent))) { |
265 | 0 | parent = result; |
266 | 0 | } |
267 | 0 |
|
268 | 0 | setCurrent(parent); |
269 | 0 | if (!parent) |
270 | 0 | setOffEdge(1); |
271 | 0 | } |
272 | | |
273 | | void |
274 | | nsFrameIterator::Next() |
275 | 0 | { |
276 | 0 | // recursive-oid method to get next frame |
277 | 0 | nsIFrame *result = nullptr; |
278 | 0 | nsIFrame *parent = getCurrent(); |
279 | 0 | if (!parent) |
280 | 0 | parent = getLast(); |
281 | 0 |
|
282 | 0 | if (mType == eLeaf) { |
283 | 0 | // Drill down to first leaf |
284 | 0 | while ((result = GetFirstChild(parent))) { |
285 | 0 | parent = result; |
286 | 0 | } |
287 | 0 | } else if (mType == ePreOrder) { |
288 | 0 | result = GetFirstChild(parent); |
289 | 0 | if (result) |
290 | 0 | parent = result; |
291 | 0 | } |
292 | 0 |
|
293 | 0 | if (parent != getCurrent()) { |
294 | 0 | result = parent; |
295 | 0 | } else { |
296 | 0 | while (parent) { |
297 | 0 | result = GetNextSibling(parent); |
298 | 0 | if (result) { |
299 | 0 | if (mType != ePreOrder) { |
300 | 0 | parent = result; |
301 | 0 | while ((result = GetFirstChild(parent))) { |
302 | 0 | parent = result; |
303 | 0 | } |
304 | 0 | result = parent; |
305 | 0 | } |
306 | 0 | break; |
307 | 0 | } |
308 | 0 | else { |
309 | 0 | result = GetParentFrameNotPopup(parent); |
310 | 0 | if (!result || IsRootFrame(result) || |
311 | 0 | (mLockScroll && result->IsScrollFrame())) { |
312 | 0 | result = nullptr; |
313 | 0 | break; |
314 | 0 | } |
315 | 0 | if (mType == ePostOrder) |
316 | 0 | break; |
317 | 0 | parent = result; |
318 | 0 | } |
319 | 0 | } |
320 | 0 | } |
321 | 0 |
|
322 | 0 | setCurrent(result); |
323 | 0 | if (!result) { |
324 | 0 | setOffEdge(1); |
325 | 0 | setLast(parent); |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | | void |
330 | | nsFrameIterator::Prev() |
331 | 0 | { |
332 | 0 | // recursive-oid method to get prev frame |
333 | 0 | nsIFrame *result = nullptr; |
334 | 0 | nsIFrame *parent = getCurrent(); |
335 | 0 | if (!parent) |
336 | 0 | parent = getLast(); |
337 | 0 |
|
338 | 0 | if (mType == eLeaf) { |
339 | 0 | // Drill down to last leaf |
340 | 0 | while ((result = GetLastChild(parent))) { |
341 | 0 | parent = result; |
342 | 0 | } |
343 | 0 | } else if (mType == ePostOrder) { |
344 | 0 | result = GetLastChild(parent); |
345 | 0 | if (result) |
346 | 0 | parent = result; |
347 | 0 | } |
348 | 0 |
|
349 | 0 | if (parent != getCurrent()) { |
350 | 0 | result = parent; |
351 | 0 | } else { |
352 | 0 | while (parent) { |
353 | 0 | result = GetPrevSibling(parent); |
354 | 0 | if (result) { |
355 | 0 | if (mType != ePostOrder) { |
356 | 0 | parent = result; |
357 | 0 | while ((result = GetLastChild(parent))) { |
358 | 0 | parent = result; |
359 | 0 | } |
360 | 0 | result = parent; |
361 | 0 | } |
362 | 0 | break; |
363 | 0 | } else { |
364 | 0 | result = GetParentFrameNotPopup(parent); |
365 | 0 | if (!result || IsRootFrame(result) || |
366 | 0 | (mLockScroll && result->IsScrollFrame())) { |
367 | 0 | result = nullptr; |
368 | 0 | break; |
369 | 0 | } |
370 | 0 | if (mType == ePreOrder) |
371 | 0 | break; |
372 | 0 | parent = result; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | 0 |
|
377 | 0 | setCurrent(result); |
378 | 0 | if (!result) { |
379 | 0 | setOffEdge(-1); |
380 | 0 | setLast(parent); |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | nsIFrame* |
385 | | nsFrameIterator::GetParentFrame(nsIFrame* aFrame) |
386 | 0 | { |
387 | 0 | if (mFollowOOFs) |
388 | 0 | aFrame = GetPlaceholderFrame(aFrame); |
389 | 0 | if (aFrame) |
390 | 0 | return aFrame->GetParent(); |
391 | 0 | |
392 | 0 | return nullptr; |
393 | 0 | } |
394 | | |
395 | | nsIFrame* |
396 | | nsFrameIterator::GetParentFrameNotPopup(nsIFrame* aFrame) |
397 | 0 | { |
398 | 0 | if (mFollowOOFs) |
399 | 0 | aFrame = GetPlaceholderFrame(aFrame); |
400 | 0 | if (aFrame) { |
401 | 0 | nsIFrame* parent = aFrame->GetParent(); |
402 | 0 | if (!IsPopupFrame(parent)) |
403 | 0 | return parent; |
404 | 0 | } |
405 | 0 | |
406 | 0 | return nullptr; |
407 | 0 | } |
408 | | |
409 | | nsIFrame* |
410 | | nsFrameIterator::GetFirstChild(nsIFrame* aFrame) |
411 | 0 | { |
412 | 0 | nsIFrame* result = GetFirstChildInner(aFrame); |
413 | 0 | if (mLockScroll && result && result->IsScrollFrame()) |
414 | 0 | return nullptr; |
415 | 0 | if (result && mFollowOOFs) { |
416 | 0 | result = nsPlaceholderFrame::GetRealFrameFor(result); |
417 | 0 |
|
418 | 0 | if (IsPopupFrame(result)) |
419 | 0 | result = GetNextSibling(result); |
420 | 0 | } |
421 | 0 | return result; |
422 | 0 | } |
423 | | |
424 | | nsIFrame* |
425 | | nsFrameIterator::GetLastChild(nsIFrame* aFrame) |
426 | 0 | { |
427 | 0 | nsIFrame* result = GetLastChildInner(aFrame); |
428 | 0 | if (mLockScroll && result && result->IsScrollFrame()) |
429 | 0 | return nullptr; |
430 | 0 | if (result && mFollowOOFs) { |
431 | 0 | result = nsPlaceholderFrame::GetRealFrameFor(result); |
432 | 0 |
|
433 | 0 | if (IsPopupFrame(result)) |
434 | 0 | result = GetPrevSibling(result); |
435 | 0 | } |
436 | 0 | return result; |
437 | 0 | } |
438 | | |
439 | | nsIFrame* |
440 | | nsFrameIterator::GetNextSibling(nsIFrame* aFrame) |
441 | 0 | { |
442 | 0 | nsIFrame* result = nullptr; |
443 | 0 | if (mFollowOOFs) |
444 | 0 | aFrame = GetPlaceholderFrame(aFrame); |
445 | 0 | if (aFrame) { |
446 | 0 | result = GetNextSiblingInner(aFrame); |
447 | 0 | if (result && mFollowOOFs) |
448 | 0 | result = nsPlaceholderFrame::GetRealFrameFor(result); |
449 | 0 | } |
450 | 0 |
|
451 | 0 | if (mFollowOOFs && IsPopupFrame(result)) |
452 | 0 | result = GetNextSibling(result); |
453 | 0 |
|
454 | 0 | return result; |
455 | 0 | } |
456 | | |
457 | | nsIFrame* |
458 | | nsFrameIterator::GetPrevSibling(nsIFrame* aFrame) |
459 | 0 | { |
460 | 0 | nsIFrame* result = nullptr; |
461 | 0 | if (mFollowOOFs) |
462 | 0 | aFrame = GetPlaceholderFrame(aFrame); |
463 | 0 | if (aFrame) { |
464 | 0 | result = GetPrevSiblingInner(aFrame); |
465 | 0 | if (result && mFollowOOFs) |
466 | 0 | result = nsPlaceholderFrame::GetRealFrameFor(result); |
467 | 0 | } |
468 | 0 |
|
469 | 0 | if (mFollowOOFs && IsPopupFrame(result)) |
470 | 0 | result = GetPrevSibling(result); |
471 | 0 |
|
472 | 0 | return result; |
473 | 0 | } |
474 | | |
475 | | nsIFrame* |
476 | | nsFrameIterator::GetFirstChildInner(nsIFrame* aFrame) |
477 | 0 | { |
478 | 0 | return aFrame->PrincipalChildList().FirstChild(); |
479 | 0 | } |
480 | | |
481 | | nsIFrame* |
482 | | nsFrameIterator::GetLastChildInner(nsIFrame* aFrame) |
483 | 0 | { |
484 | 0 | return aFrame->PrincipalChildList().LastChild(); |
485 | 0 | } |
486 | | |
487 | | nsIFrame* |
488 | | nsFrameIterator::GetNextSiblingInner(nsIFrame* aFrame) |
489 | 0 | { |
490 | 0 | return aFrame->GetNextSibling(); |
491 | 0 | } |
492 | | |
493 | | nsIFrame* |
494 | | nsFrameIterator::GetPrevSiblingInner(nsIFrame* aFrame) |
495 | 0 | { |
496 | 0 | return aFrame->GetPrevSibling(); |
497 | 0 | } |
498 | | |
499 | | |
500 | | nsIFrame* |
501 | | nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame) |
502 | 0 | { |
503 | 0 | if (MOZ_LIKELY(!aFrame || !aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) { |
504 | 0 | return aFrame; |
505 | 0 | } |
506 | 0 | nsIFrame* placeholder = aFrame->GetPlaceholderFrame(); |
507 | 0 | return placeholder ? placeholder : aFrame; |
508 | 0 | } |
509 | | |
510 | | bool |
511 | | nsFrameIterator::IsPopupFrame(nsIFrame* aFrame) |
512 | 0 | { |
513 | 0 | // If skipping popup checks, pretend this isn't one. |
514 | 0 | if (mSkipPopupChecks) { |
515 | 0 | return false; |
516 | 0 | } |
517 | 0 | |
518 | 0 | return (aFrame && |
519 | 0 | aFrame->StyleDisplay()->mDisplay == StyleDisplay::MozPopup); |
520 | 0 | } |
521 | | |
522 | | // nsVisualIterator implementation |
523 | | |
524 | | nsIFrame* |
525 | | nsVisualIterator::GetFirstChildInner(nsIFrame* aFrame) |
526 | 0 | { |
527 | 0 | return aFrame->PrincipalChildList().GetNextVisualFor(nullptr); |
528 | 0 | } |
529 | | |
530 | | nsIFrame* |
531 | | nsVisualIterator::GetLastChildInner(nsIFrame* aFrame) |
532 | 0 | { |
533 | 0 | return aFrame->PrincipalChildList().GetPrevVisualFor(nullptr); |
534 | 0 | } |
535 | | |
536 | | nsIFrame* |
537 | | nsVisualIterator::GetNextSiblingInner(nsIFrame* aFrame) |
538 | 0 | { |
539 | 0 | nsIFrame* parent = GetParentFrame(aFrame); |
540 | 0 | if (!parent) |
541 | 0 | return nullptr; |
542 | 0 | return parent->PrincipalChildList().GetNextVisualFor(aFrame); |
543 | 0 | } |
544 | | |
545 | | nsIFrame* |
546 | | nsVisualIterator::GetPrevSiblingInner(nsIFrame* aFrame) |
547 | 0 | { |
548 | 0 | nsIFrame* parent = GetParentFrame(aFrame); |
549 | 0 | if (!parent) |
550 | 0 | return nullptr; |
551 | 0 | return parent->PrincipalChildList().GetPrevVisualFor(aFrame); |
552 | 0 | } |