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