nsXULPrototypeDocument.cpp (14569B)
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 "nsXULPrototypeDocument.h" 7 8 #include "jsapi.h" 9 #include "jsfriendapi.h" 10 #include "mozilla/BasePrincipal.h" 11 #include "mozilla/DeclarationBlock.h" 12 #include "mozilla/dom/BindingUtils.h" 13 #include "mozilla/dom/CustomElementRegistry.h" 14 #include "mozilla/dom/Document.h" 15 #include "mozilla/dom/Element.h" 16 #include "mozilla/dom/Text.h" 17 #include "nsAString.h" 18 #include "nsCCUncollectableMarker.h" 19 #include "nsContentUtils.h" 20 #include "nsDOMCID.h" 21 #include "nsIObjectInputStream.h" 22 #include "nsIObjectOutputStream.h" 23 #include "nsIPrincipal.h" 24 #include "nsIScriptObjectPrincipal.h" 25 #include "nsIURI.h" 26 #include "nsJSPrincipals.h" 27 #include "nsNodeInfoManager.h" 28 #include "nsString.h" 29 #include "nsXULElement.h" 30 #include "nsXULPrototypeCache.h" 31 #include "xpcpublic.h" 32 33 using namespace mozilla; 34 using namespace mozilla::dom; 35 using mozilla::dom::DestroyProtoAndIfaceCache; 36 37 uint32_t nsXULPrototypeDocument::gRefCnt; 38 39 //---------------------------------------------------------------------- 40 // 41 // ctors, dtors, n' stuff 42 // 43 44 nsXULPrototypeDocument::nsXULPrototypeDocument() 45 : mRoot(nullptr), mLoaded(false), mCCGeneration(0), mWasL10nCached(false) { 46 ++gRefCnt; 47 } 48 49 nsresult nsXULPrototypeDocument::Init() { 50 mNodeInfoManager = new nsNodeInfoManager(nullptr, nullptr); 51 return NS_OK; 52 } 53 54 nsXULPrototypeDocument::~nsXULPrototypeDocument() { 55 if (mRoot) mRoot->ReleaseSubtree(); 56 } 57 58 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument) 59 60 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeDocument) 61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeWaiters) 62 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeDocument) 64 if (nsCCUncollectableMarker::InGeneration(cb, tmp->mCCGeneration)) { 65 return NS_SUCCESS_INTERRUPTED_TRAVERSE; 66 } 67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) 68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager) 69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 70 71 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPrototypeDocument) 72 NS_INTERFACE_MAP_ENTRY(nsISerializable) 73 NS_INTERFACE_MAP_ENTRY(nsISupports) 74 NS_INTERFACE_MAP_END 75 76 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPrototypeDocument) 77 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPrototypeDocument) 78 79 NS_IMETHODIMP 80 NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult) { 81 *aResult = nullptr; 82 RefPtr<nsXULPrototypeDocument> doc = new nsXULPrototypeDocument(); 83 84 nsresult rv = doc->Init(); 85 if (NS_FAILED(rv)) { 86 return rv; 87 } 88 89 doc.forget(aResult); 90 return rv; 91 } 92 93 //---------------------------------------------------------------------- 94 // 95 // nsISerializable methods 96 // 97 98 NS_IMETHODIMP 99 nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) { 100 nsCOMPtr<nsISupports> supports; 101 nsresult rv = aStream->ReadObject(true, getter_AddRefs(supports)); 102 if (NS_FAILED(rv)) { 103 return rv; 104 } 105 mURI = do_QueryInterface(supports); 106 107 // nsIPrincipal mNodeInfoManager->mPrincipal 108 nsAutoCString JSON; 109 rv = aStream->ReadCString(JSON); 110 if (NS_FAILED(rv)) { 111 return rv; 112 } 113 nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::FromJSON(JSON); 114 115 // Better safe than sorry.... 116 mNodeInfoManager->SetDocumentPrincipal(principal); 117 118 rv = aStream->ReadBoolean(&mWasL10nCached); 119 if (NS_FAILED(rv)) { 120 return rv; 121 } 122 123 mRoot = new nsXULPrototypeElement(); 124 125 // mozilla::dom::NodeInfo table 126 nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos; 127 128 uint32_t count, i; 129 rv = aStream->Read32(&count); 130 if (NS_FAILED(rv)) { 131 return rv; 132 } 133 nsAutoString namespaceURI, prefixStr, localName; 134 bool prefixIsNull; 135 RefPtr<nsAtom> prefix; 136 for (i = 0; i < count; ++i) { 137 rv = aStream->ReadString(namespaceURI); 138 if (NS_FAILED(rv)) { 139 return rv; 140 } 141 rv = aStream->ReadBoolean(&prefixIsNull); 142 if (NS_FAILED(rv)) { 143 return rv; 144 } 145 if (prefixIsNull) { 146 prefix = nullptr; 147 } else { 148 rv = aStream->ReadString(prefixStr); 149 if (NS_FAILED(rv)) { 150 return rv; 151 } 152 prefix = NS_Atomize(prefixStr); 153 } 154 rv = aStream->ReadString(localName); 155 if (NS_FAILED(rv)) { 156 return rv; 157 } 158 159 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 160 // Using UINT16_MAX here as we don't know which nodeinfos will be 161 // used for attributes and which for elements. And that doesn't really 162 // matter. 163 rv = mNodeInfoManager->GetNodeInfo(localName, prefix, namespaceURI, 164 UINT16_MAX, getter_AddRefs(nodeInfo)); 165 if (NS_FAILED(rv)) { 166 return rv; 167 } 168 nodeInfos.AppendElement(nodeInfo); 169 } 170 171 // Document contents 172 uint32_t type; 173 while (NS_SUCCEEDED(rv)) { 174 rv = aStream->Read32(&type); 175 if (NS_FAILED(rv)) { 176 return rv; 177 break; 178 } 179 180 if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) { 181 RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI(); 182 183 rv = pi->Deserialize(aStream, this, mURI, &nodeInfos); 184 if (NS_FAILED(rv)) { 185 return rv; 186 } 187 rv = AddProcessingInstruction(pi); 188 if (NS_FAILED(rv)) { 189 return rv; 190 } 191 } else if ((nsXULPrototypeNode::Type)type == 192 nsXULPrototypeNode::eType_Element) { 193 rv = mRoot->Deserialize(aStream, this, mURI, &nodeInfos); 194 if (NS_FAILED(rv)) { 195 return rv; 196 } 197 break; 198 } else { 199 MOZ_ASSERT_UNREACHABLE("Unexpected prototype node type"); 200 return NS_ERROR_FAILURE; 201 } 202 } 203 204 return NotifyLoadDone(); 205 } 206 207 static void GetNodeInfos(nsXULPrototypeElement* aPrototype, 208 nsTArray<RefPtr<mozilla::dom::NodeInfo>>& aArray) { 209 if (aArray.IndexOf(aPrototype->mNodeInfo) == aArray.NoIndex) { 210 aArray.AppendElement(aPrototype->mNodeInfo); 211 } 212 213 // Search attributes 214 for (nsXULPrototypeAttribute& attr : aPrototype->mAttributes) { 215 RefPtr<mozilla::dom::NodeInfo> ni; 216 nsAttrName* name = &attr.mName; 217 if (name->IsAtom()) { 218 ni = aPrototype->mNodeInfo->NodeInfoManager()->GetNodeInfo( 219 name->Atom(), nullptr, kNameSpaceID_None, nsINode::ATTRIBUTE_NODE); 220 } else { 221 ni = name->NodeInfo(); 222 } 223 224 if (aArray.IndexOf(ni) == aArray.NoIndex) { 225 aArray.AppendElement(ni); 226 } 227 } 228 229 // Search children 230 for (nsXULPrototypeNode* child : aPrototype->mChildren) { 231 if (child->mType == nsXULPrototypeNode::eType_Element) { 232 GetNodeInfos(static_cast<nsXULPrototypeElement*>(child), aArray); 233 } 234 } 235 } 236 237 NS_IMETHODIMP 238 nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream) { 239 nsresult rv; 240 241 rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true); 242 243 // nsIPrincipal mNodeInfoManager->mPrincipal 244 nsAutoCString JSON; 245 mozilla::BasePrincipal::Cast(mNodeInfoManager->DocumentPrincipal()) 246 ->ToJSON(JSON); 247 nsresult tmp = aStream->WriteStringZ(JSON.get()); 248 if (NS_FAILED(tmp)) { 249 rv = tmp; 250 } 251 252 #ifdef DEBUG 253 // XXX Worrisome if we're caching things without system principal. 254 if (!mNodeInfoManager->DocumentPrincipal()->IsSystemPrincipal()) { 255 NS_WARNING("Serializing document without system principal"); 256 } 257 #endif 258 259 tmp = aStream->WriteBoolean(mWasL10nCached); 260 if (NS_FAILED(tmp)) { 261 rv = tmp; 262 } 263 264 // mozilla::dom::NodeInfo table 265 nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos; 266 if (mRoot) { 267 GetNodeInfos(mRoot, nodeInfos); 268 } 269 270 uint32_t nodeInfoCount = nodeInfos.Length(); 271 tmp = aStream->Write32(nodeInfoCount); 272 if (NS_FAILED(tmp)) { 273 rv = tmp; 274 } 275 uint32_t i; 276 for (i = 0; i < nodeInfoCount; ++i) { 277 mozilla::dom::NodeInfo* nodeInfo = nodeInfos[i]; 278 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE); 279 280 nsAutoString namespaceURI; 281 nodeInfo->GetNamespaceURI(namespaceURI); 282 tmp = aStream->WriteWStringZ(namespaceURI.get()); 283 if (NS_FAILED(tmp)) { 284 rv = tmp; 285 } 286 287 nsAutoString prefix; 288 nodeInfo->GetPrefix(prefix); 289 bool nullPrefix = DOMStringIsNull(prefix); 290 tmp = aStream->WriteBoolean(nullPrefix); 291 if (NS_FAILED(tmp)) { 292 rv = tmp; 293 } 294 if (!nullPrefix) { 295 tmp = aStream->WriteWStringZ(prefix.get()); 296 if (NS_FAILED(tmp)) { 297 rv = tmp; 298 } 299 } 300 301 nsAutoString localName; 302 nodeInfo->GetName(localName); 303 tmp = aStream->WriteWStringZ(localName.get()); 304 if (NS_FAILED(tmp)) { 305 rv = tmp; 306 } 307 } 308 309 // Now serialize the document contents 310 uint32_t count = mProcessingInstructions.Length(); 311 for (i = 0; i < count; ++i) { 312 nsXULPrototypePI* pi = mProcessingInstructions[i]; 313 tmp = pi->Serialize(aStream, this, &nodeInfos); 314 if (NS_FAILED(tmp)) { 315 rv = tmp; 316 } 317 } 318 319 if (mRoot) { 320 tmp = mRoot->Serialize(aStream, this, &nodeInfos); 321 if (NS_FAILED(tmp)) { 322 rv = tmp; 323 } 324 } 325 326 return rv; 327 } 328 329 //---------------------------------------------------------------------- 330 // 331 332 nsresult nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI, 333 nsIPrincipal* aPrincipal) { 334 NS_ENSURE_ARG_POINTER(aURI); 335 336 mURI = aURI; 337 mNodeInfoManager->SetDocumentPrincipal(aPrincipal); 338 return NS_OK; 339 } 340 341 nsIURI* nsXULPrototypeDocument::GetURI() { 342 NS_ASSERTION(mURI, "null URI"); 343 return mURI; 344 } 345 346 nsXULPrototypeElement* nsXULPrototypeDocument::GetRootElement() { 347 return mRoot; 348 } 349 350 void nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement) { 351 mRoot = aElement; 352 } 353 354 nsresult nsXULPrototypeDocument::AddProcessingInstruction( 355 nsXULPrototypePI* aPI) { 356 MOZ_ASSERT(aPI, "null ptr"); 357 // XXX(Bug 1631371) Check if this should use a fallible operation as it 358 // pretended earlier, or change the return type to void. 359 mProcessingInstructions.AppendElement(aPI); 360 return NS_OK; 361 } 362 363 const nsTArray<RefPtr<nsXULPrototypePI>>& 364 nsXULPrototypeDocument::GetProcessingInstructions() const { 365 return mProcessingInstructions; 366 } 367 368 nsIPrincipal* nsXULPrototypeDocument::DocumentPrincipal() { 369 MOZ_ASSERT(mNodeInfoManager, "missing nodeInfoManager"); 370 return mNodeInfoManager->DocumentPrincipal(); 371 } 372 373 void nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal) { 374 mNodeInfoManager->SetDocumentPrincipal(aPrincipal); 375 } 376 377 void nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration) { 378 mCCGeneration = aCCGeneration; 379 } 380 381 nsNodeInfoManager* nsXULPrototypeDocument::GetNodeInfoManager() { 382 return mNodeInfoManager; 383 } 384 385 nsresult nsXULPrototypeDocument::AwaitLoadDone(Callback&& aCallback, 386 bool* aResult) { 387 nsresult rv = NS_OK; 388 389 *aResult = mLoaded; 390 391 if (!mLoaded) { 392 // XXX(Bug 1631371) Check if this should use a fallible operation as it 393 // pretended earlier, or change the return type to void. 394 mPrototypeWaiters.AppendElement(std::move(aCallback)); 395 } 396 397 return rv; 398 } 399 400 nsresult nsXULPrototypeDocument::NotifyLoadDone() { 401 // Call back to each XUL document that raced to start the same 402 // prototype document load, lost the race, but hit the XUL 403 // prototype cache because the winner filled the cache with 404 // the not-yet-loaded prototype object. 405 406 mLoaded = true; 407 408 for (uint32_t i = mPrototypeWaiters.Length(); i > 0;) { 409 --i; 410 mPrototypeWaiters[i](); 411 } 412 mPrototypeWaiters.Clear(); 413 414 return NS_OK; 415 } 416 417 void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) { 418 mWasL10nCached = aIsCached; 419 } 420 421 void nsXULPrototypeDocument::RebuildPrototypeFromElement( 422 nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) { 423 NodeInfo* oldNodeInfo = aElement->NodeInfo(); 424 RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo( 425 oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(), 426 oldNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE); 427 aPrototype->mNodeInfo = newNodeInfo; 428 429 // First replace the prototype attributes with the new ones from this element. 430 aPrototype->mAttributes.Clear(); 431 432 uint32_t count = aElement->GetAttrCount(); 433 nsXULPrototypeAttribute* protoAttr = 434 aPrototype->mAttributes.AppendElements(count); 435 for (uint32_t index = 0; index < count; index++) { 436 BorrowedAttrInfo attr = aElement->GetAttrInfoAt(index); 437 438 if (attr.mName->IsAtom()) { 439 protoAttr->mName.SetTo(attr.mName->Atom()); 440 } else { 441 NodeInfo* oldNodeInfo = attr.mName->NodeInfo(); 442 RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo( 443 oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(), 444 oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE); 445 protoAttr->mName.SetTo(newNodeInfo); 446 } 447 protoAttr->mValue.SetTo(*attr.mValue); 448 if (protoAttr->mValue.Type() == nsAttrValue::eCSSDeclaration) { 449 // Ensure declarations get copied-on-write if needed. 450 protoAttr->mValue.GetCSSDeclarationValue()->SetImmutable(); 451 } 452 protoAttr++; 453 } 454 455 // Make sure the mIsAtom is correct in case this prototype element has been 456 // completely rebuilt. 457 CustomElementData* ceData = aElement->GetCustomElementData(); 458 nsAtom* isAtom = ceData ? ceData->GetIs(aElement) : nullptr; 459 aPrototype->mIsAtom = isAtom; 460 461 if (aDeep) { 462 // We have to rebuild the prototype children from this element. 463 // First release the tree under this element. 464 aPrototype->ReleaseSubtree(); 465 466 RefPtr<nsXULPrototypeNode>* children = 467 aPrototype->mChildren.AppendElements(aElement->GetChildCount()); 468 for (nsIContent* child = aElement->GetFirstChild(); child; 469 child = child->GetNextSibling()) { 470 if (child->IsElement()) { 471 Element* element = child->AsElement(); 472 RefPtr<nsXULPrototypeElement> elemProto = new nsXULPrototypeElement; 473 RebuildPrototypeFromElement(elemProto, element, true); 474 *children = elemProto; 475 } else if (child->IsText()) { 476 Text* text = child->AsText(); 477 RefPtr<nsXULPrototypeText> textProto = new nsXULPrototypeText(); 478 text->AppendTextTo(textProto->mValue); 479 *children = textProto; 480 } else { 481 MOZ_ASSERT(false, "We handle only elements and text nodes here."); 482 } 483 484 children++; 485 } 486 } 487 } 488 489 void nsXULPrototypeDocument::RebuildL10nPrototype(Element* aElement, 490 bool aDeep) { 491 if (mWasL10nCached) { 492 return; 493 } 494 495 MOZ_ASSERT(aElement->HasAttr(nsGkAtoms::datal10nid)); 496 497 Document* doc = aElement->OwnerDoc(); 498 if (RefPtr<nsXULPrototypeElement> proto = 499 doc->mL10nProtoElements.Get(aElement)) { 500 RebuildPrototypeFromElement(proto, aElement, aDeep); 501 } 502 }