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 "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"
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 
nsXULPrototypeDocument()44 nsXULPrototypeDocument::nsXULPrototypeDocument()
45     : mRoot(nullptr), mLoaded(false), mCCGeneration(0), mWasL10nCached(false) {
46   ++gRefCnt;
47 }
48 
Init()49 nsresult nsXULPrototypeDocument::Init() {
50   mNodeInfoManager = new nsNodeInfoManager();
51   return mNodeInfoManager->Init(nullptr);
52 }
53 
~nsXULPrototypeDocument()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)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)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
Read(nsIObjectInputStream * aStream)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 
GetNodeInfos(nsXULPrototypeElement * aPrototype,nsTArray<RefPtr<mozilla::dom::NodeInfo>> & aArray)207 static nsresult 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   size_t i;
215   for (i = 0; i < aPrototype->mAttributes.Length(); ++i) {
216     RefPtr<mozilla::dom::NodeInfo> ni;
217     nsAttrName* name = &aPrototype->mAttributes[i].mName;
218     if (name->IsAtom()) {
219       ni = aPrototype->mNodeInfo->NodeInfoManager()->GetNodeInfo(
220           name->Atom(), nullptr, kNameSpaceID_None, nsINode::ATTRIBUTE_NODE);
221     } else {
222       ni = name->NodeInfo();
223     }
224 
225     if (aArray.IndexOf(ni) == aArray.NoIndex) {
226       aArray.AppendElement(ni);
227     }
228   }
229 
230   // Search children
231   for (i = 0; i < aPrototype->mChildren.Length(); ++i) {
232     nsXULPrototypeNode* child = aPrototype->mChildren[i];
233     if (child->mType == nsXULPrototypeNode::eType_Element) {
234       nsresult rv =
235           GetNodeInfos(static_cast<nsXULPrototypeElement*>(child), aArray);
236       NS_ENSURE_SUCCESS(rv, rv);
237     }
238   }
239 
240   return NS_OK;
241 }
242 
243 NS_IMETHODIMP
Write(nsIObjectOutputStream * aStream)244 nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream) {
245   nsresult rv;
246 
247   rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true);
248 
249   // nsIPrincipal mNodeInfoManager->mPrincipal
250   nsAutoCString JSON;
251   mozilla::BasePrincipal::Cast(mNodeInfoManager->DocumentPrincipal())
252       ->ToJSON(JSON);
253   nsresult tmp = aStream->WriteStringZ(JSON.get());
254   if (NS_FAILED(tmp)) {
255     rv = tmp;
256   }
257 
258 #ifdef DEBUG
259   // XXX Worrisome if we're caching things without system principal.
260   if (!mNodeInfoManager->DocumentPrincipal()->IsSystemPrincipal()) {
261     NS_WARNING("Serializing document without system principal");
262   }
263 #endif
264 
265   tmp = aStream->WriteBoolean(mWasL10nCached);
266   if (NS_FAILED(tmp)) {
267     rv = tmp;
268   }
269 
270   // mozilla::dom::NodeInfo table
271   nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
272   if (mRoot) {
273     tmp = GetNodeInfos(mRoot, nodeInfos);
274     if (NS_FAILED(tmp)) {
275       rv = tmp;
276     }
277   }
278 
279   uint32_t nodeInfoCount = nodeInfos.Length();
280   tmp = aStream->Write32(nodeInfoCount);
281   if (NS_FAILED(tmp)) {
282     rv = tmp;
283   }
284   uint32_t i;
285   for (i = 0; i < nodeInfoCount; ++i) {
286     mozilla::dom::NodeInfo* nodeInfo = nodeInfos[i];
287     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE);
288 
289     nsAutoString namespaceURI;
290     nodeInfo->GetNamespaceURI(namespaceURI);
291     tmp = aStream->WriteWStringZ(namespaceURI.get());
292     if (NS_FAILED(tmp)) {
293       rv = tmp;
294     }
295 
296     nsAutoString prefix;
297     nodeInfo->GetPrefix(prefix);
298     bool nullPrefix = DOMStringIsNull(prefix);
299     tmp = aStream->WriteBoolean(nullPrefix);
300     if (NS_FAILED(tmp)) {
301       rv = tmp;
302     }
303     if (!nullPrefix) {
304       tmp = aStream->WriteWStringZ(prefix.get());
305       if (NS_FAILED(tmp)) {
306         rv = tmp;
307       }
308     }
309 
310     nsAutoString localName;
311     nodeInfo->GetName(localName);
312     tmp = aStream->WriteWStringZ(localName.get());
313     if (NS_FAILED(tmp)) {
314       rv = tmp;
315     }
316   }
317 
318   // Now serialize the document contents
319   uint32_t count = mProcessingInstructions.Length();
320   for (i = 0; i < count; ++i) {
321     nsXULPrototypePI* pi = mProcessingInstructions[i];
322     tmp = pi->Serialize(aStream, this, &nodeInfos);
323     if (NS_FAILED(tmp)) {
324       rv = tmp;
325     }
326   }
327 
328   if (mRoot) {
329     tmp = mRoot->Serialize(aStream, this, &nodeInfos);
330     if (NS_FAILED(tmp)) {
331       rv = tmp;
332     }
333   }
334 
335   return rv;
336 }
337 
338 //----------------------------------------------------------------------
339 //
340 
InitPrincipal(nsIURI * aURI,nsIPrincipal * aPrincipal)341 nsresult nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI,
342                                                nsIPrincipal* aPrincipal) {
343   NS_ENSURE_ARG_POINTER(aURI);
344 
345   mURI = aURI;
346   mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
347   return NS_OK;
348 }
349 
GetURI()350 nsIURI* nsXULPrototypeDocument::GetURI() {
351   NS_ASSERTION(mURI, "null URI");
352   return mURI;
353 }
354 
GetRootElement()355 nsXULPrototypeElement* nsXULPrototypeDocument::GetRootElement() {
356   return mRoot;
357 }
358 
SetRootElement(nsXULPrototypeElement * aElement)359 void nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement) {
360   mRoot = aElement;
361 }
362 
AddProcessingInstruction(nsXULPrototypePI * aPI)363 nsresult nsXULPrototypeDocument::AddProcessingInstruction(
364     nsXULPrototypePI* aPI) {
365   MOZ_ASSERT(aPI, "null ptr");
366   // XXX(Bug 1631371) Check if this should use a fallible operation as it
367   // pretended earlier, or change the return type to void.
368   mProcessingInstructions.AppendElement(aPI);
369   return NS_OK;
370 }
371 
372 const nsTArray<RefPtr<nsXULPrototypePI>>&
GetProcessingInstructions() const373 nsXULPrototypeDocument::GetProcessingInstructions() const {
374   return mProcessingInstructions;
375 }
376 
DocumentPrincipal()377 nsIPrincipal* nsXULPrototypeDocument::DocumentPrincipal() {
378   MOZ_ASSERT(mNodeInfoManager, "missing nodeInfoManager");
379   return mNodeInfoManager->DocumentPrincipal();
380 }
381 
SetDocumentPrincipal(nsIPrincipal * aPrincipal)382 void nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal) {
383   mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
384 }
385 
MarkInCCGeneration(uint32_t aCCGeneration)386 void nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration) {
387   mCCGeneration = aCCGeneration;
388 }
389 
GetNodeInfoManager()390 nsNodeInfoManager* nsXULPrototypeDocument::GetNodeInfoManager() {
391   return mNodeInfoManager;
392 }
393 
AwaitLoadDone(Callback && aCallback,bool * aResult)394 nsresult nsXULPrototypeDocument::AwaitLoadDone(Callback&& aCallback,
395                                                bool* aResult) {
396   nsresult rv = NS_OK;
397 
398   *aResult = mLoaded;
399 
400   if (!mLoaded) {
401     // XXX(Bug 1631371) Check if this should use a fallible operation as it
402     // pretended earlier, or change the return type to void.
403     mPrototypeWaiters.AppendElement(std::move(aCallback));
404   }
405 
406   return rv;
407 }
408 
NotifyLoadDone()409 nsresult nsXULPrototypeDocument::NotifyLoadDone() {
410   // Call back to each XUL document that raced to start the same
411   // prototype document load, lost the race, but hit the XUL
412   // prototype cache because the winner filled the cache with
413   // the not-yet-loaded prototype object.
414 
415   mLoaded = true;
416 
417   for (uint32_t i = mPrototypeWaiters.Length(); i > 0;) {
418     --i;
419     mPrototypeWaiters[i]();
420   }
421   mPrototypeWaiters.Clear();
422 
423   return NS_OK;
424 }
425 
SetIsL10nCached(bool aIsCached)426 void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) {
427   mWasL10nCached = aIsCached;
428 }
429 
RebuildPrototypeFromElement(nsXULPrototypeElement * aPrototype,Element * aElement,bool aDeep)430 void nsXULPrototypeDocument::RebuildPrototypeFromElement(
431     nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) {
432   aPrototype->mHasIdAttribute = aElement->HasID();
433   aPrototype->mHasClassAttribute = aElement->MayHaveClass();
434   aPrototype->mHasStyleAttribute = aElement->MayHaveStyle();
435   NodeInfo* oldNodeInfo = aElement->NodeInfo();
436   RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
437       oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
438       oldNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
439   aPrototype->mNodeInfo = newNodeInfo;
440 
441   // First replace the prototype attributes with the new ones from this element.
442   aPrototype->mAttributes.Clear();
443 
444   uint32_t count = aElement->GetAttrCount();
445   nsXULPrototypeAttribute* protoAttr =
446       aPrototype->mAttributes.AppendElements(count);
447   for (uint32_t index = 0; index < count; index++) {
448     BorrowedAttrInfo attr = aElement->GetAttrInfoAt(index);
449 
450     if (attr.mName->IsAtom()) {
451       protoAttr->mName.SetTo(attr.mName->Atom());
452     } else {
453       NodeInfo* oldNodeInfo = attr.mName->NodeInfo();
454       RefPtr<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
455           oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
456           oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE);
457       protoAttr->mName.SetTo(newNodeInfo);
458     }
459     protoAttr->mValue.SetTo(*attr.mValue);
460 
461     protoAttr++;
462   }
463 
464   // Make sure the mIsAtom is correct in case this prototype element has been
465   // completely rebuilt.
466   CustomElementData* ceData = aElement->GetCustomElementData();
467   nsAtom* isAtom = ceData ? ceData->GetIs(aElement) : nullptr;
468   aPrototype->mIsAtom = isAtom;
469 
470   if (aDeep) {
471     // We have to rebuild the prototype children from this element.
472     // First release the tree under this element.
473     aPrototype->ReleaseSubtree();
474 
475     RefPtr<nsXULPrototypeNode>* children =
476         aPrototype->mChildren.AppendElements(aElement->GetChildCount());
477     for (nsIContent* child = aElement->GetFirstChild(); child;
478          child = child->GetNextSibling()) {
479       if (child->IsElement()) {
480         Element* element = child->AsElement();
481         RefPtr<nsXULPrototypeElement> elemProto = new nsXULPrototypeElement;
482         RebuildPrototypeFromElement(elemProto, element, true);
483         *children = elemProto;
484       } else if (child->IsText()) {
485         Text* text = child->AsText();
486         RefPtr<nsXULPrototypeText> textProto = new nsXULPrototypeText();
487         text->AppendTextTo(textProto->mValue);
488         *children = textProto;
489       } else {
490         MOZ_ASSERT(false, "We handle only elements and text nodes here.");
491       }
492 
493       children++;
494     }
495   }
496 }
497 
RebuildL10nPrototype(Element * aElement,bool aDeep)498 void nsXULPrototypeDocument::RebuildL10nPrototype(Element* aElement,
499                                                   bool aDeep) {
500   if (mWasL10nCached) {
501     return;
502   }
503 
504   Document* doc = aElement->OwnerDoc();
505 
506   nsAutoString id;
507   MOZ_RELEASE_ASSERT(aElement->GetAttr(nsGkAtoms::datal10nid, id));
508 
509   if (!doc) {
510     return;
511   }
512 
513   RefPtr<nsXULPrototypeElement> proto = doc->mL10nProtoElements.Get(aElement);
514   MOZ_RELEASE_ASSERT(proto);
515   RebuildPrototypeFromElement(proto, aElement, aDeep);
516 }
517