Bug 1707290 [wpt PR 28671] - Auto-expand details elements for find-in-page, a=testonly
[gecko.git] / dom / xul / nsXULPrototypeDocument.cpp
blob03ca13665fb9fa4a7ee7509d629a5dc2484d1f74
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/. */
6 #include "nsXULPrototypeDocument.h"
8 #include "nsXULElement.h"
9 #include "nsAString.h"
10 #include "nsIObjectInputStream.h"
11 #include "nsIObjectOutputStream.h"
12 #include "nsIPrincipal.h"
13 #include "nsJSPrincipals.h"
14 #include "nsIScriptObjectPrincipal.h"
15 #include "nsIURI.h"
16 #include "jsapi.h"
17 #include "jsfriendapi.h"
18 #include "nsString.h"
19 #include "nsDOMCID.h"
20 #include "nsNodeInfoManager.h"
21 #include "nsContentUtils.h"
22 #include "nsCCUncollectableMarker.h"
23 #include "xpcpublic.h"
24 #include "mozilla/BasePrincipal.h"
25 #include "mozilla/dom/BindingUtils.h"
26 #include "nsXULPrototypeCache.h"
27 #include "mozilla/DeclarationBlock.h"
28 #include "mozilla/dom/CustomElementRegistry.h"
29 #include "mozilla/dom/Document.h"
30 #include "mozilla/dom/Element.h"
31 #include "mozilla/dom/Text.h"
33 using namespace mozilla;
34 using namespace mozilla::dom;
35 using mozilla::dom::DestroyProtoAndIfaceCache;
37 uint32_t nsXULPrototypeDocument::gRefCnt;
39 //----------------------------------------------------------------------
41 // ctors, dtors, n' stuff
44 nsXULPrototypeDocument::nsXULPrototypeDocument()
45 : mRoot(nullptr),
46 mLoaded(false),
47 mCCGeneration(0),
48 mGCNumber(0),
49 mWasL10nCached(false) {
50 ++gRefCnt;
53 nsresult nsXULPrototypeDocument::Init() {
54 mNodeInfoManager = new nsNodeInfoManager();
55 return mNodeInfoManager->Init(nullptr);
58 nsXULPrototypeDocument::~nsXULPrototypeDocument() {
59 if (mRoot) mRoot->ReleaseSubtree();
62 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeDocument)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeWaiters)
66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeDocument)
68 if (nsCCUncollectableMarker::InGeneration(cb, tmp->mCCGeneration)) {
69 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
75 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPrototypeDocument)
76 NS_INTERFACE_MAP_ENTRY(nsISerializable)
77 NS_INTERFACE_MAP_ENTRY(nsISupports)
78 NS_INTERFACE_MAP_END
80 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPrototypeDocument)
81 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPrototypeDocument)
83 NS_IMETHODIMP
84 NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult) {
85 *aResult = nullptr;
86 RefPtr<nsXULPrototypeDocument> doc = new nsXULPrototypeDocument();
88 nsresult rv = doc->Init();
89 if (NS_FAILED(rv)) {
90 return rv;
93 doc.forget(aResult);
94 return rv;
97 //----------------------------------------------------------------------
99 // nsISerializable methods
102 NS_IMETHODIMP
103 nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) {
104 nsCOMPtr<nsISupports> supports;
105 nsresult rv = aStream->ReadObject(true, getter_AddRefs(supports));
106 if (NS_FAILED(rv)) {
107 return rv;
109 mURI = do_QueryInterface(supports);
111 // nsIPrincipal mNodeInfoManager->mPrincipal
112 nsAutoCString JSON;
113 rv = aStream->ReadCString(JSON);
114 if (NS_FAILED(rv)) {
115 return rv;
117 nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::FromJSON(JSON);
119 // Better safe than sorry....
120 mNodeInfoManager->SetDocumentPrincipal(principal);
122 rv = aStream->ReadBoolean(&mWasL10nCached);
123 if (NS_FAILED(rv)) {
124 return rv;
127 mRoot = new nsXULPrototypeElement();
129 // mozilla::dom::NodeInfo table
130 nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
132 uint32_t count, i;
133 rv = aStream->Read32(&count);
134 if (NS_FAILED(rv)) {
135 return rv;
137 nsAutoString namespaceURI, prefixStr, localName;
138 bool prefixIsNull;
139 RefPtr<nsAtom> prefix;
140 for (i = 0; i < count; ++i) {
141 rv = aStream->ReadString(namespaceURI);
142 if (NS_FAILED(rv)) {
143 return rv;
145 rv = aStream->ReadBoolean(&prefixIsNull);
146 if (NS_FAILED(rv)) {
147 return rv;
149 if (prefixIsNull) {
150 prefix = nullptr;
151 } else {
152 rv = aStream->ReadString(prefixStr);
153 if (NS_FAILED(rv)) {
154 return rv;
156 prefix = NS_Atomize(prefixStr);
158 rv = aStream->ReadString(localName);
159 if (NS_FAILED(rv)) {
160 return rv;
163 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
164 // Using UINT16_MAX here as we don't know which nodeinfos will be
165 // used for attributes and which for elements. And that doesn't really
166 // matter.
167 rv = mNodeInfoManager->GetNodeInfo(localName, prefix, namespaceURI,
168 UINT16_MAX, getter_AddRefs(nodeInfo));
169 if (NS_FAILED(rv)) {
170 return rv;
172 nodeInfos.AppendElement(nodeInfo);
175 // Document contents
176 uint32_t type;
177 while (NS_SUCCEEDED(rv)) {
178 rv = aStream->Read32(&type);
179 if (NS_FAILED(rv)) {
180 return rv;
181 break;
184 if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) {
185 RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
187 rv = pi->Deserialize(aStream, this, mURI, &nodeInfos);
188 if (NS_FAILED(rv)) {
189 return rv;
191 rv = AddProcessingInstruction(pi);
192 if (NS_FAILED(rv)) {
193 return rv;
195 } else if ((nsXULPrototypeNode::Type)type ==
196 nsXULPrototypeNode::eType_Element) {
197 rv = mRoot->Deserialize(aStream, this, mURI, &nodeInfos);
198 if (NS_FAILED(rv)) {
199 return rv;
201 break;
202 } else {
203 MOZ_ASSERT_UNREACHABLE("Unexpected prototype node type");
204 return NS_ERROR_FAILURE;
208 return NotifyLoadDone();
211 static nsresult GetNodeInfos(nsXULPrototypeElement* aPrototype,
212 nsTArray<RefPtr<mozilla::dom::NodeInfo>>& aArray) {
213 if (aArray.IndexOf(aPrototype->mNodeInfo) == aArray.NoIndex) {
214 aArray.AppendElement(aPrototype->mNodeInfo);
217 // Search attributes
218 size_t i;
219 for (i = 0; i < aPrototype->mAttributes.Length(); ++i) {
220 RefPtr<mozilla::dom::NodeInfo> ni;
221 nsAttrName* name = &aPrototype->mAttributes[i].mName;
222 if (name->IsAtom()) {
223 ni = aPrototype->mNodeInfo->NodeInfoManager()->GetNodeInfo(
224 name->Atom(), nullptr, kNameSpaceID_None, nsINode::ATTRIBUTE_NODE);
225 } else {
226 ni = name->NodeInfo();
229 if (aArray.IndexOf(ni) == aArray.NoIndex) {
230 aArray.AppendElement(ni);
234 // Search children
235 for (i = 0; i < aPrototype->mChildren.Length(); ++i) {
236 nsXULPrototypeNode* child = aPrototype->mChildren[i];
237 if (child->mType == nsXULPrototypeNode::eType_Element) {
238 nsresult rv =
239 GetNodeInfos(static_cast<nsXULPrototypeElement*>(child), aArray);
240 NS_ENSURE_SUCCESS(rv, rv);
244 return NS_OK;
247 NS_IMETHODIMP
248 nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream) {
249 nsresult rv;
251 rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true);
253 // nsIPrincipal mNodeInfoManager->mPrincipal
254 nsAutoCString JSON;
255 mozilla::BasePrincipal::Cast(mNodeInfoManager->DocumentPrincipal())
256 ->ToJSON(JSON);
257 nsresult tmp = aStream->WriteStringZ(JSON.get());
258 if (NS_FAILED(tmp)) {
259 rv = tmp;
262 #ifdef DEBUG
263 // XXX Worrisome if we're caching things without system principal.
264 if (!mNodeInfoManager->DocumentPrincipal()->IsSystemPrincipal()) {
265 NS_WARNING("Serializing document without system principal");
267 #endif
269 tmp = aStream->WriteBoolean(mWasL10nCached);
270 if (NS_FAILED(tmp)) {
271 rv = tmp;
274 // mozilla::dom::NodeInfo table
275 nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
276 if (mRoot) {
277 tmp = GetNodeInfos(mRoot, nodeInfos);
278 if (NS_FAILED(tmp)) {
279 rv = tmp;
283 uint32_t nodeInfoCount = nodeInfos.Length();
284 tmp = aStream->Write32(nodeInfoCount);
285 if (NS_FAILED(tmp)) {
286 rv = tmp;
288 uint32_t i;
289 for (i = 0; i < nodeInfoCount; ++i) {
290 mozilla::dom::NodeInfo* nodeInfo = nodeInfos[i];
291 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE);
293 nsAutoString namespaceURI;
294 nodeInfo->GetNamespaceURI(namespaceURI);
295 tmp = aStream->WriteWStringZ(namespaceURI.get());
296 if (NS_FAILED(tmp)) {
297 rv = tmp;
300 nsAutoString prefix;
301 nodeInfo->GetPrefix(prefix);
302 bool nullPrefix = DOMStringIsNull(prefix);
303 tmp = aStream->WriteBoolean(nullPrefix);
304 if (NS_FAILED(tmp)) {
305 rv = tmp;
307 if (!nullPrefix) {
308 tmp = aStream->WriteWStringZ(prefix.get());
309 if (NS_FAILED(tmp)) {
310 rv = tmp;
314 nsAutoString localName;
315 nodeInfo->GetName(localName);
316 tmp = aStream->WriteWStringZ(localName.get());
317 if (NS_FAILED(tmp)) {
318 rv = tmp;
322 // Now serialize the document contents
323 uint32_t count = mProcessingInstructions.Length();
324 for (i = 0; i < count; ++i) {
325 nsXULPrototypePI* pi = mProcessingInstructions[i];
326 tmp = pi->Serialize(aStream, this, &nodeInfos);
327 if (NS_FAILED(tmp)) {
328 rv = tmp;
332 if (mRoot) {
333 tmp = mRoot->Serialize(aStream, this, &nodeInfos);
334 if (NS_FAILED(tmp)) {
335 rv = tmp;
339 return rv;
342 //----------------------------------------------------------------------
345 nsresult nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI,
346 nsIPrincipal* aPrincipal) {
347 NS_ENSURE_ARG_POINTER(aURI);
349 mURI = aURI;
350 mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
351 return NS_OK;
354 nsIURI* nsXULPrototypeDocument::GetURI() {
355 NS_ASSERTION(mURI, "null URI");
356 return mURI;
359 nsXULPrototypeElement* nsXULPrototypeDocument::GetRootElement() {
360 return mRoot;
363 void nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement) {
364 mRoot = aElement;
367 nsresult nsXULPrototypeDocument::AddProcessingInstruction(
368 nsXULPrototypePI* aPI) {
369 MOZ_ASSERT(aPI, "null ptr");
370 // XXX(Bug 1631371) Check if this should use a fallible operation as it
371 // pretended earlier, or change the return type to void.
372 mProcessingInstructions.AppendElement(aPI);
373 return NS_OK;
376 const nsTArray<RefPtr<nsXULPrototypePI>>&
377 nsXULPrototypeDocument::GetProcessingInstructions() const {
378 return mProcessingInstructions;
381 nsIPrincipal* nsXULPrototypeDocument::DocumentPrincipal() {
382 MOZ_ASSERT(mNodeInfoManager, "missing nodeInfoManager");
383 return mNodeInfoManager->DocumentPrincipal();
386 void nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal) {
387 mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
390 void nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration) {
391 mCCGeneration = aCCGeneration;
394 nsNodeInfoManager* nsXULPrototypeDocument::GetNodeInfoManager() {
395 return mNodeInfoManager;
398 nsresult nsXULPrototypeDocument::AwaitLoadDone(Callback&& aCallback,
399 bool* aResult) {
400 nsresult rv = NS_OK;
402 *aResult = mLoaded;
404 if (!mLoaded) {
405 // XXX(Bug 1631371) Check if this should use a fallible operation as it
406 // pretended earlier, or change the return type to void.
407 mPrototypeWaiters.AppendElement(std::move(aCallback));
410 return rv;
413 nsresult nsXULPrototypeDocument::NotifyLoadDone() {
414 // Call back to each XUL document that raced to start the same
415 // prototype document load, lost the race, but hit the XUL
416 // prototype cache because the winner filled the cache with
417 // the not-yet-loaded prototype object.
419 mLoaded = true;
421 for (uint32_t i = mPrototypeWaiters.Length(); i > 0;) {
422 --i;
423 mPrototypeWaiters[i]();
425 mPrototypeWaiters.Clear();
427 return NS_OK;
430 void nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc) {
431 // Only trace the protos once per GC if we are marking.
432 if (aTrc->isMarkingTracer()) {
433 uint32_t currentGCNumber = aTrc->gcNumberForMarking();
434 if (mGCNumber == currentGCNumber) {
435 return;
437 mGCNumber = currentGCNumber;
440 if (mRoot) {
441 mRoot->TraceAllScripts(aTrc);
445 void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) {
446 mWasL10nCached = aIsCached;
449 void nsXULPrototypeDocument::RebuildPrototypeFromElement(
450 nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) {
451 aPrototype->mHasIdAttribute = aElement->HasID();
452 aPrototype->mHasClassAttribute = aElement->MayHaveClass();
453 aPrototype->mHasStyleAttribute = aElement->MayHaveStyle();
454 NodeInfo* oldNodeInfo = aElement->NodeInfo();
455 RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
456 oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
457 oldNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
458 aPrototype->mNodeInfo = newNodeInfo;
460 // First replace the prototype attributes with the new ones from this element.
461 aPrototype->mAttributes.Clear();
463 uint32_t count = aElement->GetAttrCount();
464 nsXULPrototypeAttribute* protoAttr =
465 aPrototype->mAttributes.AppendElements(count);
466 for (uint32_t index = 0; index < count; index++) {
467 BorrowedAttrInfo attr = aElement->GetAttrInfoAt(index);
469 if (attr.mName->IsAtom()) {
470 protoAttr->mName.SetTo(attr.mName->Atom());
471 } else {
472 NodeInfo* oldNodeInfo = attr.mName->NodeInfo();
473 RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
474 oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
475 oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE);
476 protoAttr->mName.SetTo(newNodeInfo);
478 protoAttr->mValue.SetTo(*attr.mValue);
480 protoAttr++;
483 // Make sure the mIsAtom is correct in case this prototype element has been
484 // completely rebuilt.
485 CustomElementData* ceData = aElement->GetCustomElementData();
486 nsAtom* isAtom = ceData ? ceData->GetIs(aElement) : nullptr;
487 aPrototype->mIsAtom = isAtom;
489 if (aDeep) {
490 // We have to rebuild the prototype children from this element.
491 // First release the tree under this element.
492 aPrototype->ReleaseSubtree();
494 RefPtr<nsXULPrototypeNode>* children =
495 aPrototype->mChildren.AppendElements(aElement->GetChildCount());
496 for (nsIContent* child = aElement->GetFirstChild(); child;
497 child = child->GetNextSibling()) {
498 if (child->IsElement()) {
499 Element* element = child->AsElement();
500 RefPtr<nsXULPrototypeElement> elemProto = new nsXULPrototypeElement;
501 RebuildPrototypeFromElement(elemProto, element, true);
502 *children = elemProto;
503 } else if (child->IsText()) {
504 Text* text = child->AsText();
505 RefPtr<nsXULPrototypeText> textProto = new nsXULPrototypeText();
506 text->AppendTextTo(textProto->mValue);
507 *children = textProto;
508 } else {
509 MOZ_ASSERT(false, "We handle only elements and text nodes here.");
512 children++;
517 void nsXULPrototypeDocument::RebuildL10nPrototype(Element* aElement,
518 bool aDeep) {
519 if (mWasL10nCached) {
520 return;
523 Document* doc = aElement->OwnerDoc();
525 nsAutoString id;
526 MOZ_RELEASE_ASSERT(aElement->GetAttr(nsGkAtoms::datal10nid, id));
528 if (!doc) {
529 return;
532 RefPtr<nsXULPrototypeElement> proto = doc->mL10nProtoElements.Get(aElement);
533 MOZ_RELEASE_ASSERT(proto);
534 RebuildPrototypeFromElement(proto, aElement, aDeep);