nsFrameTraversal.cpp (10197B)
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 "mozilla/Assertions.h" 10 #include "mozilla/dom/Element.h" 11 #include "mozilla/dom/PopoverData.h" 12 #include "nsCOMPtr.h" 13 #include "nsContainerFrame.h" 14 #include "nsFrameList.h" 15 #include "nsGkAtoms.h" 16 #include "nsPlaceholderFrame.h" 17 #include "nsPresContext.h" 18 19 using namespace mozilla; 20 using namespace mozilla::dom; 21 22 nsFrameIterator::nsFrameIterator(nsPresContext* aPresContext, nsIFrame* aStart, 23 Type aType, bool aVisual, 24 bool aLockInScrollView, bool aFollowOOFs, 25 bool aSkipPopupChecks, const Element* aLimiter) 26 : mPresContext(aPresContext), 27 mLockScroll(aLockInScrollView), 28 mFollowOOFs(aFollowOOFs), 29 mSkipPopupChecks(aSkipPopupChecks), 30 mVisual(aVisual), 31 mType(aType), 32 mStart(aFollowOOFs ? nsPlaceholderFrame::GetRealFrameFor(aStart) 33 : aStart), 34 mCurrent(aStart), 35 mLast(aStart), 36 mLimiter(aLimiter), 37 mOffEdge(0) {} 38 39 nsIFrame* nsFrameIterator::CurrentItem() { 40 if (mOffEdge) { 41 return nullptr; 42 } 43 44 return mCurrent; 45 } 46 47 bool nsFrameIterator::IsDone() { return mOffEdge != 0; } 48 49 void nsFrameIterator::First() { mCurrent = mStart; } 50 51 static bool IsRootFrame(nsIFrame* aFrame) { return aFrame->IsCanvasFrame(); } 52 53 void nsFrameIterator::Last() { 54 nsIFrame* result; 55 nsIFrame* parent = GetCurrent(); 56 // If the current frame is a popup, don't move farther up the tree. 57 // Otherwise, get the nearest root frame or popup. 58 if (mSkipPopupChecks || !parent->IsMenuPopupFrame()) { 59 while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent))) { 60 parent = result; 61 } 62 } 63 64 while ((result = GetLastChild(parent))) { 65 parent = result; 66 } 67 68 SetCurrent(parent); 69 if (!parent) { 70 SetOffEdge(1); 71 } 72 } 73 74 void nsFrameIterator::Next() { 75 // recursive-oid method to get next frame 76 nsIFrame* result = nullptr; 77 nsIFrame* parent = GetCurrent(); 78 if (!parent) { 79 parent = GetLast(); 80 } 81 82 if (mType == Type::Leaf) { 83 // Drill down to first leaf 84 while ((result = GetFirstChild(parent))) { 85 parent = result; 86 } 87 } else if (mType == Type::PreOrder) { 88 result = GetFirstChild(parent); 89 if (result) { 90 parent = result; 91 } 92 } 93 94 if (parent != GetCurrent()) { 95 result = parent; 96 } else { 97 while (parent) { 98 result = GetNextSibling(parent); 99 if (result) { 100 if (mType != Type::PreOrder) { 101 parent = result; 102 while ((result = GetFirstChild(parent))) { 103 parent = result; 104 } 105 result = parent; 106 } 107 break; 108 } 109 result = GetParentFrameNotPopup(parent); 110 if (!result || IsRootFrame(result) || 111 (mLockScroll && result->IsScrollContainerFrame())) { 112 result = nullptr; 113 break; 114 } 115 if (mType == Type::PostOrder) { 116 break; 117 } 118 parent = result; 119 } 120 } 121 122 SetCurrent(result); 123 if (!result) { 124 SetOffEdge(1); 125 SetLast(parent); 126 } 127 } 128 129 void nsFrameIterator::Prev() { 130 // recursive-oid method to get prev frame 131 nsIFrame* result = nullptr; 132 nsIFrame* parent = GetCurrent(); 133 if (!parent) { 134 parent = GetLast(); 135 } 136 137 if (mType == Type::Leaf) { 138 // Drill down to last leaf 139 while ((result = GetLastChild(parent))) { 140 parent = result; 141 } 142 } else if (mType == Type::PostOrder) { 143 result = GetLastChild(parent); 144 if (result) { 145 parent = result; 146 } 147 } 148 149 if (parent != GetCurrent()) { 150 result = parent; 151 } else { 152 while (parent) { 153 result = GetPrevSibling(parent); 154 if (result) { 155 if (mType != Type::PostOrder) { 156 parent = result; 157 while ((result = GetLastChild(parent))) { 158 parent = result; 159 } 160 result = parent; 161 } 162 break; 163 } 164 result = GetParentFrameNotPopup(parent); 165 if (!result || IsRootFrame(result) || 166 (mLockScroll && result->IsScrollContainerFrame())) { 167 result = nullptr; 168 break; 169 } 170 if (mType == Type::PreOrder) { 171 break; 172 } 173 parent = result; 174 } 175 } 176 177 SetCurrent(result); 178 if (!result) { 179 SetOffEdge(-1); 180 SetLast(parent); 181 } 182 } 183 184 nsIFrame* nsFrameIterator::GetParentFrame(nsIFrame* aFrame, 185 const Element* aAncestorLimiter) { 186 if (mFollowOOFs) { 187 aFrame = GetPlaceholderFrame(aFrame); 188 } 189 if (!aFrame) { 190 return nullptr; 191 } 192 if (aAncestorLimiter && aFrame->GetContent() == aAncestorLimiter) { 193 return nullptr; 194 } 195 return aFrame->GetParent(); 196 } 197 198 nsIFrame* nsFrameIterator::GetParentFrameNotPopup(nsIFrame* aFrame) { 199 if (mFollowOOFs) { 200 aFrame = GetPlaceholderFrame(aFrame); 201 } 202 if (!aFrame) { 203 return nullptr; 204 } 205 206 if (mLimiter && aFrame->GetContent() == mLimiter) { 207 return nullptr; 208 } 209 nsIFrame* const parent = aFrame->GetParent(); 210 return IsPopupFrame(parent) ? nullptr : parent; 211 } 212 213 nsIFrame* nsFrameIterator::GetFirstChild(nsIFrame* aFrame) { 214 nsIFrame* result = GetFirstChildInner(aFrame); 215 if (mLockScroll && result && result->IsScrollContainerFrame()) { 216 return nullptr; 217 } 218 if (result && mFollowOOFs) { 219 result = nsPlaceholderFrame::GetRealFrameFor(result); 220 221 if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) { 222 result = GetNextSibling(result); 223 } 224 } 225 226 return result; 227 } 228 229 nsIFrame* nsFrameIterator::GetLastChild(nsIFrame* aFrame) { 230 nsIFrame* result = GetLastChildInner(aFrame); 231 if (mLockScroll && result && result->IsScrollContainerFrame()) { 232 return nullptr; 233 } 234 if (result && mFollowOOFs) { 235 result = nsPlaceholderFrame::GetRealFrameFor(result); 236 237 if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) { 238 result = GetPrevSibling(result); 239 } 240 } 241 242 return result; 243 } 244 245 nsIFrame* nsFrameIterator::GetNextSibling(nsIFrame* aFrame) { 246 nsIFrame* result = nullptr; 247 if (mFollowOOFs) { 248 aFrame = GetPlaceholderFrame(aFrame); 249 } 250 if (aFrame) { 251 result = GetNextSiblingInner(aFrame); 252 if (result && mFollowOOFs) { 253 result = nsPlaceholderFrame::GetRealFrameFor(result); 254 if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) { 255 result = GetNextSibling(result); 256 } 257 } 258 } 259 260 return result; 261 } 262 263 nsIFrame* nsFrameIterator::GetPrevSibling(nsIFrame* aFrame) { 264 nsIFrame* result = nullptr; 265 if (mFollowOOFs) { 266 aFrame = GetPlaceholderFrame(aFrame); 267 } 268 if (aFrame) { 269 result = GetPrevSiblingInner(aFrame); 270 if (result && mFollowOOFs) { 271 result = nsPlaceholderFrame::GetRealFrameFor(result); 272 if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) { 273 result = GetPrevSibling(result); 274 } 275 } 276 } 277 278 return result; 279 } 280 281 nsIFrame* nsFrameIterator::GetFirstChildInner(nsIFrame* aFrame) { 282 return mVisual ? aFrame->PrincipalChildList().GetNextVisualFor(nullptr) 283 : aFrame->PrincipalChildList().FirstChild(); 284 } 285 286 nsIFrame* nsFrameIterator::GetLastChildInner(nsIFrame* aFrame) { 287 return mVisual ? aFrame->PrincipalChildList().GetPrevVisualFor(nullptr) 288 : aFrame->PrincipalChildList().LastChild(); 289 } 290 291 /** 292 * Check whether aDestFrame is still in aLimiter if aLimiter is not nullptr. 293 * aDestFrame should be next or previous frame of aOriginFrame. 294 */ 295 static bool DidCrossLimiterBoundary(nsIFrame* aOriginFrame, 296 nsIFrame* aDestFrame, 297 const Element* aLimiter) { 298 MOZ_ASSERT(aOriginFrame); 299 MOZ_ASSERT(aDestFrame); 300 MOZ_ASSERT(aOriginFrame->GetContent()); 301 MOZ_ASSERT_IF( 302 aLimiter, 303 aOriginFrame->GetContent()->IsInclusiveFlatTreeDescendantOf(aLimiter)); 304 if (!aLimiter || aOriginFrame->GetContent() == aDestFrame->GetContent() || 305 aOriginFrame->GetContent() != aLimiter) { 306 return false; 307 } 308 return !aDestFrame->GetContent() || 309 !aDestFrame->GetContent()->IsInclusiveFlatTreeDescendantOf(aLimiter); 310 } 311 312 nsIFrame* nsFrameIterator::GetNextSiblingInner(nsIFrame* aFrame) { 313 if (!mVisual) { 314 nsIFrame* const next = aFrame->GetNextSibling(); 315 if (!next || DidCrossLimiterBoundary(aFrame, next, mLimiter)) { 316 return nullptr; 317 } 318 return next; 319 } 320 nsIFrame* const parent = GetParentFrame(aFrame, nullptr); 321 if (!parent) { 322 return nullptr; 323 } 324 nsIFrame* const next = parent->PrincipalChildList().GetNextVisualFor(aFrame); 325 if (!next || DidCrossLimiterBoundary(aFrame, next, mLimiter)) { 326 return nullptr; 327 } 328 return next; 329 } 330 331 nsIFrame* nsFrameIterator::GetPrevSiblingInner(nsIFrame* aFrame) { 332 if (!mVisual) { 333 nsIFrame* const prev = aFrame->GetPrevSibling(); 334 if (!prev || DidCrossLimiterBoundary(aFrame, prev, mLimiter)) { 335 return nullptr; 336 } 337 return prev; 338 } 339 nsIFrame* const parent = GetParentFrame(aFrame, nullptr); 340 if (!parent) { 341 return nullptr; 342 } 343 nsIFrame* const prev = parent->PrincipalChildList().GetPrevVisualFor(aFrame); 344 if (!prev || DidCrossLimiterBoundary(aFrame, prev, mLimiter)) { 345 return nullptr; 346 } 347 return prev; 348 } 349 350 nsIFrame* nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame) { 351 if (MOZ_LIKELY(!aFrame || !aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) { 352 return aFrame; 353 } 354 nsIFrame* placeholder = aFrame->GetPlaceholderFrame(); 355 return placeholder ? placeholder : aFrame; 356 } 357 358 bool nsFrameIterator::IsPopupFrame(nsIFrame* aFrame) { 359 // If skipping popup checks, pretend this isn't one. 360 if (mSkipPopupChecks) { 361 return false; 362 } 363 return aFrame && aFrame->IsMenuPopupFrame(); 364 } 365 366 bool nsFrameIterator::IsInvokerOpenPopoverFrame(nsIFrame* aFrame) { 367 if (const nsIContent* currentContent = aFrame->GetContent()) { 368 if (const auto* popover = Element::FromNode(currentContent)) { 369 return popover && popover->IsPopoverOpen() && 370 popover->GetPopoverData()->GetInvoker(); 371 } 372 } 373 return false; 374 }