inDeepTreeWalker.cpp (9171B)
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 "inDeepTreeWalker.h" 8 9 #include "ChildIterator.h" 10 #include "inLayoutUtils.h" 11 #include "mozilla/Try.h" 12 #include "mozilla/dom/CSSStyleRule.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/Element.h" 15 #include "mozilla/dom/InspectorUtils.h" 16 #include "mozilla/dom/NodeFilterBinding.h" 17 #include "nsIContent.h" 18 #include "nsServiceManagerUtils.h" 19 #include "nsString.h" 20 21 using mozilla::dom::InspectorUtils; 22 23 /***************************************************************************** 24 * This implementation does not currently operaate according to the W3C spec. 25 * In particular it does NOT handle DOM mutations during the walk. It also 26 * ignores whatToShow and the filter. 27 *****************************************************************************/ 28 29 //////////////////////////////////////////////////// 30 31 inDeepTreeWalker::inDeepTreeWalker() = default; 32 inDeepTreeWalker::~inDeepTreeWalker() = default; 33 34 NS_IMPL_ISUPPORTS(inDeepTreeWalker, inIDeepTreeWalker) 35 36 //////////////////////////////////////////////////// 37 // inIDeepTreeWalker 38 39 NS_IMETHODIMP 40 inDeepTreeWalker::GetShowAnonymousContent(bool* aShowAnonymousContent) { 41 *aShowAnonymousContent = mShowAnonymousContent; 42 return NS_OK; 43 } 44 45 NS_IMETHODIMP 46 inDeepTreeWalker::SetShowAnonymousContent(bool aShowAnonymousContent) { 47 mShowAnonymousContent = aShowAnonymousContent; 48 return NS_OK; 49 } 50 51 NS_IMETHODIMP 52 inDeepTreeWalker::GetShowSubDocuments(bool* aShowSubDocuments) { 53 *aShowSubDocuments = mShowSubDocuments; 54 return NS_OK; 55 } 56 57 NS_IMETHODIMP 58 inDeepTreeWalker::SetShowSubDocuments(bool aShowSubDocuments) { 59 mShowSubDocuments = aShowSubDocuments; 60 return NS_OK; 61 } 62 63 NS_IMETHODIMP 64 inDeepTreeWalker::GetShowDocumentsAsNodes(bool* aShowDocumentsAsNodes) { 65 *aShowDocumentsAsNodes = mShowDocumentsAsNodes; 66 return NS_OK; 67 } 68 69 NS_IMETHODIMP 70 inDeepTreeWalker::SetShowDocumentsAsNodes(bool aShowDocumentsAsNodes) { 71 mShowDocumentsAsNodes = aShowDocumentsAsNodes; 72 return NS_OK; 73 } 74 75 NS_IMETHODIMP 76 inDeepTreeWalker::Init(nsINode* aRoot) { 77 if (!aRoot) { 78 return NS_ERROR_INVALID_ARG; 79 } 80 81 mRoot = aRoot; 82 mCurrentNode = aRoot; 83 84 return NS_OK; 85 } 86 87 //////////////////////////////////////////////////// 88 89 NS_IMETHODIMP 90 inDeepTreeWalker::GetRoot(nsINode** aRoot) { 91 *aRoot = mRoot; 92 NS_IF_ADDREF(*aRoot); 93 return NS_OK; 94 } 95 96 NS_IMETHODIMP 97 inDeepTreeWalker::GetCurrentNode(nsINode** aCurrentNode) { 98 *aCurrentNode = mCurrentNode; 99 NS_IF_ADDREF(*aCurrentNode); 100 return NS_OK; 101 } 102 103 already_AddRefed<nsINode> inDeepTreeWalker::GetParent() { 104 MOZ_ASSERT(mCurrentNode); 105 106 if (mCurrentNode == mRoot) { 107 return nullptr; 108 } 109 110 nsINode* parentNode = 111 InspectorUtils::GetParentForNode(*mCurrentNode, mShowAnonymousContent); 112 if (!parentNode) { 113 return nullptr; 114 } 115 116 // For compatibility reasons by default we skip the document nodes 117 // from the walk. 118 if (!mShowDocumentsAsNodes && parentNode->IsDocument() && 119 parentNode != mRoot) { 120 parentNode = 121 InspectorUtils::GetParentForNode(*parentNode, mShowAnonymousContent); 122 } 123 124 return do_AddRef(parentNode); 125 } 126 127 void inDeepTreeWalker::GetChildren(nsINode& aParent, ChildList& aChildList) { 128 aChildList.ClearAndRetainStorage(); 129 InspectorUtils::GetChildrenForNode(aParent, mShowAnonymousContent, 130 /* aIncludeAssignedNodes = */ false, 131 mShowSubDocuments, aChildList); 132 if (aChildList.Length() == 1 && aChildList.ElementAt(0)->IsDocument() && 133 !mShowDocumentsAsNodes) { 134 RefPtr parent = aChildList.ElementAt(0); 135 aChildList.ClearAndRetainStorage(); 136 InspectorUtils::GetChildrenForNode(*parent, mShowAnonymousContent, 137 /* aIncludeAssignedNodes = */ false, 138 mShowSubDocuments, aChildList); 139 } 140 } 141 142 NS_IMETHODIMP 143 inDeepTreeWalker::SetCurrentNode(nsINode* aCurrentNode) { 144 // mCurrentNode can only be null if init either failed, or has not been called 145 // yet. 146 if (!mCurrentNode || !aCurrentNode) { 147 return NS_ERROR_FAILURE; 148 } 149 150 // If Document nodes are skipped by the walk, we should not allow one to set 151 // one as the current node either. 152 if (!mShowDocumentsAsNodes) { 153 if (aCurrentNode->IsDocument()) { 154 return NS_ERROR_FAILURE; 155 } 156 } 157 158 // We want to store the original state so in case of error 159 // we can restore that. 160 ChildList oldSiblings; 161 mSiblings.SwapElements(oldSiblings); 162 nsCOMPtr<nsINode> oldCurrent = std::move(mCurrentNode); 163 164 mCurrentNode = aCurrentNode; 165 if (RefPtr<nsINode> parent = GetParent()) { 166 GetChildren(*parent, mSiblings); 167 // We cached all the siblings (if there are any) of the current node, but we 168 // still have to set the index too, to be able to iterate over them. 169 int32_t index = mSiblings.IndexOf(mCurrentNode); 170 if (index < 0) { 171 // If someone tries to set current node to some value that is not 172 // reachable otherwise, let's throw. (For example mShowAnonymousContent is 173 // false and some NAC was passed in). 174 // Restore state first. 175 mCurrentNode = std::move(oldCurrent); 176 oldSiblings.SwapElements(mSiblings); 177 return NS_ERROR_INVALID_ARG; 178 } 179 mCurrentIndex = index; 180 } else { 181 mCurrentIndex = -1; 182 } 183 return NS_OK; 184 } 185 186 NS_IMETHODIMP 187 inDeepTreeWalker::ParentNode(nsINode** _retval) { 188 *_retval = nullptr; 189 if (!mCurrentNode || mCurrentNode == mRoot) { 190 return NS_OK; 191 } 192 193 nsCOMPtr<nsINode> parent = GetParent(); 194 if (!parent) { 195 return NS_OK; 196 } 197 198 MOZ_TRY(SetCurrentNode(parent)); 199 200 parent.forget(_retval); 201 return NS_OK; 202 } 203 204 // FirstChild and LastChild are very similar methods, this is the generic 205 // version for internal use. With aReverse = true it returns the LastChild. 206 nsresult inDeepTreeWalker::EdgeChild(nsINode** _retval, bool aFront) { 207 if (!mCurrentNode) { 208 return NS_ERROR_FAILURE; 209 } 210 211 *_retval = nullptr; 212 213 ChildList children; 214 GetChildren(*mCurrentNode, children); 215 if (children.IsEmpty()) { 216 return NS_OK; 217 } 218 mSiblings = std::move(children); 219 mCurrentIndex = aFront ? 0 : mSiblings.Length() - 1; 220 mCurrentNode = mSiblings.ElementAt(mCurrentIndex); 221 NS_ADDREF(*_retval = mCurrentNode); 222 return NS_OK; 223 } 224 225 NS_IMETHODIMP 226 inDeepTreeWalker::FirstChild(nsINode** _retval) { 227 return EdgeChild(_retval, /* aFront = */ true); 228 } 229 230 NS_IMETHODIMP 231 inDeepTreeWalker::LastChild(nsINode** _retval) { 232 return EdgeChild(_retval, /* aFront = */ false); 233 } 234 235 NS_IMETHODIMP 236 inDeepTreeWalker::PreviousSibling(nsINode** _retval) { 237 *_retval = nullptr; 238 if (!mCurrentNode || mCurrentIndex < 1) { 239 return NS_OK; 240 } 241 242 nsINode* prev = mSiblings.ElementAt(--mCurrentIndex); 243 mCurrentNode = prev; 244 NS_ADDREF(*_retval = mCurrentNode); 245 return NS_OK; 246 } 247 248 NS_IMETHODIMP 249 inDeepTreeWalker::NextSibling(nsINode** _retval) { 250 *_retval = nullptr; 251 if (!mCurrentNode || mCurrentIndex + 1 >= (int32_t)mSiblings.Length()) { 252 return NS_OK; 253 } 254 255 nsINode* next = mSiblings.ElementAt(++mCurrentIndex); 256 mCurrentNode = next; 257 NS_ADDREF(*_retval = mCurrentNode); 258 return NS_OK; 259 } 260 261 NS_IMETHODIMP 262 inDeepTreeWalker::PreviousNode(nsINode** _retval) { 263 if (!mCurrentNode || mCurrentNode == mRoot) { 264 // Nowhere to go from here 265 *_retval = nullptr; 266 return NS_OK; 267 } 268 269 nsCOMPtr<nsINode> node; 270 PreviousSibling(getter_AddRefs(node)); 271 272 if (!node) { 273 return ParentNode(_retval); 274 } 275 276 // Now we're positioned at our previous sibling. But since the DOM tree 277 // traversal is depth-first, the previous node is its most deeply nested last 278 // child. Just loop until LastChild() returns null; since the LastChild() 279 // call that returns null won't affect our position, we will then be 280 // positioned at the correct node. 281 while (node) { 282 LastChild(getter_AddRefs(node)); 283 } 284 285 NS_ADDREF(*_retval = mCurrentNode); 286 return NS_OK; 287 } 288 289 NS_IMETHODIMP 290 inDeepTreeWalker::NextNode(nsINode** _retval) { 291 if (!mCurrentNode) { 292 return NS_OK; 293 } 294 295 // First try our kids 296 FirstChild(_retval); 297 298 if (*_retval) { 299 return NS_OK; 300 } 301 302 // Now keep trying next siblings up the parent chain, but if we 303 // discover there's nothing else restore our state. 304 #ifdef DEBUG 305 nsINode* origCurrentNode = mCurrentNode; 306 #endif 307 uint32_t lastChildCallsToMake = 0; 308 while (1) { 309 NextSibling(_retval); 310 311 if (*_retval) { 312 return NS_OK; 313 } 314 315 nsCOMPtr<nsINode> parent; 316 ParentNode(getter_AddRefs(parent)); 317 if (!parent) { 318 // Nowhere else to go; we're done. Restore our state. 319 while (lastChildCallsToMake--) { 320 nsCOMPtr<nsINode> dummy; 321 LastChild(getter_AddRefs(dummy)); 322 } 323 NS_ASSERTION(mCurrentNode == origCurrentNode, 324 "Didn't go back to the right node?"); 325 *_retval = nullptr; 326 return NS_OK; 327 } 328 ++lastChildCallsToMake; 329 } 330 331 MOZ_ASSERT_UNREACHABLE("how did we get here?"); 332 return NS_OK; 333 }